Hooks & Filters: Extending the Core

Last updated on May 21, 2026 01:19

The golden rule of PolyCMS development is: Never modify the core codebase.

To allow developers to alter system behavior, inject data, or change rendering outputs safely, PolyCMS utilizes a powerful event-driven architecture known as the Hook System, managed by the HookManager service.

If you are familiar with WordPress development, this system will feel instantly recognizable.

Actions vs. Filters

There are two types of hooks in PolyCMS:

  • Actions (doAction / addAction):

Actions are void callbacks. They occur at specific lifecycle events (e.g., a post is saved, an order is refunded, the admin menu is built). You use Actions to do something (like send an email or write to a log) when an event happens. They do not return a value to the core.

  • Filters (applyFilters / addFilter):

Filters are value transformers. The core system passes a variable (like a string of HTML or an array of configuration data) through a Filter hook before using it. You use Filters to modify and return that data.

Using the Hook Facade

Both Modules (in their Service Providers) and Themes (in their functions.php file) interact with the system using the App\Facades\Hook facade.

Registering an Action

To execute code when a specific event happens, use Hook::addAction().

Syntax: Hook::addAction(string $hookName, callable|string $callback, int $priority = 10);

Example: Triggering a custom notification when a product is saved.

use App\Facades\Hook;
use App\Models\Product;

// Priority 10 is the default. Lower numbers run earlier.
Hook::addAction('product.saved', function (Product $product) {
    // Logic to sync product data to an external ERP system
    MyErpService::sync($product);
}, 10);

Registering a Filter

To modify data before the core processes it, use Hook::addFilter().

Syntax: Hook::addFilter(string $hookName, callable|string $callback, int $priority = 10);

Example: Appending a custom copyright notice to the bottom of all post content.

use App\Facades\Hook;

Hook::addFilter('content.render.html', function (string $html) {
    $copyright = '<p class="text-sm text-gray-500">© ' . date('Y') . ' PolyCMS.</p>';
    
    // You MUST return the modified value in a Filter!
    return $html . $copyright; 
}, 20); // Priority 20 ensures this runs after other standard text filters

Creating Custom Hooks in Your Modules

You are not limited to using core hooks. If you are building a complex module, you should expose your own hooks so other developers can extend your code.

Triggering an Action:

// Inside your module's controller
Hook::doAction('my_module.user.subscribed', $user, $planId);

Applying a Filter:

// Inside your module's rendering engine
$price = 100;
// Allow other modules to modify the price before charging
$finalPrice = Hook::applyFilters('my_module.checkout.price', $price, $user);

Best Practices

  • Priority Management: Be mindful of priorities. If you need your filter to execute last to guarantee it overrides everything else, set a high priority number (e.g., 999).

  • Performance: Do not run heavy database queries inside rendering filters (like content.render.html), as this hook may fire dozens of times per page load.

  • Documentation: Always document any custom hooks you add to your modules so other developers know they exist!