Skip to main content
LibreChat is joining ClickHouse to power the open-source Agentic Data Stack 🎉 Learn more
LibreChat

Code Standards and Conventions

Coding standards, workspace boundaries, and conventions for contributing to LibreChat.

Archived documentation

You are reading the frozen documentation for v0.8.7, which is no longer maintained. Go to the latest docs.

Workspace Boundaries

LibreChat is a monorepo. All new code should target the correct workspace:

WorkspaceLanguageSidePurpose
/apiJS (legacy)BackendExpress server — minimize changes here
/packages/apiTypeScriptBackendNew backend code lives here (TS only, consumed by /api)
/packages/data-schemasTypeScriptBackendDatabase models/schemas and database-specific shared logic
/packages/data-providerTypeScriptSharedAPI types, endpoints, data-service — used by frontend and backend
/clientTypeScript/ReactFrontendFrontend SPA
/packages/clientTypeScriptFrontendShared frontend utilities
  • All new backend code must be TypeScript in /packages/api.
  • Keep /api changes 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.
  • Rebuild shared data-provider code after API/type changes: npm run build:data-provider.

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 .eslintrc and .prettierrc files for consistent code formatting.
  • Fix all formatting lint errors using auto-fix when available. All TypeScript/ESLint warnings and errors must be resolved.

Naming and File Organization

  • Use single-word file names whenever possible, such as permissions.ts, capabilities.ts, or service.ts.
  • When multiple words are needed, prefer a single-word directory that gives the file context, such as admin/capabilities.ts instead of adminCapabilities.ts.
  • Let the directory provide context. Prefer app/service.ts over app/appConfigService.ts.

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/reduce over 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). Prefer parameterized helpers, constants, shared validators, centralized error handling, and shared types over near-duplicate implementations.

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/Set for lookups instead of Array.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 — avoid unknown, Record<string, unknown>, and as unknown as T assertions. A Record<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):

  1. Package imports — sorted from shortest to longest line length (react is always the first import).
  2. import type imports — sorted from longest to shortest (package types first, then local types; length sorting resets between sub-groups).
  3. 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 inline type keyword 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...of for simple array iteration.
  • for...in only 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 utils directory to log important events and errors.
  • Do JWT-based, stateless authentication using the requireJWTAuth middleware.

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 /api namespace.

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.js and users collection).
  • 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-utils for rendering components in frontend tests.
  • Prefer real logic over mocks. Mock only what cannot be controlled locally, such as external HTTP APIs, rate-limited services, and non-deterministic system calls.
  • Use spies when you need to assert calls without replacing the underlying implementation.
  • Use mongodb-memory-server for MongoDB-backed tests so queries and schema validation exercise real database behavior.