ArtisanPack UI
DocsCMS FrameworkHooks and Events

Hooks and Events

CMS Framework v2.16.0

The CMS Framework exposes a small set of WordPress‑style hooks and events to allow packages and applications to extend behavior without tightly coupling...

The CMS Framework exposes a small set of WordPress‑style hooks and events to allow packages and applications to extend behavior without tightly coupling code.

This guide documents the available hooks grouped by module.

Namespace normalized in 2.5.0 (#193, #194, #195) — most infrastructure, lifecycle, and ability hooks moved onto the ap.cmsFramework.* (or ap.rbac.*) namespace. Every pre-2.5.0 name below is registered as a WordPress-style alias, so existing subscribers keep firing; the old names emit a one-per-request deprecation log entry so downstreams can migrate at their own pace.

Core: Assets

Filters for modifying enqueued assets before retrieval:

  • ap.cmsFramework.admin.enqueuedAssets (was ap.admin.enqueuedAssets)
  • ap.cmsFramework.public.enqueuedAssets (was ap.public.enqueuedAssets)
  • ap.cmsFramework.auth.enqueuedAssets (was ap.auth.enqueuedAssets)

Each filter receives the current associative array of assets and should return the modified array.

addFilter('ap.cmsFramework.admin.enqueuedAssets', function (array $assets) {
    $assets['custom-admin'] = [
        'path' => mix('js/custom-admin.js'),
        'inFooter' => true,
    ];
    return $assets;
});

Users: Settings UI

  • ap.users.settings.sections (filter)

Add or modify sections (tabs) for your user settings UI.

apRegisterUserSettingsSection('profile', 'Profile', 10);

// Internally this uses the following filter:
addFilter('ap.users.settings.sections', function (array $sections) {
    $sections['profile'] = ['label' => 'Profile', 'order' => 10];
    return $sections;
});

Users: Model Events

Actions fired when roles or permissions are registered via managers:

  • ap.rbac.roleRegistered (action, was ap.roleRegistered)
  • ap.rbac.permissionRegistered (action, was ap.permissionRegistered)
addAction('ap.rbac.roleRegistered', function ($role) {
    // React to role creation/registration
});

addAction('ap.rbac.permissionRegistered', function ($permission) {
    // React to permission creation/registration
});

Content: Lifecycle

Added in 2.5.0 (#196).

Every Post and Page model emits lifecycle actions via the FiresLifecycleHooks concern under src/Modules/ContentTypes/Models/Concerns/:

  • ap.cmsFramework.{post,page}.saving
  • ap.cmsFramework.{post,page}.saved
  • ap.cmsFramework.{post,page}.published — fires only on a transition into ContentStatus::Published: first-save-as-published, draft→published, and scheduled→published all count; subsequent saves of an already-published record do not.
  • ap.cmsFramework.{post,page}.trashed — soft delete only; force-delete is skipped.
  • ap.cmsFramework.{post,page}.restored

Admin: Dashboard Widgets

Added in 2.5.0 (#196).

AdminWidgetManager::getAvailableWidgetsForUser() runs its output through ap.cmsFramework.admin.dashboardWidgets, passing the resolved user (or null) so subscribers can make per-user injections without re-resolving auth.

addFilter('ap.cmsFramework.admin.dashboardWidgets', function (array $widgets, ?User $user) {
    if ($user?->hasRole('editor')) {
        $widgets['editorial-queue'] = new EditorialQueueWidget();
    }
    return $widgets;
}, 10, 2);

Admin: Page Middleware

Added in 2.13.0 (#357).

AdminPageManager::registerRoutes() passes the cms.admin.middleware config through ap.cmsFramework.admin.middleware before applying it to every admin page route. Each page's can:{capability} gate is appended after the filtered stack. The config is normalized before the filter runs (non-string and empty entries dropped, an empty or null config replaced with ['web', 'auth']), so callbacks always receive an array. The filtered result is normalized the same way; only an empty result falls back to ['web', 'auth'].

addFilter('ap.cmsFramework.admin.middleware', function (array $middleware) {
    $middleware[] = 'verified';
    return $middleware;
});

Plugins: Hook Registration

Added in 2.5.0 (#196).

PluginManager::activate() fires ap.cmsFramework.plugin.hookRegistered immediately after the plugin's service provider registers, carrying (string $pluginSlug, array $hooks). The hooks array is the optional hooks field from the plugin's plugin.json (empty array when absent) so observers still get a per-plugin signal.

Plugins: Skipped Version Checks

Added in 2.14.0 (#372).

PluginManager::activate() fires ap.cmsFramework.plugin.versionCheckSkipped when a plugin declares min_framework_version (or the deprecated min_host_version) or min_app_version, but the version to compare against is unknown or not a comparable semver. Activation goes ahead. The payload is (string $pluginSlug, array $details), where $details holds constraint (framework or app), required_version, host_version (null when unresolved) and reason (unresolved or not_semver). A warning is logged as well; listen to the action to surface the skip in an admin UI. See Versioning & host compatibility.

Plugins: Deletion and Uninstall

PluginManager::delete() fires ap.cmsFramework.plugin.deleting with (string $slug) before teardown and ap.cmsFramework.plugin.deleted with (string $slug) once the row and files are gone. A deleting listener only runs if the plugin's service provider booted in the same request, which is usually not the case for a plugin deactivated earlier. For cleanup a plugin owns, declare an uninstall class in plugin.json instead (2.13.0, #358); see Uninstalling.

Search: Query

Added in 2.5.0 (#196).

HasContentFilters::applySearchFilter() — used by both BlogManager::getArchiveQuery() and PageManager::getPageQuery() — runs the assembled search query through ap.cmsFramework.search.query with (Builder $q, string $term, array $context) where $context carries the calling manager class, the queried model class, and the full filter array. Enough for a subscriber to swap in a full-text index or route to an external search service.

Site Editor: Reverts

Added in 2.16.0 (#423).

Reverting a template, template part or global-styles override, or unassigning a menu location, fires ap.cmsFramework.siteEditor.reverted with (string $type, ?string $slug, string $theme) after the row is deleted. The row is deleted through its model, so eloquent.deleting / eloquent.deleted fire too. See Site Editor: Hooks and Events.

Conventions

  • Filters should return the modified value; actions return void.
  • Hook names are namespaced with the ap. prefix.
  • Prefer kebab‑case segments for readability.

Utilities

The examples above assume the presence of the following global helpers (provided by the artisanpack-ui/hooks package):

  • addFilter(string $hook, callable $callback)
  • applyFilters(string $hook, mixed $value): mixed
  • addAction(string $hook, callable $callback)
  • doAction(string $hook, ...$args): void

Settings: Registered Settings

  • ap.settings.registeredSettings (filter)

Provide settings definitions from anywhere. Each item should include:

addFilter('ap.settings.registeredSettings', function (array $settings) {
    $settings['site.title'] = [
        'default'  => 'My Site',
        'type'     => 'string',
        'callback' => fn ($value) => trim((string) $value),
    ];

    return $settings;
});

See the Settings module docs for details: Settings Hooks and Events.