Skip to content

Extension points


title: Extension points description: A table of all the places a module can attach to the core: attributes, interfaces, tags and YAML files; which are automatic and which are tagged by hand.

Below are all the ways to extend without changing the core. There are three mechanisms:

  1. Attribute: you add #[...] to a class or method; it is collected at build time.
  2. Interface: you implement an interface. Some interfaces are tagged automatically; for others you need to give an _instanceof tag in the module's services.yaml file.
  3. YAML file: you put it under a specific name; the core reads it.
The rule of the tag

If the "Automatic" column in the table is yes, implementing the interface is enough (autoconfigure: true). If no, add the following to your module's services.yaml file:

services:
    _instanceof:
        App\Core\Queue\AsyncJobHandlerInterface:
            tags: ['cp.async_job_handler']

Attributes

Attribute Where it goes What it does Details
#[CpAdminMenu] Controller method Studio/AACP menu item Menu and permissions
#[CpSetting] Empty carrier class (repeatable) Setting definition Settings
#[CpResource] Doctrine entity Business record: capability, audit, tenant, workflow Content model
#[CpField] Entity property Field presentation in a CpResource form Content model
#[CpEntityType] Entity Opens up to platform services such as fields and revisions Field system
#[CpFieldType] Field type class A new field type Field system
#[CpHook] Service method (repeatable) Attaching to a hook point Hooks, cron, queue
#[CpCronJob] Service method Scheduled job Hooks, cron, queue
#[CpApi] Service method REST endpoint (/api/...) API and webhooks
#[Auditable], #[Publishable], #[SoftDeletable], #[NotTrashable] Entity Audit log, publication status, soft delete, excluded from trash Content model

Interfaces

Tagged automatically

Interface What it adds
SearchProviderInterface A site search group
UpdateHookInterface A data job that runs once on a version upgrade
DoctorCheckInterface A cp:doctor check
RebuilderInterface A "recount" job that runs in batches; mandatory for a module that produces content (Rebuild)
MailTemplateProviderInterface Editable email templates
AccountProfileExtensionInterface A section on the account page
SettingVariantProviderInterface Supplying a setting's options dynamically
WidgetTypeInterface, WidgetAreaProviderInterface, WidgetAreaRendererInterface Widget type, area and rendering
StudioDashboardStatsProviderInterface Statistics for the Studio dashboard
StudioShellBrandingInterface Studio branding
SystemWidgetProviderInterface A system widget for the AACP dashboard
SeoHeadRendererInterface Full <head> SEO rendering
MigrationInterface and related Data migration (cp:migrate)
PluginInterface A sub-feature inside a module (plugin)

Tagged by hand with _instanceof

Interface Tag What it adds
AsyncJobHandlerInterface cp.async_job_handler Background job handler
AdminSearchProviderInterface cpalius.admin_search.provider A group for the panel live search
NotificationChannelInterface cpalius.notification.channel Notification channel
ConfigProviderInterface cp.config_provider A configuration document for cp:config
TokenValueProviderInterface cp.token_value_provider A [type:property] token type
TextFilterInterface cp.text_filter A text format filter
InboxPulseChannelInterface cpalius.inbox_pulse_channel Inbox badge source
CspSourceProviderInterface cp.csp_source_provider An allowed source for CSP
TrashPurgeHandlerInterface cp.trash_purge_handler Extra cleanup on permanent deletion from the trash
InboundWebhookHandlerInterface contributions.yaml → inbound_webhooks Incoming webhook endpoint
Forgetting the tag is silent

A class without a tag is simply not collected; it does not give an error. The first check for the "my provider does not work" problem is the tag. php cp-core/bin/console cp:debug and debug:container --tag=<tag> list what was collected.

YAML files

File Location Contents
capabilities.yaml Resources/config/ The module's capabilities
routes.yaml Resources/config/ Route loaders
services.yaml Resources/config/ Service definitions and tags
contributions.yaml Resources/config/ Contributions to the core (content type routes, schema.org, home page modes, Studio shortcuts…)
importmap.php Resources/config/ JavaScript entry points
*.sql Resources/migrations/ Schema migrations
messages+intl-icu.<lang>.yaml Resources/translations/ Interface translations
user.role.*.yaml cp-content/config/sync/ Role definition (the installer can add)
workflow/*.yaml cp-content/config/sync/ Workflow definition
path_pattern.*.yaml cp-content/config/sync/ Address pattern
Hooks/<hook_point>.php Module root A file hook without DI
Hooks/cron.<name>.php Module root A file cron job without DI

The core's own YAMLs (cp-core/config/): capabilities.yaml, text_formats.yaml, tokens.yaml, view_modes.yaml.

Hook points

The list of hook points offered by the core and modules and how to write them is on the Hooks, cron, queue page. A short list: entity.<type>.pre_save | post_insert | post_update | pre_delete | post_delete, entity.any.<moment>, entity.<type>.access, workflow.<name>.transitioned, theme.*, studio.body.end (end of Studio pages), page.render.content, blog.render.sidebar.

Which one to choose?

Need Way
Do something on every record An entity.any.post_insert hook
Adding HTML to a page A theme.* hook point or a widget
Notify an external system Webhook (WebhookEventEmitter)
Receive data from outside #[CpApi] or an incoming webhook
Periodic job #[CpCronJob]
Slow job AsyncJob queue
Appear in search SearchProviderInterface
A new field type #[CpFieldType]
A manageable setting #[CpSetting]
Email text MailTemplateProviderInterface
Data fix (on upgrade) UpdateHookInterface
Last updated: 13.10.2026