# Create a Plugin

Learn how to create an erxes plugin from scratch


Create paired backend and frontend projects in the erxes monorepo, enable them locally, and validate the first working feature. To develop a plugin in its own repository instead, see [Standalone Plugins](https://erxes.io/docs/create-plugin-standalone).

## Prerequisites

- Complete [Local Setup](https://erxes.io/docs/local-setup): pnpm ≥ 8, MongoDB, Redis, `pnpm install`, and `pnpm nx build erxes-api-shared`.
- A unique plugin name, its first business module, and unused API and UI ports (step 2 lists the ports already taken).
- Decide whether the plugin needs the backend, the frontend, or both. The generator always creates both; for a one-sided plugin you remove the unused project right away.

## Generate and connect a plugin

### 1. Run the generator

From the repository root, run the interactive generator:

```bash
pnpm create-plugin
```

Or pass both names for a repeatable run:

```bash
pnpm create-plugin --plugin-name=inventory --module-name=items
# short flags: -p inventory -m items
```

Names must match `^[a-zA-Z][a-zA-Z0-9]*$`: start with a letter, letters and digits only. The generator converts camelCase to kebab-case for directory names (`myPlugin` becomes `my-plugin`). It creates two Nx projects:

- `backend/plugins/inventory_api` (Nx project `inventory_api`)
- `frontend/plugins/inventory_ui` (Nx project `inventory_ui`)

### 2. Review the generated files and ports

The generator ([create-plugin.js](https://github.com/erxes/erxes/blob/f91e8c6f4714c910a32c6b6e14fe085f6435c89e/scripts/create-plugin.js), [create-backend-plugin.js](https://github.com/erxes/erxes/blob/f91e8c6f4714c910a32c6b6e14fe085f6435c89e/scripts/create-backend-plugin.js)) writes these files:

```text
backend/plugins/inventory_api/
  package.json              "dev": "nodemon src/main.ts"; dep: erxes-api-shared
  project.json              targets: build, build:packageJson, start, serve, docker-build
  tsconfig.json, tsconfig.build.json
  .gitignore
  src/main.ts               startPlugin({ name: 'inventory', port: 33010, ... })
  src/connectionResolvers.ts  createGenerateModels<IModels>(loadClasses)
  src/apollo/typeDefs.ts    apolloCommonTypes + extend type Query/Mutation
  src/apollo/schema/schema.ts
  src/apollo/resolvers/{index,mutations,queries,resolvers}.ts
  src/trpc/init-trpc.ts     appRouter with a hello procedure
  src/trpc/trpcClients.ts   coreTRPCClient via getPlugin('core')
  src/modules/items/        @types, db/{definitions,models},
                            graphql/{schemas,resolvers/{queries,mutations,customResolvers}}

frontend/plugins/inventory_ui/
  project.json              serve port 3005; build/serve/serve-static targets
  module-federation.config.ts
  rspack.config.ts, rspack.config.prod.ts
  tsconfig.json, tsconfig.app.json, tsconfig.spec.json
  jest.config.ts, eslint.config.js
  src/index.html, src/main.ts, src/bootstrap.tsx
  src/config.tsx            IUIConfig with navigationGroup + one module
  src/assets/{example-icon.svg, example-image.svg, README.md}
  src/modules/{InventoryMain,InventoryNavigation,InventorySettingsNavigation,InventorySettings}.tsx
  src/pages/items/IndexPage.tsx
  src/widgets/Widgets.tsx
```

Both default ports collide with existing plugins: API `33010` belongs to an Enterprise Edition plugin and UI `3005` is `sales_ui`'s port. Pick unused values. Ports already in use:

- API: core `3300`; plugins `3303`–`3305`, `3307`–`3313`, `33010`; gateway `4000`.
- UI: `3002`–`3011`; core-ui host `3001`.

`process.env.PORT` overrides the API port at runtime, so keep the registered port and any hard-coded references consistent.

### 3. Enable the plugin locally

Add the base name (no `_api` or `_ui` suffix) to `.env`:

```dotenv
ENABLED_PLUGINS=sales,inventory
```

Comma-separated, no spaces. `ENABLED_PLUGINS_ONLY_API` exists for API-only plugins and takes the same base names. Restart the dev processes after changing the list.

### 4. Start the dev processes

```bash
# Terminal 1: core-api, gateway, and every *_api in ENABLED_PLUGINS
pnpm dev:apis

# Terminal 2: core-ui host plus every *_ui remote
pnpm dev:uis
```

Open `http://localhost:3001`: the core-ui host loads your remote, so the remote's own port is not the app entry point. For focused work, `pnpm nx serve inventory_api` or `pnpm nx serve inventory_ui` runs one project, but core-api and the gateway must still be running.

### 5. Implement the first feature inside the plugin boundary

Stay inside the two plugin directories. Use `erxes-api-shared`, `erxes-ui`, and `ui-modules` through their public exports only: no cross-plugin imports, no edits to core or shared libraries. See [Backend Plugins](https://erxes.io/docs/plugin-backend) and [Frontend Plugins](https://erxes.io/docs/plugin-frontend) for the service and host APIs, and [Plugin Metadata & Extensions](https://erxes.io/docs/plugin-metadata-extensions) for permissions and hooks.

### 6. Check it works

```bash
pnpm nx lint inventory_api && pnpm nx build inventory_api
pnpm nx lint inventory_ui && pnpm nx build inventory_ui
```

List a project's targets with `pnpm nx show project inventory_api`. Then check the plugin end to end:

- The API registers with the gateway and composes into the supergraph.
- The host renders your navigation and route.
- A permitted user can create, read, update, and delete records with immediate UI feedback.

## The scaffold is not a deliverable

Generated code is a starting point, not a working plugin. The generated `IndexPage`, for example, includes placeholder text ("Add your content here") and a `More` button with no handler. Before treating the plugin as usable:

- Replace sample pages, widgets, models, schemas, resolvers, and placeholder text with complete behavior, or remove them.
- Replace the generated ports (`33010`, `3005`) with unused plugin-specific ports.
- Make plugin name, module name, routes, navigation paths, and Module Federation exposes consistent.
- Make sure each `config.tsx` path maps to a real route and each expose maps to a real named export; the host loads `./<name>` and `./<name>Settings` by convention.
- Remove unused generated files and imports.
- Fix generated types, validation, loading states, empty states, error states, and mutation feedback.
- Use named exports; default exports are allowed only where a tool requires them (the generated `module-federation.config.ts`, `rspack.config.ts`, and `jest.config.ts` are such cases).
- Add only the plugin's own dependencies; do not add a dependency a platform package already covers.
- Create or update each plugin's `AGENTS.md` whenever source, config, schema, routes, or user-visible behavior changes; the repo requires one at the root of each plugin project (`inventory_api`, `inventory_ui`). Its sections, in order: Identity, Scope, Current Capabilities, Architecture, Contracts, Data and State, Local Invariants, Validation, Recent Changes.

## Troubleshooting

- `EADDRINUSE` on start: the generated `33010`/`3005` ports are still set and collide with existing plugins.
- Nx cannot find the project: project names are `inventory_api`/`inventory_ui`, not the directory path.
- Plugin API never joins the gateway: check the name in `ENABLED_PLUGINS`, startup errors, Redis reachability, and the registered address (`erxes-service-<name>` key).
- Navigation renders but the page fails: compare the `config.tsx` path with your routes, check expose names and named exports, and inspect the remote's network request.
- `erxes-api-shared` import fails: run `pnpm nx build erxes-api-shared`, then restart the API.

## Next steps

- [Backend Plugins](https://erxes.io/docs/plugin-backend): startPlugin contract, tenant models, meta dispatch, endpoints
- [Frontend Plugins](https://erxes.io/docs/plugin-frontend): IUIConfig, federation exposes, host loading
- [GraphQL & tRPC](https://erxes.io/docs/plugin-graphql-trpc): Subgraph conventions, pagination, service calls
- [Plugin Metadata & Extensions](https://erxes.io/docs/plugin-metadata-extensions): Permissions, automations, agent tools

