Skip to content

[6.x] Registries (widget types, field types, ...)#19270

Open
riasvdv wants to merge 19 commits into
6.xfrom
feature/registries
Open

[6.x] Registries (widget types, field types, ...)#19270
riasvdv wants to merge 19 commits into
6.xfrom
feature/registries

Conversation

@riasvdv

@riasvdv riasvdv commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Description

Instead of relying on events for extensibility, this moves registration and the source of truth for all extension points into registries


Extension Registries

Craft’s Laravel registration events have been replaced with singleton registries. Plugins that listen for any of the removed *Resolving events must register their extensions during their boot() method instead.

Legacy Yii registration events remain supported through craftcms/yii2-adapter.

Type Registries

Most extension types use a shared registry API:

public function boot(FieldTypeRegistry $registry): void
{
    $registry->register(MyField::class);
}

Registries also support replacing and removing types:

$registry->replace(CoreField::class, MyField::class);
$registry->remove(MyField::class);

Registries validate that registered classes implement the appropriate contract. Types with domain identifiers, such as utility IDs, link type IDs, GraphQL directive names, and element reference handles, must have unique identifiers.

Some core types are protected and cannot be removed or replaced:

  • The url link type
  • The dependencies and resources template cache collectors

Collections returned by types() are snapshots. Modify the registry through register(), replace(), or remove() rather than modifying the returned collection.

GraphQL Argument Handlers

CraftCms\Cms\Gql\Events\GqlArgumentHandlersResolving has been replaced by CraftCms\Cms\Gql\GqlArgumentHandlerRegistry.

use CraftCms\Cms\Gql\GqlArgumentHandlerRegistry;

public function boot(GqlArgumentHandlerRegistry $registry): void
{
    $registry->register('customArgument', CustomArgumentHandler::class);
}

Handlers may be registered as class names or container-invoked factories:

$registry->register(
    'customArgument',
    fn (SomeDependency $dependency) => new CustomArgumentHandler($dependency),
);

Handler classes must implement CraftCms\Cms\Gql\Contracts\ArgumentHandlerInterface. Registering handler instances is no longer supported; use a class name or factory instead.

Handlers can be removed by argument name:

$registry->remove('customArgument');

System Messages

CraftCms\Cms\SystemMessage\Events\SystemMessagesResolving has been replaced by CraftCms\Cms\SystemMessage\SystemMessageRegistry.

use CraftCms\Cms\SystemMessage\Models\SystemMessage;
use CraftCms\Cms\SystemMessage\SystemMessageRegistry;

public function boot(SystemMessageRegistry $registry): void
{
    $registry->register(
        'account_approved',
        fn () => new SystemMessage([
            'key' => 'account_approved',
            'heading' => 'Account approved',
            'subject' => 'Your account has been approved',
            'body' => 'Your account is ready to use.',
        ]),
    );
}

System messages are registered using lazy, container-invoked factories. The factory must return a SystemMessage whose key matches the registered key.

Messages can be removed by key:

$registry->remove('account_approved');

Factories are resolved in the active site locale.

User Permissions

CraftCms\Cms\User\Events\UserPermissionsResolving has been replaced by CraftCms\Cms\User\PermissionGroupRegistry.

Permission groups are registered through providers that receive and return a collection:

use CraftCms\Cms\User\Data\Permission;
use CraftCms\Cms\User\Data\PermissionGroup;
use CraftCms\Cms\User\PermissionGroupRegistry;
use Illuminate\Support\Collection;

public function boot(PermissionGroupRegistry $registry): void
{
    $registry->register(
        'plugin:my-plugin',
        fn (Collection $groups) => $groups->push(
            new PermissionGroup(
                handle: 'plugin:my-plugin',
                heading: 'My Plugin',
                permissions: collect([
                    new Permission(
                        key: 'manageMyPlugin',
                        label: 'Manage My Plugin',
                    ),
                ]),
            ),
        ),
    );
}

Provider handles must be unique. Each resolved PermissionGroup must also have a unique, stable handle; handles are no longer inferred from group headings.

Providers run whenever Craft rebuilds its permission catalog, so they may dynamically append, transform, or replace permission groups.

Template Roots

The following events have been replaced by CraftCms\Cms\View\TemplateRootRegistry:

  • CraftCms\Cms\View\Events\CpTemplateRootsResolving
  • CraftCms\Cms\View\Events\SiteTemplateRootsResolving

Register control panel template roots with TemplateMode::Cp:

use CraftCms\Cms\View\TemplateMode;
use CraftCms\Cms\View\TemplateRootRegistry;

public function boot(TemplateRootRegistry $registry): void
{
    $registry->register(
        TemplateMode::Cp,
        'my-plugin',
        __DIR__ . '/../templates',
    );
}

Register site template roots with TemplateMode::Site:

$registry->register(
    TemplateMode::Site,
    'my-plugin',
    __DIR__ . '/../templates',
);

Multiple paths may be registered under the same namespace:

$registry->register(
    TemplateMode::Cp,
    'my-plugin',
    $primaryPath,
    $fallbackPath,
);

Namespaces can be removed independently for each template mode:

$registry->remove(TemplateMode::Cp, 'my-plugin');

Registration Timing

Registration should normally happen during a plugin or service provider’s boot() method. Unlike resolving events, registries are not redispatched whenever a catalog is read.

The following registrations remain lazy:

  • GraphQL argument-handler factories resolve for each argument manager.
  • System-message factories resolve when messages are requested.
  • Permission providers run when the permission catalog is rebuilt.
  • Utility availability is evaluated when utility types are requested.

Register GraphQL directives before schemas are built and template cache collectors before template-cache collection begins.

Legacy Plugins

Plugins using the deprecated Yii registration events do not need to migrate immediately. The Yii2 adapter continues to bridge those events into the new registries after legacy plugins initialize and modern plugins boot.

New Craft 6 plugin code should use the registries directly.

@github-actions

github-actions Bot commented Jul 20, 2026

Copy link
Copy Markdown

📚 Storybook previews

@craftcms/uiopen Storybook

No changed components detected in this Storybook.

resources/jsopen Storybook

No changed components detected in this Storybook.

@riasvdv riasvdv changed the title [6.x] Registries (widgets, entry types, field types, ...) [6.x] Registries (widget types, field types, ...) Jul 22, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR migrates multiple Craft CMS extension points from “resolve-time events” to registry-based APIs, making registries the primary source of truth while preserving Yii2-adapter compatibility via explicit legacy-finalization steps.

Changes:

  • Introduces a shared TypeRegistry foundation and new registries for key extension points (elements, fields, widgets, utilities, filesystem types, image transformers, template roots/cache collectors, GraphQL directives/argument handlers, permissions, system messages).
  • Refactors core services and plugin concerns to register/resolve via registries instead of dispatching “*Resolving” events.
  • Updates Yii2-adapter bridges and adds/updates tests to validate modern+legacy interoperability and registry semantics.

Reviewed changes

Copilot reviewed 122 out of 122 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
CHANGELOG-WIP.md Updates changelog entries to reference registry APIs instead of resolving events.
CHANGELOG.md Updates changelog entries to reference registry APIs instead of resolving events.
src/Component/TypeRegistry.php Adds a reusable base registry with identity/protection/validation semantics.
src/Dashboard/Dashboard.php Switches widget type resolution to WidgetTypeRegistry.
src/Dashboard/Events/WidgetTypesResolving.php Removes the widget-type resolving event (replaced by registry).
src/Dashboard/WidgetTypeRegistry.php Adds widget-type registry backed by TypeRegistry.
src/Element/ElementTypeRegistry.php Adds element-type registry backed by TypeRegistry.
src/Element/Elements.php Switches element type resolution from events to ElementTypeRegistry.
src/Element/Events/ElementTypesResolving.php Removes the element-types resolving event (replaced by registry).
src/Field/Events/FieldTypesResolving.php Removes the field-types resolving event (replaced by registry).
src/Field/Events/LinkTypesResolving.php Removes the link-types resolving event (replaced by registry).
src/Field/FieldTypeRegistry.php Adds field-type registry backed by TypeRegistry.
src/Field/Fields.php Switches field type resolution from events to FieldTypeRegistry.
src/Field/Link.php Switches link type resolution to LinkTypeRegistry.
src/Field/LinkTypeRegistry.php Adds link-type registry backed by TypeRegistry with protected URL identity.
src/Filesystem/Events/FilesystemTypesResolving.php Removes the filesystem-types resolving event (replaced by registry).
src/Filesystem/FilesystemTypeRegistry.php Adds filesystem-type registry backed by TypeRegistry.
src/Filesystem/Filesystems.php Switches filesystem type resolution from events to FilesystemTypeRegistry.
src/Gql/ArgumentManager.php Switches argument handlers from event resolution to GqlArgumentHandlerRegistry, adds factory support and validation.
src/Gql/Events/GqlArgumentHandlersResolving.php Removes the argument-handlers resolving event (replaced by registry).
src/Gql/Events/GqlDirectivesResolving.php Removes the directives resolving event (replaced by registry).
src/Gql/Gql.php Switches directive loading from events to GqlDirectiveRegistry.
src/Gql/GqlArgumentHandlerRegistry.php Adds argument-handler registry supporting class handlers and factories.
src/Gql/GqlDirectiveRegistry.php Adds directive registry with schema-scope filtering and reserved identity protection.
src/Http/Controllers/Gql/SchemasController.php Updates permission-group construction to include stable handles.
src/Image/Events/ImageTransformersResolving.php Removes the image-transformers resolving event (replaced by registry).
src/Image/ImageTransformerRegistry.php Adds image-transformer registry backed by TypeRegistry.
src/Image/ImageTransforms.php Switches transformer type resolution from events to ImageTransformerRegistry.
src/Plugin/Concerns/HasElementTypes.php Registers plugin element types via ElementTypeRegistry.
src/Plugin/Concerns/HasFieldtypes.php Registers plugin field types via FieldTypeRegistry.
src/Plugin/Concerns/HasPermissions.php Registers plugin permission groups via PermissionGroupRegistry instead of resolving event listeners.
src/Plugin/Concerns/HasUtilities.php Registers plugin utilities via UtilityTypeRegistry.
src/Plugin/Concerns/HasViews.php Registers plugin CP template roots via TemplateRootRegistry.
src/Plugin/Concerns/HasWidgets.php Registers plugin widgets via WidgetTypeRegistry.
src/Support/Facades/HtmlSanitizers.php Updates @see reference to local class name.
src/Support/Facades/Template.php Updates @see reference to local class name.
src/SystemMessage/Events/SystemMessagesResolving.php Removes system-messages resolving event (replaced by registry).
src/SystemMessage/SystemMessageRegistry.php Adds system-message registry with container-invoked factories and validation.
src/SystemMessage/SystemMessages.php Switches default message resolution to SystemMessageRegistry; scopes caching per locale scope.
src/User/Data/PermissionGroup.php Makes permission-group handle an explicit constructor field (stable identity).
src/User/Events/UserPermissionsResolving.php Removes permissions resolving event (replaced by registry).
src/User/PermissionGroupRegistry.php Adds registry for permission-group providers with validation and snapshot behavior.
src/User/UserPermissions.php Applies registry providers to core permission groups and assigns stable handles to core groups.
src/Utility/Events/UtilitiesResolving.php Removes utilities resolving event (replaced by registry).
src/Utility/Utilities.php Switches utility type resolution to UtilityTypeRegistry.
src/Utility/UtilityTypeRegistry.php Adds utility-type registry with config/edition/volume-based filtering and reserved identities.
src/View/Events/CpTemplateRootsResolving.php Removes CP template-roots resolving event (replaced by registry).
src/View/Events/SiteTemplateRootsResolving.php Removes site template-roots resolving event (replaced by registry).
src/View/Events/TemplateCacheCollectorsResolving.php Removes template-cache collector resolving event (replaced by registry).
src/View/TemplateCacheCollectorRegistry.php Adds template-cache collector registry (core collectors + extensibility).
src/View/TemplateCaches.php Switches collector discovery from events to TemplateCacheCollectorRegistry.
src/View/TemplateMode.php Switches templateRoots() to read from TemplateRootRegistry.
src/View/TemplateRootRegistry.php Adds registry for template roots keyed by mode+namespace with ordering and snapshots.
src/View/ViewServiceProvider.php Adjusts template-root integration timing to bootstrap phase (BootProviders).
tests/Feature/Dashboard/DashboardTest.php Updates dashboard tests to register widgets via registry.
tests/Feature/Element/ElementTypesTest.php Updates element type tests to use registry and handle dynamic changes.
tests/Feature/Field/FieldsTest.php Updates field type tests to use registry (including provider-based registration).
tests/Feature/Filesystem/FilesystemsTest.php Updates filesystem type tests to use registry.
tests/Feature/Gql/ArgumentManagerTest.php Updates argument manager tests for registry handlers, factories, and binding behavior.
tests/Feature/Gql/GqlTest.php Updates directive tests to use GqlDirectiveRegistry and verify schema caching behavior.
tests/Feature/Http/Controllers/Elements/DeleteElementsControllerTest.php Updates Elements service test doubles for new constructor dependency (ElementTypeRegistry).
tests/Feature/Http/Controllers/Elements/SaveElementControllerTest.php Updates Elements service test doubles for new constructor dependency (ElementTypeRegistry).
tests/Feature/Http/Controllers/Elements/SaveElementIndexElementsControllerTest.php Updates Elements service test doubles for new constructor dependency (ElementTypeRegistry).
tests/Feature/Http/Controllers/Elements/SearchControllerTest.php Updates Elements service test doubles for new constructor dependency (ElementTypeRegistry).
tests/Feature/Http/Controllers/MatrixControllerTest.php Updates Elements service test double for new constructor dependency (ElementTypeRegistry).
tests/Feature/Http/Controllers/SiteRouteControllerTest.php Updates site template root setup to use TemplateRootRegistry.
tests/Feature/Image/ImageTransformsTest.php Updates transformer tests to use ImageTransformerRegistry.
tests/Feature/SystemMessage/SystemMessagesTest.php Updates system message tests to use SystemMessageRegistry and verify locale scoping.
tests/Feature/User/UserPermissionsTest.php Adds coverage for permission registry register/remove and stable handles.
tests/Feature/Utility/UtilitiesTest.php Updates utility tests to use UtilityTypeRegistry and validate filtering behavior.
tests/Unit/Field/LinkTest.php Adds unit coverage for link types reading from LinkTypeRegistry.
tests/Unit/Gql/GqlArgumentHandlerRegistryTest.php Adds unit coverage for handler registry ordering, updates, and validation.
tests/Unit/Plugin/Concerns/HasElementTypesTest.php Updates plugin element type boot behavior tests to use registry-backed services.
tests/Unit/Plugin/Concerns/HasFieldtypesTest.php Updates plugin field type boot behavior tests to use registry-backed services.
tests/Unit/Plugin/Concerns/HasPermissionsTest.php Updates plugin permissions boot behavior tests to use PermissionGroupRegistry.
tests/Unit/Plugin/Concerns/HasUtilitiesTest.php Updates plugin utilities boot behavior tests to use registry-backed services.
tests/Unit/Plugin/Concerns/HasViewsTest.php Updates plugin view root tests to rely on TemplateMode::templateRoots()/registry.
tests/Unit/Plugin/Concerns/HasWidgetsTest.php Updates plugin widget tests to use registry-backed dashboard service.
tests/Unit/SystemMessage/SystemMessageRegistryTest.php Adds unit coverage for SystemMessageRegistry behavior and validation.
tests/Unit/Twig/TemplateResolverTest.php Updates template resolver tests to register CP roots via TemplateRootRegistry.
tests/Unit/User/PermissionGroupRegistryTest.php Adds unit coverage for permission group provider registry semantics and snapshots.
tests/Unit/View/TemplateCachesTest.php Updates template cache tests to register collectors via registry.
tests/Unit/View/TemplateModeTest.php Removes event-dispatch assertions now that template roots are registry-backed.
tests/Unit/View/TemplateRootRegistryTest.php Adds unit coverage for TemplateRootRegistry behavior across modes and snapshots.
tests/Unit/View/TwigEngineTest.php Updates TwigEngine test to register CP template roots via TemplateRootRegistry.
yii2-adapter/constants/Field/Concerns/LegacyFieldConstants.php Updates legacy link-type bridging to use LinkTypeRegistry and finalize-style registration.
yii2-adapter/legacy/events/RegisterComponentTypesEvent.php Updates deprecation messaging to point to registries.
yii2-adapter/legacy/events/RegisterEmailMessagesEvent.php Updates deprecation messaging to point to SystemMessageRegistry.
yii2-adapter/legacy/events/RegisterGqlArgumentHandlersEvent.php Updates handler typing/docs for closures and points deprecation to registry API.
yii2-adapter/legacy/events/RegisterGqlDirectivesEvent.php Updates deprecation messaging to point to GqlDirectiveRegistry.
yii2-adapter/legacy/events/RegisterTemplateRootsEvent.php Updates deprecation messaging to point to TemplateRootRegistry.
yii2-adapter/legacy/events/RegisterUserPermissionsEvent.php Updates deprecation messaging to point to PermissionGroupRegistry.
yii2-adapter/legacy/services/Dashboard.php Moves legacy widget registration bridging into finalize-style registry reconciliation.
yii2-adapter/legacy/services/Elements.php Moves legacy element type registration bridging into finalize-style registry reconciliation.
yii2-adapter/legacy/services/Fields.php Moves legacy field type registration bridging into finalize-style registry reconciliation.
yii2-adapter/legacy/services/Fs.php Moves legacy filesystem type registration bridging into finalize-style registry reconciliation.
yii2-adapter/legacy/services/Gql.php Moves legacy directive registration bridging into finalize-style registry reconciliation.
yii2-adapter/legacy/services/ImageTransforms.php Moves legacy transformer type registration bridging into finalize-style registry reconciliation.
yii2-adapter/legacy/services/SystemMessages.php Removes legacy system-message resolving bridge in favor of registry-based adapter implementation.
yii2-adapter/legacy/services/UserPermissions.php Bridges legacy permissions via PermissionGroupRegistry provider + resets modern cache.
yii2-adapter/legacy/services/Utilities.php Moves legacy utility registration bridging into finalize-style registry reconciliation.
yii2-adapter/legacy/web/View.php Moves legacy template-root bridging into finalize-style registry reconciliation.
yii2-adapter/src/DeprecatedConcepts.php Registers deprecated-concept-provided types via registries rather than resolving events.
yii2-adapter/src/Event/EventCompatibility.php Switches adapter boot bridges to register into registries and adds explicit finalize step.
yii2-adapter/src/Event/LegacyGqlEvents.php Adds registry-based finalize for legacy GQL argument handlers; removes resolve-time bridge.
yii2-adapter/src/LegacyApp.php Ensures legacy finalize registration events run after boot when installed; resets deprecated support flags.
yii2-adapter/src/SystemMessage/LegacySystemMessageRegistry.php Adds adapter wrapper to expose registry messages to legacy Yii event transforms with locale scoping/caching.
yii2-adapter/src/Yii2ServiceProvider.php Wraps SystemMessageRegistry with legacy adapter and registers base site template root via registry.
yii2-adapter/tests-laravel/Fixtures/LegacyPlugin/templates/index.twig Adds fixture template for legacy plugin template-root tests.
yii2-adapter/tests-laravel/Gql/LegacyGqlCompatibilityTest.php Expands adapter test coverage for directive/argument handler bridging behavior.
yii2-adapter/tests-laravel/Http/LegacyActionRoutingTest.php Updates CP routing test to register template roots via registry.
yii2-adapter/tests-laravel/Http/LegacySiteTemplateRoutingTest.php Removes Once::flush() dependency now that template roots are registry-backed.
yii2-adapter/tests-laravel/Legacy/DeprecatedConceptRegistrationCompatibilityTest.php Adds coverage for deprecated-concept registrations finalized through registries.
yii2-adapter/tests-laravel/Legacy/PermissionRegistryCompatibilityTest.php Adds coverage for permission registry/legacy handle stability and rebuild behavior.
yii2-adapter/tests-laravel/Legacy/PluginLifecycleCompatibilityTest.php Adds coverage ensuring legacy registrations finalize after legacy init + modern boot and propagate to view finder hints.
yii2-adapter/tests-laravel/Legacy/RegistrationRegistryCompatibilityTest.php Adds coverage for system messages, permissions, and template roots bridging via registries.
yii2-adapter/tests-laravel/Legacy/TypeRegistryCompatibilityTest.php Adds coverage for legacy type registration events applying to modern registries and identity rules.
yii2-adapter/tests-laravel/View/RegisterTemplateCacheCollectorsTest.php Updates adapter test to assert collector registration via registry.
yii2-adapter/tests/unit/gql/ArgumentHandlerTest.php Updates unit test to register argument handlers via GqlArgumentHandlerRegistry.
yii2-adapter/tests/unit/mail/MailerTest.php Removes commented event-based system-message registration snippet (obsolete with registries).
yii2-adapter/tests/unit/web/ViewTest.php Updates template root tests to populate TemplateRootRegistry instead of listening for resolving events.

Comment thread src/Element/Elements.php
@riasvdv
riasvdv requested a review from brandonkelly July 23, 2026 14:09
@riasvdv
riasvdv marked this pull request as ready for review July 23, 2026 14:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants