Plugin Metadata & Extensions
Register permissions, automations, segments, and other cross-module hooks so core and the gateway can discover plugin behavior.
The meta object passed to startPlugin does two things: joinErxesGateway serializes it into the Redis key erxesservice:config:<name> (read by core-api, the gateway, and other services through getPlugin), and startPlugin mounts or starts the subsystems that need live producers. See Backend Plugins for the startPlugin options.
Extension-point keys
The IMeta keys from start-plugin.ts, with who consumes them:
meta key | Config shape | Consumer |
|---|---|---|
permissions | IPermissionConfig | core-api permission resolvers, group-action map, OAuth scopes (via Redis config) |
automations | AutomationConfigs | startAutomations mounts tRPC producers; automations-service calls them |
segments | SegmentConfigs | initSegmentProducers; core segments engine and logs service call them |
documents | { types: { label, contentType }[] } | core documents (serialized) |
notifications | { plugin, modules[{ name, icon, events[] }] } | initializePluginConfig; core notifications (serialized) |
importExport | ImportExportConfigs | startImportExportWorker: BullMQ workers on <plugin>-import-processor / -export-processor |
payments | { <jobName>: (ctx, data) => … } | startPayments: BullMQ worker on <plugin>-payments |
beforeResolvers | BeforeResolversConfig | startBeforeResolvers; every GraphQL resolver runs through it |
afterProcess | AfterProcessConfigs | startAfterProcess; fires after mutations/documents/API calls/auth |
references | TRecordReferencesConfig | initRecordReferences; core record-reference lookups |
tags | { types: [{ type, description }] } | core tags (serialized) |
properties | IPropertyMeta | core properties: systemFields per content type (serialized) |
relations | { subscribedTypes: string[] } | core relations activity log (serialized) |
logs | LogsConfigs ({ contentTypes: [{ moduleName, collectionName }] }) | logs service (serialized) |
Sales registers nearly all of these in sales_api/src/main.ts and src/meta/; use it as the structural reference without importing its source.
Permissions: src/meta/permissions.ts
Declare an IPermissionConfig (permission.ts): { plugin, modules[], defaultGroups[] }. Each module has name, scopes (own/group/all), ownerFields, scopeField, and actions ({ title, name, description, always?, disabled?, oauthScope(s)?, type? }). Resolvers enforce actions via context.checkPermission('<action>'); OAuth clients map to the same action names.
defaultGroups provides ready-made groups keyed <plugin>:<group> (the colon marks them as default-group ids):
// sales_api/src/meta/permissions.ts (excerpt)
export const permissions: IPermissionConfig = {
plugin: 'sales',
modules: [
{
name: 'deal',
description: 'Deals management',
ownerFields: ['userId', 'assignedUserIds'],
scopes: [
{ name: 'own', description: 'Deals created by or assigned to user' },
{ name: 'all', description: 'All deals' },
],
actions: [
{ title: 'View deals', name: 'showDeals', description: 'View deals', always: true },
{ title: 'Add deals', name: 'dealsAdd', description: 'Create deals' },
],
},
],
defaultGroups: [
{
id: 'sales:admin',
name: 'Sales Admin',
permissions: [{ plugin: 'sales', module: 'deal', actions: ['showDeals', 'dealsAdd'], scope: 'all' }],
},
],
};
beforeResolvers and afterProcess
beforeResolvers (beforeResolvers.ts) intercepts GraphQL resolvers by name before they run: { resolvers: { <rootField>: [<resolverName>…] }, handler, check?, blocker? }. The handler returns { status: 'ok' } (optionally rewriting args), { status: 'blocked', code, message }, or { status: 'resolved', data }. Sales uses it to rewrite productIds on productsRemove/productsMerge to only the ids not referenced by deals.
afterProcess fires after work completes: { rules: [{ type: 'afterMutation', mutationNames }], afterMutation, afterAuth, afterApiRequest, afterDocumentCreated, afterDocumentUpdated }. Sales uses afterMutation to sync POS product groups and publish subscription events.
Migrations and boundaries
Keep migrations inside the plugin (for example backend/plugins/operation_api/src/migrations/). Never read or mutate another plugin's collections; communicate through published GraphQL, tRPC, HTTP, event, or federation interfaces.