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.

Rule Why How 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 Radix Grep the diff for @radix-ui imports
After a create/update/delete mutation, update the UI immediately through Apollo cache, refetch, or subscribeToMore Users must never need a manual refresh Exercise 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 code Half-finished pages ship bugs to self-hosters Click 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 it Named exports survive refactors and are what host loaders import Review exports in the diff
No any, as any, or unnecessary casts in new or modified code; type all props and hook returns The ESLint rule @typescript-eslint/no-explicit-any is turned off in eslint.config.js, so type safety is a review rule, not a lint rule pnpm 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 feature Operation names are public API used by caching, logging, and codegen Search 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 change Tenant models and shared schema behavior are load-bearing Compare 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 infrastructure Plugins must remain independently buildable, testable, deployable, and removable git diff --stat shows only allowed directories
No leftover console.log/debugger, commented-out code, unused imports/variables, or untracked TODOs Dead code rots fast in a monorepo pnpm 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:

Area Rule
Project shape Route-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.
Imports Use 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.
UI Match 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.
React Functional 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.
GraphQL Search 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 Federation Check 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.

Operate on one system.

Talk to our team about consolidating your stack onto erxes. 30-minute walkthrough, custom to your org.