ArtisanPack UI
DocsCMS FrameworkThemes - Lifecycle Hooks

Themes - Lifecycle Hooks

CMS Framework v2.8.0

The Themes module fires action hooks at six points in a theme's lifecycle — two on activation, two on installation, and two on update. Hooks use the...

The Themes module fires action hooks at six points in a theme's lifecycle — two on activation, two on installation, and two on update. Hooks use the artisanpack-ui/hooks package (doAction() / addAction()).

Added in 2.0.0.

Activation hooks

ThemeManager::activateTheme($slug) fires:

theme.activating

  • Fired: before the active-theme setting changes.
  • Arguments: (string $slug, ThemeContract $theme)
  • Listener may throw: yes — a thrown exception short-circuits activation and the prior theme stays active.
  • Typical uses: validate the incoming theme against app-side preconditions, clear theme-scoped caches, write an audit log.

theme.activated

  • Fired: after the active-theme setting is updated and view paths are re-registered.
  • Arguments: (string $slug, ThemeContract $theme)
  • Listener may throw: yes, but the change is already committed — throwing rolls back nothing.
  • Typical uses: warm caches that depend on the active theme, send a "theme switched" notification, fire a webhook.

Installation hooks

ThemeManager::installFromZip($zipPath) fires:

theme.installing

  • Fired: after successful extraction and strict manifest validation, before the install is committed.
  • Arguments: (string $slug, array $manifest)
  • Listener may throw: yes — a thrown exception aborts the install. The extracted directory is rolled back so you never end up with a half-installed theme.
  • Typical uses: verify the installing user has permission, check a license server, scan the extracted files for unwanted contents.

theme.installed

  • Fired: after the install is committed.
  • Arguments: (string $slug, array $manifest)
  • Listener may throw: technically yes, but the install is already on disk; throwing won't roll it back.
  • Typical uses: run theme-shipped seeders, queue a thumbnail-generation job, notify the user, fire a webhook.

Update hooks

UpdateManager::updateTheme($slug) fires these. See themes/Updating for the full update flow.

Added in 2.8.0.

theme.updating

  • Fired: after an update has been found, before anything is backed up or downloaded.
  • Arguments: (string $slug, string $oldVersion, string $newVersion)
  • Listener may throw: yes — a thrown exception aborts the update. Nothing has changed on disk at that point, so a veto costs nothing and needs no rollback.
  • Typical uses: pin a theme to a version, check a license server, refuse updates during a content freeze.

theme.updated

  • Fired: after the new files are swapped in and caches are refreshed.
  • Arguments: (string $slug, string $newVersion, array $manifest)
  • Listener may throw: it propagates to the caller, but it will not roll the update back. The new files are already validated and in place; reverting them because a notification listener failed would be worse than surfacing the error.
  • Typical uses: clear a CDN, rebuild assets, notify the user, fire a webhook.

Registering listeners

Register with addAction():

use function addAction;

// In a service provider's boot()
addAction('theme.activating', function (string $slug, $theme) {
    if (! auth()->user()?->can('themes.activate')) {
        throw new \RuntimeException('Not allowed to activate themes.');
    }
});

addAction('theme.installed', function (string $slug, array $manifest) {
    \Log::info('Theme installed', ['slug' => $slug, 'version' => $manifest['version'] ?? 'unknown']);

    \App\Jobs\GenerateThemeThumbnail::dispatch($slug);
});

Listener priority

addAction() accepts an optional priority argument — lower numbers run first, default is 10. Use this to ensure your listener runs before or after another package's listener:

addAction('theme.installing', fn () => /* runs first */, 5);
addAction('theme.installing', fn () => /* runs default */);
addAction('theme.installing', fn () => /* runs last */, 20);

Order of operations

activateTheme($slug):
  1. discoverThemes() (refresh cache)
  2. resolveTheme($slug)
  3. doAction('theme.activating', $slug, $theme) ← may throw to abort
  4. update settings['themes.activeTheme'] = $slug
  5. re-register view paths
  6. doAction('theme.activated', $slug, $theme)

installFromZip($zipPath):
  1. validateZip($zipPath)
  2. extractZip($zipPath) → temporary directory
  3. validateManifest($extractedManifest) ← may throw, rolls back
  4. doAction('theme.installing', $slug, $manifest) ← may throw, rolls back
  5. move extracted directory into themes path
  6. doAction('theme.installed', $slug, $manifest)

updateTheme($slug):
  1. checkThemeUpdate($slug) → no update? return early, no hooks fire
  2. doAction('theme.updating', $slug, $old, $new) ← may throw to abort
  3. download + verify checksum ← may throw, nothing touched yet
  4. extract + validate into themes/.updates/ ← may throw, nothing touched yet
  5. back up the installed directory
  6. swap staged directory into place ← may throw, restores from backup
  7. refresh caches + view paths
  8. doAction('theme.updated', $slug, $new, $manifest) ← throwing does NOT roll back

Removing listeners

Use removeAction() to unregister a specific callback, or removeAllActions() to clear everything at a given priority (or for the whole hook):

use function removeAction;
use function removeAllActions;

$callback = fn (string $slug) => \Log::info('Activated', ['slug' => $slug]);
addAction('theme.activated', $callback);

// Remove just this callback
removeAction('theme.activated', $callback);

// Remove all callbacks at priority 5
removeAllActions('theme.activated', 5);

// Remove every listener for the hook
removeAllActions('theme.activated');