# Plugin Metadata & Extensions

Permissions and cross-module hooks


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](https://erxes.io/docs/plugin-backend) for the `startPlugin` options.

## Extension-point keys

The `IMeta` keys from [start-plugin.ts](https://github.com/erxes/erxes/blob/f91e8c6f4714c910a32c6b6e14fe085f6435c89e/backend/erxes-api-shared/src/utils/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`](https://github.com/erxes/erxes/blob/f91e8c6f4714c910a32c6b6e14fe085f6435c89e/backend/plugins/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](https://github.com/erxes/erxes/blob/f91e8c6f4714c910a32c6b6e14fe085f6435c89e/backend/erxes-api-shared/src/core-types/modules/permissions/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):

```ts
// 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](https://github.com/erxes/erxes/blob/f91e8c6f4714c910a32c6b6e14fe085f6435c89e/backend/erxes-api-shared/src/utils/apollo/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.

