Xgenious/ docs

Hooks Reference

This is the complete catalogue of extension points MultiSaas fires. Plugins and themes both extend the platform through this single contract. For the API itself and worked examples, see Plugin Development and Theme Development.

Danger

Removing or renaming an emitter tag silently breaks every listener — there is no error at edit time. The tags listed with a CRITICAL badge are protected by php artisan hook:audit and by the rule that you never remove a hook call while editing.

How the system works

All extensibility is on the WordPress-like hook bus:

add_action('tag', $cb, $priority) / do_action('tag', ...$args) / render_action('tag', ...$args)
add_filter('tag', $cb, $priority) / apply_filters('tag', $value, ...$args)
menu_insert_before / menu_insert_after / menu_insert_before_many / menu_insert

hook() (HookService) stores tag ⇒ priority ⇒ [callable]. Unknown tags are no-ops (do_action returns void, render_action returns '', apply_filters returns $value). Register callbacks from a service provider's boot(); the registry is per-request.

Naming: snake_case with a scope prefix. Generic frontend_* / admin_* tags fire on both landlord and tenant; scoped landlord_* / tenant_* tags fire only in that context.

Frontend hooks

TagTypeScopeFired in
frontend_headactionbothlandlord/frontend/partials/header.blade.php and tenant/frontend/partials/header.blade.php, before </head>
landlord_frontend_headactionlandlordlandlord header, before </head>
tenant_frontend_headactiontenanttenant header, before </head> (theme headerHook CSS/JS is emitted independently)
landlord_frontend_headeractionlandlordafter the navbar
tenant_frontend_headeractiontenantafter the navbar
frontend_footeractionbothboth footers, before </body>
landlord_frontend_footeractionlandlordlandlord footer
tenant_frontend_footeractiontenanttenant footer — Blog / Service / Product register here
landlord_frontend_before_content / _after_contentactionlandlordfrontend-page-master, around @yield('content')
tenant_frontend_before_content / _after_contentactiontenantsame, tenant master
tenant_frontend_navbaractiontenanttenant/frontend/partials/navbar.blade.php after the theme navbar fallback
tenant_frontend_widget_areaactiontenanttenant/frontend/partials/widget-area.blade.php — theme footerWidgetArea
add_action('tenant_frontend_head', function () {
    echo '<link rel="stylesheet" href="'.asset('plugin-assets/yourplugin.css').'">';
}, 10);

Admin panel hooks

TagTypeScopeFired in
admin_headactionbothboth admin headers, before </head>
admin_footeractionbothboth admin footers, before </body>
landlord_admin_headactionlandlord adminlandlord/admin/partials/header.blade.php
landlord_admin_footeractionlandlord adminlandlord/admin/partials/footer.blade.php
landlord_admin_before_contentactionlandlord adminadmin-master, inside the content wrapper, before @yield('content') — every landlord admin page
landlord_admin_after_contentactionlandlord adminsame position, after the page content
landlord_admin_menu_itemsfilterlandlord admin sidebarSidebarMenuHelper::render_sidebar_menus() — positionable item array; anchor keys are item ids
landlord_admin_menuactionlandlord admin sidebarraw-HTML append after the rendered items
tenant_admin_headactiontenant admintenant/admin/partials/header.blade.php
tenant_admin_footeractiontenant admintenant/admin/partials/footer.blade.php
tenant_admin_before_contentactiontenant admintenant/admin/admin-master.blade.php, before @yield('content')
tenant_admin_after_contentactiontenant adminsame position, after the page content
tenant_admin_menu_itemsfiltertenant admin sidebarSidebarMenuHelper::render_tenant_sidebar_menus() — positionable item array; item shape route, label, parent, icon, permissions, badge
tenant_admin_menuactiontenant admin sidebarraw-HTML append after the rendered items

landlord_admin_menu_items uses the same shape as tenant_admin_menu_items (MenuWithPermission items). Use the menu helpers with anchor keys equal to item ids (dashboard-menu, general-settings-menu-items, appearance-settings-menu-items, languages, external menu ids):

add_filter('tenant_admin_menu_items', function (array $items) {
    return menu_insert_before_many($items, 'appearance-settings-menu-items', [
        'seo-manager-menu' => [
            'route'       => 'tenant.admin.seo-manager.settings',
            'label'       => __('SEO Manager'),
            'parent'      => null,
            'permissions' => ['seo-manager'],
            'icon'        => 'mdi mdi-search-web',
            'class'       => 'plugin-menu',
        ],
    ]);
}, 10);

User dashboard menu hooks

The landlord user dashboard sidebar (landlord/frontend/user/dashboard/user-master.blade.php, web guard — fires only for authenticated users) and the tenant user dashboard mirror each other.

landlord_user_dashboard_menu_items (filter, preferred)

Core items in render order, with their anchor keys:

KeyItemConditional
dashboardDashboardalways
walletMy Wallet (submenu: My Wallet, Settings)Wallet module enabled + option on
payment_logsPayment Logsalways
custom_domainCustom Domainuser owns a tenant
support_ticketsSupport Ticketsalways
edit_profileEdit Profilealways
change_passwordChange Passwordalways
logoutLogoutalways
add_filter('landlord_user_dashboard_menu_items', function (array $items) {
    if (! auth()->user() || count(auth()->user()->tenant_details ?? []) < 1) {
        return $items;               // condition decides visibility
    }
    return menu_insert_after($items, 'custom_domain', 'my_domains', [
        'label'  => __('My Domains'),
        'url'    => route('landlord.user.dashboard.domains.index'),
        'icon'   => 'la-globe',
        'active' => request()->routeIs('landlord.user.dashboard.domains.*'),
    ]);
}, 10);

When an anchor is optional (may be absent for some users), use menu_insert with fallbacks so the item stays pinned above a safe anchor:

return menu_insert($items, 'tolt_affiliate', [
    'label' => __('Affiliate Earnings'), 'url' => route('landlord.user.dashboard.tolt.index'),
    'icon' => 'la-handshake', 'active' => request()->routeIs('landlord.user.dashboard.tolt.*'),
], ['after' => 'payment_logs', 'before' => 'logout']);

Item shape: label, url, icon (a las la-* / la-* icon name), optional active (bool), optional children (array of ['label','url','active'] for submenus).

Other dashboard hooks

TagTypeScope
landlord_user_dashboard_menuactionlandlord user dashboard — legacy raw-HTML append after Logout
landlord_user_dashboard_before_content / _after_contentactionlandlord user dashboard — around the whole sidebar + content wrapper
tenant_user_dashboard_menu_itemsfiltertenant user dashboard — anchors: dashboard, hotel_bookings, restaurant_orders, product_logs, donation_logs, plan_logs, event_logs, applied_jobs, appointments, support_tickets, edit_profile, change_password, logout
tenant_user_dashboard_menuactiontenant user dashboard — legacy raw-HTML append
tenant_user_dashboard_before_content / _after_contentactiontenant user dashboard — around the whole wrapper
Note

hotel_bookings and restaurant_orders are module-gated and may be absent as anchors. The menu_insert helper appends when no anchor exists, which keeps the item visible instead of silently dropping it.

Landlord tenant-list hooks

landlord/admin/tenant/index.blade.php is fully hookable end-to-end:

TagTypePosition / payload
landlord_admin_tenant_list_beforeactionBefore the card
landlord_admin_tenant_list_toolbaractionInside the toolbar
landlord_admin_tenant_list_thactionInside <thead> — echo a <th>
landlord_admin_tenant_list_cellfilterPer row: ($html, $user) → extra <td> HTML
landlord_admin_tenant_row_actions_afteractionInside the row dropdown: ($user)
landlord_admin_tenant_list_rows_afteractionAfter all rows in <tbody>
landlord_admin_tenant_list_after_tableactionAfter the table element
landlord_admin_tenant_list_afteractionAfter the whole card
landlord_admin_tenant_paginationfilter($html) → modified pagination markup
landlord_admin_tenant_list_scriptsactionAfter the page scripts section

Pricing, plan and order hooks

Extension points on the landlord frontend purchase funnel (/pricing is the PageBuilder price-plan addon; details is view-plan; checkout is order-page):

TagTypeFired inPayload
landlord_pricing_plan_buttonsfilterPageBuilder/views/landlord/addons/common/price-plan.blade.php — after each plan's default buttons($html, $price_plan_item) → HTML appended to the button wrapper
landlord_plan_view_topactionview-plan.blade.php — top of the pricing-details section—
landlord_plan_view_bottomactionsame file — bottom of the section—
landlord_plan_featuresfiltersame file — the features list($html, $order_details) — return non-empty HTML to replace the default list; '' for the default
landlord_order_form_topactionorder-page.blade.php — inside the order form, after @csrf($order_details)
landlord_order_form_bottomactionsame file — right after </form>($order_details)
landlord_order_gatewaysfiltersame file — the payment-gateway block($html, $order_details) — wraps render_payment_gateway_for_form() output
add_filter('landlord_pricing_plan_buttons', function ($html, $plan) {
    return $plan->price == 0 ? $html.'<span class="badge">Free forever</span>' : $html;
}, 10, 2);

Fallback template filters

Fallback frontend templates (used when the active theme does not ship a view) expose filters so plugins can replace or decorate whole sections without touching core views. When the filter returns empty, the fallback renders its own default.

TagTypeScopeControls
shop_grid_productsfiltertenantProduct grid HTML in fallbacks/frontend/shop/*
shop_sidebar_filtersfiltertenantShop sidebar filter block
shop_before_detailsactiontenantBefore the product details section
blog_sidebarfiltertenantBlog sidebar block
event_gridfiltertenantEvent listing grid
Warning

Never hard-reference a plugin/module view namespace in a fallback without a view_exists() guard. An unregistered namespace throws "No hint path defined"; the helper catches and returns false.

Payment gateway hooks

TagTypeFires whenContract
landlord_payment_gateways / tenant_payment_gatewaysfilterThe registry builds the gateway list (lazy, once per request)(array $gateways) — add $gateways[$slug] = […definition…], return the array
{scope}_gateway_credentials_{slug}filterSettings UI or your charge handler needs saved credentials(array $credentials) → key/value array
{scope}_gateway_settings_save_{slug}actionAdmin submits the generic settings form(array $data) — persist the POST (field names follow {slug}_{field})
{scope}_gateway_ipn_{slug}actionProvider calls the generic IPN endpoint(Request $request) → return redirect()/Response via dispatchActionResponse
{scope}_gateway_payment_completeactionAfter gateway_mark_payment_complete() records the payment($order_id, $transaction_id)
{scope}_gateway_checkout_optionsfilterCheckout page builds the gateway selection list(array $options) → modified options

Critical tags (money movement): landlord_payment_gateways, tenant_payment_gateways, tenant_gateway_payment_complete, landlord_gateway_payment_complete. Plugins must never remove the filter or the IPN dispatch — payments silently stop.

Public helpers: gateway_registry(), gateway_currency(), gateway_success_url($scope,$id), gateway_cancel_url($scope), gateway_ipn_url($slug), gateway_mark_payment_complete($scope,$id,$txn), gateway_charge_context($params).

Response conventions:

CallsiteMust returnIf not
Charge handlerRedirectResponse / ResponseThe calling flow has nothing to redirect to
IPN closureredirect() / Response (returned, not echoed)No callback → 404; returns nothing → core falls back to the cancel URL
Settings save closureNothing (plain action)—
Checkout options / credentials filtersThe (modified) arrayForgetting return silently empties the list

Theme-local hooks

Every theme ships these placeholders (empty string when no plugin listens). hook:audit verifies them.

LocationTagPurpose
headerNavbarArea/navbar.blade.php toptheme_before_navbarInject above the navbar.
headerNavbarArea/navbar.blade.php bottomtheme_after_navbarInject below the navbar.
footerWidgetArea/widget-area.blade.php toptheme_before_footer_widgetsInject above the footer widgets.
footerWidgetArea/widget-area.blade.php bottomtheme_after_footer_widgetsInject below the footer widgets.

These are generic — a plugin listening on theme_before_navbar fires for every theme. For theme-scoped hooks use ThemeHookService::themeTag($slug, 'before_navbar').

Hooks fired by shipped plugins

These are stable extension points you can consume today.

DomainReseller

TagTypePayloadFires when
domain_providersfilter(array $providers)The provider registry is built — add/replace a reseller provider
domain_dns_providerfilter(mixed $class, string $domain)DNS provider resolved for a domain — return a class to override
domain_purchase_bodyfilter(array $body, array $domain_data)Just before the purchase API call
domain_dns_record_setaction($domain, $type, $name, $value, $result)After a DNS record is created/updated
domain_dns_record_deletedaction($domain, $recordId, $result)After a DNS record is deleted
domain_whois_privacy_toggledaction($domain, bool $enable, $result)After WHOIS privacy is toggled
domain_wallet_deductedaction($userId, $amount, $domainPaymentLogId)After a successful wallet deduction

ToltAffiliate

ToltAffiliate consumes several hooks and fires none of its own yet; it registers on landlord_admin_menu_items, landlord_user_dashboard_menu_items, landlord_gateway_payment_complete (to report completed orders to Tolt), landlord_frontend_head and landlord_frontend_footer (the tolt.js pixel and lead binding).

Firing your own hooks

  • Prefix with your slug: yourplugin_after_enrollment.
  • Fire filters wherever a value could plausibly be overridden: $title = apply_filters('yourplugin_widget_title', $title, $widget);.
  • For template sections, use render_action in your Blade.
  • Document every hook you add (tag, type, payload, firing location) in the same format as this page.
do_action('yourplugin_after_enrollment', $enrollment, $user);

add_action('yourplugin_after_enrollment', function ($enrollment, $user) {
    // react
}, 10);

Verifying and debugging

# Full audit: emitters × listeners × per-theme hooks
php artisan hook:audit
php artisan hook:audit --verbose          # prints the file:line table
php artisan hook:audit --theme=agency     # one theme
php artisan hook:audit --theme=my-theme   # third-party theme in themes/my-theme/

# Raw grep used to generate the audit
grep -rn "do_action\|render_action\|apply_filters" core --include="*.php" --include="*.blade.php" | sort
grep -rn "add_action\|add_filter" core plugins --include="*.php" | grep -v "function add_" | sort

App\Services\Hook\MandatoryHooks::all() is the machine-readable source of truth consumed by hook:audit. If you add a new render_action or apply_filters in core, add its tag to MandatoryHooks and to this page in the same commit.

Tip

Hook registration is per-request, so after editing a provider run php artisan view:clear; opcache can otherwise serve stale provider code.

Still stuck?
Our support team is ready to help you get set up.
Get support