Backend Plugins
Build a plugin API service with tenant isolation, permissions, and gateway registration. This guide follows version 3.1.7 (03acdf712c).
Start with Create a Plugin. For service-to-service calls, see GraphQL & tRPC.
Generated layout
pnpm create-plugin --plugin-name=inventory --module-name=items creates backend/plugins/inventory_api/:
src/
main.ts startPlugin({ name, port })
connectionResolvers.ts createGenerateModels per subdomain
apollo/ typeDefs + resolvers
trpc/ init-trpc.ts, routers, trpcClients
modules/items/ @types, db/{models,definitions}, graphql/{schemas,resolvers}
meta/ permissions, automations, segments, notifications
The generator template uses API port 33010; every generated plugin reuses it, so assign an unused port (Sales uses 3305). The Nx project is named inventory_api.
Runtime contract
backend/erxes-api-shared/src/utils/start-plugin.ts defines startPlugin({ name, port, graphql, apolloServerContext, trpcAppRouter, expressRouter, hasSubscriptions, meta }):
- Builds tenant models from the request subdomain for GraphQL and tRPC.
- Serves
/health,/graphql(federated subgraph),/trpc, and/agent-tools/*when a tRPC router exists. - Keeps connections alive (
keepAliveTimeout 120000,headersTimeout 121000). - Dispatches
meta(automations, segments, documents, notifications, import/export, payments, before/after hooks) and callsjoinErxesGateway.
Enable locally with the base name (no _api suffix):
ENABLED_PLUGINS=inventory
Implementation rules
- Prefix every GraphQL operation with the plugin or module name and keep names unique repo-wide.
- Check authentication and permissions before sensitive reads and mutations.
- Define Mongoose schemas explicitly and follow the owning module's
new Schema(...)plusschemaWrapperpattern. - Keep resolvers thin; put business rules in services or models.
- Own migrations inside the plugin; never read or mutate another plugin's collections.
- Maintain the plugin's
AGENTS.mdbounded recent-changes section when behavior changes.