Back to Main Site

Core Hooks & Filters Reference: Complete Developer Guide

Last updated on Jun 23, 2026 22:26

Core Hooks & Filters Reference

Audience: Developer — Requires PHP / Laravel knowledge. Category: Technical Reference — Technical documentation for integrated programmers.

What you will learn

  • Understand the Action vs Filter mechanism in PolyCMS
  • Look up hook name, parameters, dispatch location
  • Know how to register hooks from theme functions.php and module ServiceProvider
  • Use priority to control execution order
  • Apply actual use-cases to each hook group

1. Introducing the Hook system

PolyCMS uses a Hook/Filter system similar to WordPress, built natively on Laravel:

  • Action (Hook::doAction): Run side-effects (logging, sending emails, synchronizing data). No need to return.
  • Filter (Hook::applyFilters): Get a value, edit, and force return the adjusted value.

Basic syntax

use App\Facades\Hook;

// Sign up for Action
Hook::addAction('order_completed', function ($order) {
    Mail::to($order->user)->send(new OrderCompletedMail($order));
}, priority: 10);

// Sign up for Filter
Hook::addFilter('post.default_image', function (?string $imageUrl, $post) {
    if ($post?->categories->contains('slug', 'news')) {
        return '/images/news-default.jpg';
    }
    return $imageUrl;
}, priority: 10);

2. Content & Posts

Filters

Hook Parameters Description
post.default_image $imageUrl, $context Default image when the article does not have a featured image
post.frontend_url $url, $post Customize the post's public URL
post.content.render $html, $post Filter HTML content before returning API
post.query.builder $query, $request Edit Eloquent query for list of posts
category.frontend_url $url, $category Customize your category's public URL
content.render.blocks $blocks Filter the block array before rendering
content.render.html $html, $blocks Filter final output HTML
content.render.block.{type} $html, $block Render or override a specific block type

Actions

Hook Parameters Description
tag.saved $tag, $context After the tag is created/updated
tag.deleted $tag, $context After the tag is deleted
category.saved $category, $context After the category is created/updated
category.deleted $category, $context After the category is deleted

Practical use-cases

UC1 — Representative image by category: The news site wants each category to have its own default image when the article has not set a featured image:

Hook::addFilter('post.default_image', function (?string $url, $post) {
    if ($post?->categories->contains('slug', 'tech')) return '/images/defaults/tech.jpg';
    if ($post?->categories->contains('slug', 'lifestyle')) return '/images/defaults/lifestyle.jpg';
    return $url; // fallback admin setting
});

UC2 — Wiki URL: Document theme wants to move post URL from /posts/slug to /docs/slug:

Hook::addFilter('post.frontend_url', function ($url, $post) {
    return ($post->type === 'wiki') ? '/docs/' . $post->slug : $url;
});

UC3 — Automatically insert ads: The advertising module wants to insert a banner after the 3rd paragraph in the article content:

Hook::addFilter('content.render.html', function (string $html) {
    $adBanner = '<div class=\"ad-inline\">Advertisement</div>';
    $parts = explode('</p>', $html, 4);
    if (count($parts) > 3) {
        $parts[2] .= '</p>' . $adBanner;
        return implode('</p>', $parts);
    }
    return $html;
});

3. Media Library

Filters

Hook Parameters Description
media.upload.file $file, $data Edit files before processing
media.upload.data $data, $file Edit upload metadata
media.create.data $mediaData, $file Adjust record data before saving DB
media.delete.should $shouldDelete, $media Gate: return false to prevent deletion
media.url $url, $media Rewrite URL (e.g. CDN)

Actions

Hook Parameters Description
media.uploaded $media, $file, $data After successful upload
media.deleting $media Before deleting
media.deleted $media After deleting

Practical use-cases

UC1 — CDN rewrite: Integrates Cloudflare R2/S3, automatically converts media URLs to CDN domain:

Hook::addFilter('media.url', function (string $url, $media) {
    return str_replace('/storage/', 'https://cdn.mysite.com/', $url);
});

UC2 — Protect important files: Prevent deletion of images being used as site logos:

Hook::addFilter('media.delete.should', function (bool $allow, $media) {
    $logoUrl = get_option('site_logo', null, 'general');
    return ($media->url === $logoUrl) ? false : $allow;
});

UC3 — Auto-optimize when uploading: Automatically create WebP from uploaded images:

Hook::addAction('media.uploaded', function ($media, $file) {
    if (str_starts_with($media->mime_type, 'image/')) {
        dispatch(new ConvertToWebpJob($media));
    }
});

4. E-Commerce — Orders & Refunds

Actions

Hook Parameters Description
order_status_updated $order, $oldStatus, $newStatus When order status changes
order_completed $order When the order is completed
order.refund.processing $order, $validated Before making a refund
order.refund.completed $order, $result After successful refund
order.refund.succeeded $order, $result, $validated, $userId API refund successful
order.refund.failed $order, $validated, $exception, $userId API refund failed

Filters

Hook Parameters Description
order.refund.preview.result $preview, $order, $input Adjust refund preview

Practical use-cases

UC1 — Telegram notification when there is a new order:

Hook::addAction('order_status_updated', function ($order, $old, $new) {
    if ($old === 'pending' && $new === 'processing') {
        TelegramBot::send("Order #{$order->code} has been confirmed!");
    }
});

UC2 — Earn loyalty points when completing an application:

Hook::addAction('order_completed', function ($order) {
    if ($order->user_id) {
        $points = (int) floor($order->total / 10000); // 1 point per 10k
        LoyaltyService::addPoints($order->user_id, $points, "Order #{$order->code}");
    }
});

UC3 — Record audit log when refund fails:

Hook::addAction('order.refund.failed', function ($order, $data, $exception, $userId) {
    AuditLog::create([
        'action' => 'refund_failed',
        'order_id' => $order->id,
        'user_id' => $userId,
        'reason' => $exception->getMessage(),
    ]);
});

5. E-Commerce — Cart, Shipping, Tax, Inventory

Filters

Hook Parameters Description
cart.totals $totals, $cart Adjust cart total
shipping.calculate_cost $cost, $method, $cart Override shipping fees
shipping.available_methods $methods, $address, $cart Filter shipping methods
tax.calculated $result, $subtotal, $address Override tax calculation results
inventory.is_stockable_product $default, $product, $context Identify products with inventory management
review.can_submit $allowed, $user, $product Gate: does it allow reviews?

Actions

Hook Parameters Description
cart.item.added $item, $cart After adding items to cart
cart.item.updated $item, $oldQty, $newQty After updating the quantity
cart.item.removed $item, $cart After removing items from cart
cart.cleared $cart After deleting the entire cart
cart.merged $userCart, $guestCart When merging the guest cart into the user cart
review.submitted $review After submitting a review
review.approved $review After reviewing reviews
review.rejected $review After rejecting the review

Practical use-cases

UC1 — Free shipping for orders over 500k:

Hook::addFilter('shipping.calculate_cost', function ($cost, $method, $cart) {
    $subtotal = collect($cart->items)->sum(fn($i) => $i->price * $i->quantity);
    return ($subtotal >= 500000) ? 0 : $cost;
});

UC2 — Block reviews if you haven't purchased yet:

Hook::addFilter('review.can_submit', function (bool $allowed, $user, $product) {
    $hasPurchased = Order::where('user_id', $user->id)
        ->where('status', 'completed')
        ->whereHas('items', fn($q) => $q->where('product_id', $product->id))
        ->exists();
    return $hasPurchased;
});

UC3 — Facebook Pixel tracking when adding cart:

Hook::addAction('cart.item.added', function ($item, $cart) {
    session()->push('fb_pixel_events', [
        'event' => 'AddToCart',
        'product_id' => $item->product_id,
        'value' => $item->price,
    ]);
});

6. E-Commerce — Payment Gateways

Filters

Hook Parameters Description
payment.gateway.config_schema $schema, $gateway Expand the gateway configuration schema

Use-case: Add "Branch Code" field for Bank Transfer gateway:

Hook::addFilter('payment.gateway.config_schema', function ($schema, $gateway) {
    if ($gateway->code === 'bank_transfer') {
        $schema['branch_code'] = [
            'type' => 'text', 'label' => 'Branch code', 'required' => false,
        ];
    }
    return $schema;
});

7. Theme & Appearance

Filters

Hook Parameters Description
theme.view.data $data, $viewName Inject data into any view
theme.template.resolve $viewName, $templateTheme, $entityType, $entity Override Blade view is rendered
theme.template.registry $templates, $viewType Register new page template
theme.options.values $options Filter the theme options
theme.options.css_vars $cssVars, $themeOptionValues Adjust CSS custom properties
theme.breadcrumbs.post $breadcrumbs, $post Edit article breadcrumb
theme.breadcrumbs.product $breadcrumbs, $product Edit product breadcrumbs
theme.show_page_header $show, $page Hide/show page header
frontend.topbar.banners $banners Inject advertising banners
themes.list $themes Filter admin theme list

Actions

Hook Parameters Description
theme.activated $theme When the main theme is loaded
theme.main.changed $theme, $oldMainTheme When changing the main theme
theme.installing $file Before processing ZIP theme
theme.activating $slug, $type, $mode Before activating the theme
theme.deactivating $slug Before deactivating theme
theme.deleting $theme Before deleting the theme
cms_head (none) Output hook in <head>

Practical use-cases

UC1 — Site-wide promotional banner from module:

Hook::addFilter('frontend.topbar.banners', function (array $banners) {
    $banners[] = [
        'text' => ' Flash Sale — 30% off today!',
        'url' => '/sale',
        'bg_color' => '#ff4444',
    ];
    return $banners;
});

UC2 — Inject Google Analytics into <head>:

Hook::addAction('cms_head', function () {
    $ga = get_option('google_analytics_id', null, 'seo');
    if ($ga) {
        echo "<script async src='https://www.googletagmanager.com/gtag/js?id={$ga}'></script>";
    }
});

UC3 — Inject sidebar data for blog page:

Hook::addFilter('theme.view.data', function ($data, $viewName) {
    if ($viewName === 'posts.index') {
        $data['popular_posts'] = Post::orderBy('views', 'desc')->limit(5)->get();
    }
    return $data;
});

8. Settings & Configuration

Filters

Hook Parameters Description
settings.defaults $defaults, $settingsService Extend/override setting definitions
settings.media.drivers $drivers Register new storage driver
settings.permalinks.structure $structure, $settingsService Adjust permalink structure

Actions

Hook Parameters Description
setting.updating $key, $value, $group, $type Before saving settings
settings.saved $payload After saving settings

Practical use-cases

UC1 — Module registers individual settings on Settings page:

Hook::addFilter('settings.defaults', function ($defaults) {
    $defaults['mymodule'] = [
        'mymodule_api_key' => [
            'key' => 'mymodule_api_key', 'value' => '', 'type' => 'text',
            'label' => 'API Key', 'description' => 'Enter your API key',
        ],
    ];
    return $defaults;
});

UC2 — Clear cache when updating permalink:

Hook::addAction('settings.saved', function ($payload) {
    if (($payload['group'] ?? '') === 'permalinks') {
        Artisan::call('route:clear');
        Cache::tags('routes')->flush();
    }
});

9. Users, Roles & Auth

Filters

Hook Parameters Description
user.resource.to_array $data, $user, $request Expand user API response
auth.login.pre_token $response, $user, $request Intercept login (for 2FA)

Actions

Hook Parameters Description
user.creating / user.updating / user.deleting $user|$data CRUD lifecycle
role.creating / role.updating / role.deleting / role.cloning $role|$data CRUD lifecycle

Practical use-cases

UC1 — Required 2FA for admin:

Hook::addFilter('auth.login.pre_token', function ($response, $user, $request) {
    if ($user->hasRole('admin') && !$request->filled('otp_code')) {
        return response()->json(['requires_2fa' => true, 'user_id' => $user->id], 403);
    }
    return $response; // null = continue creating tokens
});

UC2 — Send welcome email when creating user:

Hook::addAction('user.creating', function ($data) {
    // Queued email will be sent after the user is saved
    dispatch(new SendWelcomeEmail($data['email'], $data['name']));
});

10. SEO

Filters

Hook Parameters Description
seo.canonical_url $url Override canonical URL
seo.site_favicon $iconUrl Override favicon URL

Use-case — Canonical URL for multi-language:

Hook::addFilter('seo.canonical_url', function (string $url) {
    $locale = app()->getLocale();
    if ($locale !== 'en') {
        return url("/{$locale}" . parse_url($url, PHP_URL_PATH));
    }
    return $url;
});

11. Widgets

Filters

Hook Parameters Description
widget.render.instance $instance Adjust widget instance before rendering
widget.render.{widget_type} $widget Edit specific widget data
widget.area.render.instances $instances, $area Filter instances in area
widget.render.output $html, $instance Filter HTML output widget
widget.area.render.output $html, $area Filter HTML output area
widgets.types $widgets Filter widget types admin

Actions

Hook Parameters Description
widgets.register_types $widgetManager Register new type widget
widgets.register_areas $widgetManager Register new widget area

Use-case — Register widget type "Store Map":

Hook::addAction('widgets.register_types', function ($manager) {
    $manager->registerType('store_map', [
        'label' => 'Store Map',
        'description' => 'Google Maps showing store locations',
        'fields' => [
            'api_key' => ['type' => 'text', 'label' => 'Google Maps API Key'],
            'lat' => ['type' => 'text', 'label' => 'Latitude'],
            'lng' => ['type' => 'text', 'label' => 'Longitude'],
        ],
        'view' => 'widgets.store-map',
    ]);
});

12. Admin Navigation & Editor

Filters

Hook Parameters Description
topbar.menu.items $items, $request, $user Add/remove frontend topbar item
topbar.menu.should_show $show, $user Toggle display topbar
admin.editor.panels $panels, $type, $user Add custom panel to block editor

Actions

Hook Parameters Description
admin.menu.build (none) When the sidebar admin menu is built
topbar.menu.context $request, $user When the topbar context is initialized

Use-case — Module adds menu item "SEO Score" to the topbar:

Hook::addFilter('topbar.menu.items', function (array $items, $request, $user) {
    $items[] = [
        'key' => 'seo_score',
        'label' => 'SEO Score',
        'icon' => 'chart-bar',
        'url' => '/admin/seo/score',
        'position' => 50,
    ];
    return $items;
});

13. Modules

Filters

Hook Parameters Description
module.resource.meta $meta, $moduleData Expand the metadata module
modules.list $modulesArray Filter the admin module list
product.query.builder $query, $request Adjust Eloquent query for products

Actions

Hook Parameters Description
module.activating $moduleKey, $module Before activating module
module.deactivating $moduleKey, $module Before deactivating module
module.deleting $moduleKey, $module Before deleting module
module.installing $uploadedFile When installing the ZIP

Use-case — Run migration when activating module:

Hook::addAction('module.activating', function ($moduleKey, $module) {
    Artisan::call('migrate', [
        '--path' => "modules/Polyx/{$moduleKey}/database/migrations",
        '--force' => true,
    ]);
});

14. System Bootstrap

Actions

Hook Parameters Description
roles.register_permissions $permissionRegistry Register custom permissions
register_email_templates $emailTemplateManager Sign up for email templates
layout.register_assets $layoutAssetManager Register layout assets
routes.frontend.register (none) Register additional frontend routes

Use-case — Private permission registration module:

Hook::addAction('roles.register_permissions', function ($registry) {
    $registry->register('manage analytics', 'Analytics', 'View and manage analytics dashboard');
    $registry->register('export reports', 'Analytics', 'Export analytics reports to CSV');
});

Related articles


PolyCMS is an open-source content management system for modern web applications, inspired by the WordPress plugin and theme ecosystem but built on top of the Laravel framework. It is designed to provide a complete foundation for content publishing, e-commerce, multi-language support, and extensible module architecture — powered by a Vue 3 admin panel with data served entirely through RESTful APIs.

Whether you're building a blog, a documentation site, an online store, or a multi-tenant SaaS platform, PolyCMS aims to give you a comprehensive starting scaffold so you can ship quickly and extend easily through integrated modules and themes. In particular, themes in PolyCMS follow a multi-theme architecture — one Main theme and an unlimited number of Sub themes can run side by side on the same installation.

We hope this ready-made foundation proves useful for building your next website, blog, or web app, saving you from having to start completely from scratch.