Code Standards and Conventions
Coding standards, workspace boundaries, and conventions for contributing to LibreChat.
Workspace Boundaries
LibreChat is a monorepo. All new code should target the correct workspace:
| Workspace | Language | Side | Purpose |
|---|---|---|---|
/api | JS (legacy) | Backend | Express server — minimize changes here |
/packages/api | TypeScript | Backend | New backend code lives here (TS only, consumed by /api) |
/packages/data-schemas | TypeScript | Backend | Database models/schemas |
/packages/data-provider | TypeScript | Shared | API types, endpoints, data-service — used by frontend and backend |
/client | TypeScript/React | Frontend | Frontend SPA |
/packages/client | TypeScript | Frontend | Shared frontend utilities |
- All new backend code must be TypeScript in
/packages/api. - Keep
/apichanges to the absolute minimum (thin JS wrappers calling into/packages/api). - Database-specific shared logic goes in
/packages/data-schemas. - Frontend/backend shared API logic (endpoints, types, data-service) goes in
/packages/data-provider. - Build all compiled code from project root:
npm run build.
General Guidelines
- Use "clean code" principles: keep functions and modules small, adhere to the single responsibility principle, and write expressive and readable code.
- Use meaningful and descriptive variable and function names.
- Prioritize code readability and maintainability over brevity.
- Use the provided
.eslintrcand.prettierrcfiles for consistent code formatting. - Fix all formatting lint errors using auto-fix when available. All TypeScript/ESLint warnings and errors must be resolved.
Code Structure
- Never-nesting: use early returns, flat code, minimal indentation. Break complex operations into well-named helpers.
- Functional first: pure functions, immutable data,
map/filter/reduceover imperative loops. Only reach for OOP when it clearly improves domain modeling or state encapsulation. - No dynamic imports unless absolutely necessary.
- Extract repeated logic into dedicated utility functions (DRY).
Iteration and Performance
- Minimize looping — especially over shared data structures like message arrays, which are iterated frequently. Every additional pass adds up at scale.
- Consolidate sequential O(n) operations into a single pass whenever possible; never loop over the same collection twice if the work can be combined.
- Choose data structures that reduce the need to iterate (e.g.,
Map/Setfor lookups instead ofArray.find/Array.includes). - Avoid unnecessary object creation; consider space-time tradeoffs.
- Prevent memory leaks: be careful with closures, dispose resources/event listeners, avoid circular references.
Type Safety
- Never use
any. Explicit types for all parameters, return values, and variables. - Limit
unknown— avoidunknown,Record<string, unknown>, andas unknown as Tassertions. ARecord<string, unknown>almost always signals a missing explicit type definition. - Don't duplicate types — check whether a type already exists in the project (especially
packages/data-provider) before defining a new one. Reuse and extend existing types. - Use union types, generics, and interfaces appropriately.
Comments and Documentation
- Write self-documenting code; no inline comments narrating what code does.
- JSDoc only for complex/non-obvious logic or intellisense on public APIs.
- Single-line JSDoc for brief docs, multi-line for complex cases.
- Avoid standalone
//comments unless absolutely necessary.
Import Order
Imports are organized into three sections (in order):
- Package imports — sorted from shortest to longest line length (
reactis always the first import). import typeimports — sorted from longest to shortest (package types first, then local types; length sorting resets between sub-groups).- Local/project imports — sorted from longest to shortest.
- Consolidate value imports from the same module as much as possible.
- Always use standalone
import type { ... }for type imports; never use inlinetypekeyword inside value imports (e.g.,import { Foo, type Bar }is wrong).
Loop Preferences
- Limit looping as much as possible. Prefer single-pass transformations and avoid re-iterating the same data.
for (let i = 0; ...)for performance-critical or index-dependent operations.for...offor simple array iteration.for...inonly for object property enumeration.
Node.js API Server
API Design
- Follow RESTful principles when designing APIs.
- Use meaningful and descriptive names for routes, controllers, services, and models.
- Use appropriate HTTP methods (GET, POST, PUT, DELETE) for each route.
- Use proper status codes and response structures for consistent API responses (2xx for success, 4xx for bad request from client, 5xx for server error).
- Use try-catch blocks to catch and handle exceptions gracefully.
- Implement proper error handling and consistently return appropriate error responses.
- Use the logging system included in the
utilsdirectory to log important events and errors. - Do JWT-based, stateless authentication using the
requireJWTAuthmiddleware.
File Structure
New backend code goes in /packages/api as TypeScript. The legacy /api directory follows this structure:
Routes
Specifies each HTTP request method, any middleware to be used, and the controller function to be called for each route.
- Define routes using the Express Router in separate files for each resource or logical grouping.
- Use descriptive route names and adhere to RESTful conventions.
- Keep routes concise and focused on a single responsibility.
- Prefix all routes with the
/apinamespace.
Controllers
Contains the logic for each route, including calling the appropriate service functions and returning the appropriate response status code and JSON body.
- Create a separate controller file for each route to handle the request/response logic.
- Name controller files using the PascalCase convention and append "Controller" to the file name (e.g.,
UserController.js). - Keep controllers thin by delegating complex operations to service or model files.
Services
Contains complex business logic or operations shared across multiple controllers.
- Name service files using the PascalCase convention and append "Service" to the file name (e.g.,
AuthService.js). - Avoid tightly coupling services to specific models or databases for better reusability.
- Maintain a single responsibility principle within each service.
Models
Defines Mongoose models to represent data entities and their relationships.
- Use singular, PascalCase names for model files and their associated collections (e.g.,
User.jsanduserscollection). - Include only the necessary fields, indexes, and validations in the models.
- Keep models independent of the API layer by avoiding direct references to request/response objects.
Database Access (MongoDB and Mongoose)
- Use Mongoose (https://mongoosejs.com) as the MongoDB ODM.
- Create separate model files for each entity and ensure clear separation of concerns.
- Use Mongoose schema validation to enforce data integrity.
- Handle database connections efficiently and avoid connection leaks.
- Use Mongoose query builders to create concise and readable database queries.
React Client
General TypeScript and React Best Practices
- Use TypeScript best practices to benefit from static typing and improved tooling.
- Group related files together within feature directories (e.g.,
SidePanel/Memories/). - Name components using the PascalCase convention.
- Use concise and descriptive names that accurately reflect the component's purpose.
- Split complex components into smaller, reusable ones when appropriate.
- Keep the rendering logic within components minimal.
- Extract reusable parts into separate functions or hooks.
- Apply prop type definitions using TypeScript types or interfaces.
- Use form validation where appropriate (we use React Hook Form for form validation and submission).
Localization
- All client-facing text must be localized using the
useLocalize()hook. - Only update English keys in
client/src/locales/en/translation.json(other languages are automated externally). - Use semantic localization key prefixes:
com_ui_,com_assistants_, etc. - Always provide meaningful fallback text for new localization keys.
Data Services
- Create data provider hooks in
client/src/data-provider/[Feature]/queries.ts. - Export all hooks from
client/src/data-provider/[Feature]/index.ts. - Add feature exports to main
client/src/data-provider/index.ts. - Use React Query (
@tanstack/react-query) for all API interactions. - Implement proper query invalidation on mutations.
- Add QueryKeys and MutationKeys to
packages/data-provider/src/keys.ts.
When adding shared API integration, update:
packages/data-provider/src/api-endpoints.ts(endpoints)packages/data-provider/src/data-service.ts(data service functions)packages/data-provider/src/types/queries.ts(TypeScript types)
Performance
- Prioritize memory and speed efficiency at scale.
- Implement proper cursor pagination for large datasets.
- Avoid unnecessary re-renders with proper dependency arrays.
- Leverage React Query's caching and background refetching features.
Testing and Documentation
- Write unit tests for all critical and complex functionalities using Jest.
- Write integration tests for all API endpoints using Supertest.
- Write end-to-end tests for all client-side functionalities using Playwright.
- Use descriptive test case and function names to clearly express the test's purpose.
- Run tests from their workspace directory:
cd api && npx jest <pattern>,cd packages/api && npx jest <pattern>, etc. - Cover loading, success, and error states for UI/data flows.
- Use
test/layout-test-utilsfor rendering components in frontend tests.
How is this guide?