Code Standards

The conventions reviewers apply to every change in erxes/erxes, drawn from the repository's rule files and the lint and formatter configuration. For the fork-and-PR workflow, see Contribute to codebase.

How the rules are organized

The root AGENTS.md is the repository-wide rule file, and each directory can carry its own. Nested files may add constraints or pick among approved patterns, but they cannot weaken the non-negotiable rules, the plugin scope boundary, security requirements, or tenant isolation. When several guides apply to a file, all of them apply.

When rules conflict with preference, the working principles set a priority order: existing repository patterns first, then local plugin consistency, minimal changes, clear behavior and maintainability, reusability, performance, and personal preference last. If you are unsure whether a change violates a rule, ask rather than guess.

Non-negotiable rules

These must hold in any new or modified code.

RuleWhyHow to check
Use erxes-ui and ui-modules components; never import @radix-ui/* directly or hand-roll primitives; prefer composed APIs (Form.Field, RecordTable.Provider, Sheet.Content)One design system; primitives already wrap RadixGrep the diff for @radix-ui imports
After a create/update/delete mutation, update the UI immediately through Apollo cache, refetch, or subscribeToMoreUsers must never need a manual refreshExercise the mutation in the browser without reloading
Completeness: every button has a handler, every form validates, every list has loading and empty states, every mutation reports success/error; no placeholder codeHalf-finished pages ship bugs to self-hostersClick through the whole flow in each state
Named exports in application code; a default export only where an external tool (Nx, Rspack, Module Federation loader) requires itNamed exports survive refactors and are what host loaders importReview exports in the diff
No any, as any, or unnecessary casts in new or modified code; type all props and hook returnsThe ESLint rule @typescript-eslint/no-explicit-any is turned off in eslint.config.js, so type safety is a review rule, not a lint rulepnpm exec tsc --noEmit on the project; read the diff
GraphQL operations are named, unique repository-wide, and prefixed with the plugin or module (cmsPageList, salesDealCreate), colocated with the featureOperation names are public API used by caching, logging, and codegenSearch the repo for the operation name before adding it
Backend schemas follow their module's established new Schema(...) and schemaWrapper pattern; never change a backend API from a frontend-only changeTenant models and shared schema behavior are load-bearingCompare with a sibling module in the same plugin
Plugin isolation: a plugin change stays inside that plugin; no cross-plugin imports; no edits to core, shared libraries, or root infrastructurePlugins must remain independently buildable, testable, deployable, and removablegit diff --stat shows only allowed directories
No leftover console.log/debugger, commented-out code, unused imports/variables, or untracked TODOsDead code rots fast in a monorepopnpm nx lint <project> plus a final diff read

Forbidden changes

  • Introducing a new UI system or replacing existing patterns with personal preferences.
  • Casually renaming public APIs, GraphQL operation names, routes, or Module Federation exposes.
  • Moving Module Federation exposes without updating every host reference.
  • Large refactors without an explicit request.
  • Changing core, shared libraries, root infrastructure, or another plugin as an implicit part of plugin work; that is a separate, explicitly scoped task.
  • New dependencies the change does not require, or ones an existing platform package already covers.

Frontend plugin rules

For work under frontend/plugins/<name>_ui:

AreaRule
Project shapeRoute-level pages live in src/pages or src/modules following the touched plugin's pattern; feature internals stay near the feature in src/modules; src/widgets is only for Module Federation widget exports. Shared primitives go to frontend/libs/erxes-ui, shared product modules to frontend/libs/ui-modules; never to core-ui.
ImportsUse local imports or the plugin aliases (~/* for src, @/* for src/modules); no cross-plugin imports; move UI to a shared lib only for clear cross-plugin reuse.
UIMatch nearby layout, spacing, loading/empty states, filters, and action placement; reuse erxes-ui/ui-modules tables, forms, sheets, dialogs, and filters; icons come from @tabler/icons-react; preserve responsive behavior; no inline styles unless neighbors use them.
ReactFunctional components and existing hooks; Jotai only for shared sibling/page state or existing atom-backed patterns; keep hooks, constants, types, and GraphQL documents next to the feature.
GraphQLSearch existing documents first; prefix names with the plugin/module and keep them unique; reuse the plugin's fragments and Apollo hooks; do not change backend APIs from frontend work unless explicitly requested.
Module FederationCheck module-federation.config.ts before exposing modules; the named-export rule applies to every expose; never satisfy a host loader with a default export; fix the expose or the loader instead.

Lint and formatting

  • ESLint uses a flat config rooted at eslint.config.js: the Nx flat/base, flat/typescript, and flat/javascript presets, plus @nx/enforce-module-boundaries set to error. Its options are enforceBuildableLibDependency, allowCircularSelfDependency, and a depConstraints entry allowing any source tag to depend on any tagged library. Frontend plugin configs (for example sales_ui) spread the root config and add nx.configs['flat/react'].
  • @typescript-eslint/no-explicit-any is off; the ban on any comes from code review, not from lint. Do not read a green lint run as proof of type safety.
  • Prettier (.prettierrc) enforces single quotes, trailing commas everywhere, and endOfLine: "auto". The repository conventions add two-space indentation, kebab-case config files, PascalCase components, camelCase functions, and UPPER_SNAKE_CASE global constants; match the touched project where it differs.
  • Run pnpm nx lint <project> and pnpm nx build <project> before pushing; both must pass along with pnpm nx test <project> when the project defines tests. See Testing.
Was this helpful?