Xgenious/ docs

Hooks, Menus and Routes

All three are registered from your provider's boot() method.

Hooks

Hooks work like WordPress. An action runs your code at a point in the page. A filter receives a value, may change it, and returns it.

// Action: output something
$context->hooks()->addAction('admin.layout.footer', fn ($request) => '<!-- hi -->', priority: 10, acceptedArgs: 1);

// Filter: change a value (note the 4th and 5th arguments: priority, number of arguments you accept)
$context->hooks()->addFilter('chat.visitor_message.before_store', function (string $text, $session) {
    return str_replace('secret', '[hidden]', $text);
}, 10, 2);

Core code fires hooks like this:

$html = app(\App\Plugins\HookManager::class)->applyFilters('user.dashboard.nav.items', $groups, $request);

and Blade views fire actions with a directive:

@pluginHook('admin.layout.head', request())

Hook catalog

The framework fires six hooks. They are summarised here; the Hooks Reference explains each one in full, including its exact arguments, priority guidance and examples.

TypeHookFired inPayload
actionadmin.layout.headbackend/partials/header.blade.php$request
actionadmin.layout.footerbackend/admin-master.blade.php$request
actionuser.dashboard.before_contentclient-dashboard/layout/app.blade.php$request
actionuser.dashboard.after_contentclient-dashboard/layout/app.blade.php$request
filteruser.dashboard.nav.itemsUserDashboardNav::applyNavFilter()array of nav groups, $request
filterchat.visitor_message.before_storeChatbotMessageRepositorymessage text, ChatbotSession. Return the text to store.
Note

chat.visitor_message.before_store runs before a visitor message is saved, so the stored transcript, the chat history and the AI provider all see your returned text. The Privacy & Compliance plugin uses it to mask card numbers.

A hook callback that throws is caught and logged. It never breaks the host page. See the Hooks Reference for the full argument and error-handling details.

Listening to Laravel events

Plugins can also use ordinary Laravel events. For example, CRM Sync reacts when a contact is created:

Event::listen('eloquent.created: '.\App\Models\Contact::class, function (\App\Models\Contact $contact) {
    // queue work; never throw into the visitor's request
});

The Slack integration listens to MessageSent, MessageReceived, MessageSentByHumanAgent and ChatHandoffRequested. When your listener runs inside a visitor's request, catch exceptions and prefer queued jobs or app()->terminating() so a slow third party never delays the visitor.

use App\Plugins\Menu\MenuItem;
use App\Plugins\Menu\MenuPosition;

$context->menus()->add('user.dashboard.sidebar', new MenuItem(
    id: 'myplugin.index',
    label: 'My Plugin',
    route: 'plugin.my-plugin.user.index',   // or url: 'https://…'
    icon: 'las la-cog',
    position: MenuPosition::end(10),
    badge: 'PRO',                            // optional small tag next to the label
));

MenuItem accepts id, label, route or url, icon, permission (a string or array; null means always visible), position, weight, children and badge.

LocationRendered byAnchors available
admin.sidebar.topbackend/partials/sidebar.blade.php, below Dashboardcore.dashboard, core.end
admin.sidebarbackend/partials/sidebar.blade.php, above Log Outcore.end
user.dashboard.sidebarUserDashboardNav::pluginGroups(), grouped under "Plugins"none (end only)

Position helpers are MenuPosition::before('anchor', priority), MenuPosition::after('anchor', priority) and MenuPosition::end(priority). An unknown anchor falls back to core.end and logs a warning. Admin items are permission-checked for each viewer.

Routes

Routes are registered through $context->routes(). The framework applies the right middleware group, URL prefix and route-name prefix for you:

MethodMiddlewareURL prefixRoute name prefix
admin(fn)web, auth:admin, admin language and variablesplugins/{id}plugin.{id}.admin.
user(fn)web, auth:web, email verified, user language and variables, maintenance modeuser/plugins/{id}plugin.{id}.user.
web(fn)web{id}plugin.{id}.web.
$context->routes()->user(function ($r) {
    $r->get('/', [MyController::class, 'index'], 'index');      // plugin.my-plugin.user.index
    $r->post('save', [MyController::class, 'save'], 'save');    // plugin.my-plugin.user.save
});

The third argument is the route name, added to the prefix shown above.

Public endpoints (webhooks)

Third-party services such as Slack call your site without a session. Register a bare route outside the groups and protect it yourself:

$context->routes()->post('my-plugin/webhook/{token}', [WebhookController::class, 'handle'], 'plugin.my-plugin.webhook')
    ->middleware('throttle:120,1');
Warning

A bare route has no CSRF or login protection. Always authenticate the request yourself (for example by verifying the provider's signature) and add a throttle.

Getting the current customer

Inside user-dashboard controllers, use the acting_client() helper to get the account owner (it also works when an admin is impersonating a customer). Always scope every query by that client's id so one customer can never see another's data:

$client = acting_client() ?? abort(403);
$items = Item::where('client_id', $client->id)->get();
Still stuck?
Our support team is ready to help you get set up.
Get support