Extending behavior without forking core.
Filters vs events
- Filters — Transform data as it flows through core (return modified value)
- Events — React after something happens (side effects, logging)
Registering a filter
In plugin boot():
use App\Filter\FilterHook;
$context->addFilter(FilterHook::MENU_ITEMS, function (array $items, array $ctx): array {
$items[] = ['label' => 'Status', 'href' => '/status', 'target' => '', 'css_class' => ''];
return $items;
}, 10);
Lower priority runs first (default 10). Declare used filters in plugin.json → hooks.filters.
Common filter hooks
| Constant | Value | Transforms |
|---|---|---|
FilterHook::SEO_META | seo.meta | Resolved meta array |
FilterHook::MENU_ITEMS | menu.items | Public menu items for a location |
FilterHook::API_ENTRY_RESPONSE | api.entry.response | REST entry detail JSON |
FilterHook::CONTENT_SAVE | content.save | Entry save POST body before validation |
FilterHook::HTML_SANITIZE | html.sanitize | Sanitized HTML after HTMLPurifier |
Listening to events
use App\Event\ContentEntrySavedEvent;
$context->listenEvent(ContentEntrySavedEvent::class, function (ContentEntrySavedEvent $e) use ($ctx): void {
// React to save
});
Declare events in plugin.json → hooks.events. Use listenEvent() not raw event bus so manifest validation applies.
Capability enforcement
When a plugin declares capabilities or hooks, Struxa enforces the contract at boot—e.g. pdo() requires database.read or database.write; addFilter() requires matching capability for that hook.
Performance
Filter callbacks over 25 ms and boot over 50 ms are logged. Check Extensions → Plugins performance column and Site Health warnings.