# Code Standards

Boundaries, types, and UI rules


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](https://erxes.io/docs/contribute-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 `TODO`s | 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`](https://github.com/erxes/erxes/blob/f91e8c6f4714c910a32c6b6e14fe085f6435c89e/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`](https://github.com/erxes/erxes/blob/f91e8c6f4714c910a32c6b6e14fe085f6435c89e/frontend/plugins/sales_ui/eslint.config.js)) 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`](https://github.com/erxes/erxes/blob/f91e8c6f4714c910a32c6b6e14fe085f6435c89e/.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](https://erxes.io/docs/contribute-testing).

