GraphQL & tRPC
Expose federated GraphQL for UI reads and tRPC for service calls and agent tools. This guide follows version 3.1.7 (03acdf712c).
For auth headers, see Authentication. For the composed endpoint, see Backend & API Gateway.
GraphQL
Each backend service builds a federated subgraph with buildSubgraphSchema({ typeDefs, resolvers }) and serves it at /graphql. The gateway composes all subgraphs with Rover into Apollo Router.
- Keep schema and resolvers inside the owning plugin.
- Prefix operation names with the plugin or module and keep them unique repo-wide (for example
salesDeals,frontlineInbox). - Pagination uses either cursor args (
limit,cursor,cursorMode,direction,orderBy,sortMode,aggregationPipelinefromGQL_CURSOR_PARAM_DEFS) or offset args (page,perPage,sortField,sortDirectionfromGQL_OFFSET_PARAM_DEFS). Check the operation's schema; the two styles are not interchangeable. - Validate inputs at the boundary and return actionable errors.
tRPC
Plugins define routers in src/trpc/ (init-trpc.ts creates a tenant-aware context via generateModels(subdomain)). Example: backend/plugins/sales_api/src/trpc/ merges deal, POS, and document routers. Cross-service clients use httpBatchLink({ url: (await getPlugin('core')).address + '/trpc' }).
Agent-callable tools use an admit-only annotation. A procedure is invisible to agents unless it carries .meta({ agent: { description, permission } }):
findOne: t.procedure
.meta({
agent: {
description: "Find one item by query criteria",
permission: { module: "inventory", action: "showItems" },
},
})
.input(z.object({ _id: z.string() }))
.query(async ({ ctx, input }) => {
return ctx.models.Items.findOne(input).lean();
}),
The platform auto-mounts GET /agent-tools/manifest and POST /agent-tools/call on every plugin with a trpcAppRouter. Calls enforce tenant curation, user permissions, destructive-operation approval, and a response byte cap. Only annotate safe, well-scoped procedures with typed Zod inputs and a permission the plugin actually registers. See Erxes Support Agent.