Skip to main content
Render hooks give you named injection points inside Shopper’s admin views. Instead of overriding templates, you register a callback on a hook name and Shopper renders your content in the right place at the right time. This is the recommended way to extend any admin page adding a custom banner to the order detail sidebar, inserting a widget above the product list, or injecting tracking scripts into the page head.

Registering a Hook

Hooks are registered on the ShopperPanel instance via the Shopper facade. The recommended place to register them is in the boot() method of your application’s service provider or in a dedicated extension service provider.
use Shopper\Facades\Shopper;
use Shopper\View\OrderRenderHook;

Shopper::renderHook(
    OrderRenderHook::DETAIL_SIDEBAR_AFTER,
    fn (): string => view('orders.custom-sidebar-widget')->render(),
);
The callback receives no arguments and must return a string. You can return any HTML, a rendered Blade view, or a Livewire component tag.

Returning a Blade View

use Shopper\Facades\Shopper;
use Shopper\View\ProductRenderHook;

Shopper::renderHook(
    ProductRenderHook::EDIT_CONTENT_AFTER,
    fn (): string => view('products.seo-panel')->render(),
);

Returning a Livewire Component

use Shopper\Facades\Shopper;
use Shopper\View\CustomerRenderHook;

Shopper::renderHook(
    CustomerRenderHook::SHOW_TABS_END,
    fn (): string => '<livewire:customer-loyalty-tab />',
);

Registering Multiple Hooks

You can chain multiple renderHook() calls on the same Shopper instance:
use Shopper\Facades\Shopper;
use Shopper\View\LayoutRenderHook;

Shopper::renderHook(
    LayoutRenderHook::HEAD_END,
    fn (): string => '<script src="https://cdn.example.com/analytics.js" defer></script>',
);

Shopper::renderHook(
    LayoutRenderHook::BODY_START,
    fn (): string => view('analytics.noscript-tag')->render(),
);
Multiple callbacks registered on the same hook are rendered in the order they were registered, concatenated together.

Disabling a Hook

Hooks registered from an addon respect the addon’s enabled state they are never registered when the addon is disabled. For application-level hooks, you can conditionally register them:
if (config('features.custom_order_widget')) {
    Shopper::renderHook(
        OrderRenderHook::DETAIL_MAIN_AFTER,
        fn (): string => view('orders.external-status')->render(),
    );
}

Available Hooks

Layout Hooks

LayoutRenderHook covers the global admin layout present on every page.
ConstantHook NamePosition
HEAD_STARTshopper::head.startImmediately after <head>
HEAD_ENDshopper::head.endImmediately before </head>
BODY_STARTshopper::body.startImmediately after <body>
BODY_ENDshopper::body.endImmediately before </body>
HEADER_STARTshopper::header.startStart of the top navigation bar
HEADER_ENDshopper::header.endEnd of the top navigation bar
CONTENT_STARTshopper::content.startBefore the main page content area
CONTENT_ENDshopper::content.endAfter the main page content area
DASHBOARD_STARTshopper::dashboard.startTop of the dashboard page
DASHBOARD_ENDshopper::dashboard.endBottom of the dashboard page
ACCOUNT_STARTshopper::account.startTop of the account/profile page
ACCOUNT_ENDshopper::account.endBottom of the account/profile page
SETTINGS_INDEX_STARTshopper::settings.index.startTop of the settings index page
SETTINGS_INDEX_ENDshopper::settings.index.endBottom of the settings index page

Product Hooks

ProductRenderHook targets the product list and product edit pages.
ConstantHook NamePosition
INDEX_TABLE_BEFOREshopper::products.index.table.beforeAbove the products table
INDEX_TABLE_AFTERshopper::products.index.table.afterBelow the products table
EDIT_HEADER_AFTERshopper::product.edit.header.afterBelow the product page header
EDIT_TABS_BEFOREshopper::product.edit.tabs.beforeBefore the edit page tabs
EDIT_TABS_ENDshopper::product.edit.tabs.endAfter the last edit page tab (use to add new tabs)
EDIT_CONTENT_BEFOREshopper::product.edit.content.beforeBefore the active tab content
EDIT_CONTENT_AFTERshopper::product.edit.content.afterAfter the active tab content
VARIANT_HEADER_AFTERshopper::product.variant.header.afterBelow the variant page header
VARIANT_MAIN_AFTERshopper::product.variant.main.afterAfter the variant main content area
VARIANT_SIDEBAR_AFTERshopper::product.variant.sidebar.afterAfter the variant sidebar

Order Hooks

OrderRenderHook targets the order list, order detail, shipments, and abandoned carts pages.
ConstantHook NamePosition
INDEX_TABLE_BEFOREshopper::orders.index.table.beforeAbove the orders table
INDEX_TABLE_AFTERshopper::orders.index.table.afterBelow the orders table
DETAIL_HEADER_AFTERshopper::order.detail.header.afterBelow the order detail page header
DETAIL_MAIN_BEFOREshopper::order.detail.main.beforeBefore the main content column
DETAIL_MAIN_AFTERshopper::order.detail.main.afterAfter the main content column
DETAIL_SIDEBAR_BEFOREshopper::order.detail.sidebar.beforeBefore the sidebar column
DETAIL_SIDEBAR_AFTERshopper::order.detail.sidebar.afterAfter the sidebar column
SHIPMENTS_TABLE_BEFOREshopper::shipments.index.table.beforeAbove the shipments table
SHIPMENTS_TABLE_AFTERshopper::shipments.index.table.afterBelow the shipments table
ABANDONED_CARTS_TABLE_BEFOREshopper::abandoned-carts.table.beforeAbove the abandoned carts table
ABANDONED_CARTS_TABLE_AFTERshopper::abandoned-carts.table.afterBelow the abandoned carts table

Customer Hooks

CustomerRenderHook targets the customer list and customer detail pages.
ConstantHook NamePosition
INDEX_TABLE_BEFOREshopper::customers.index.table.beforeAbove the customers table
INDEX_TABLE_AFTERshopper::customers.index.table.afterBelow the customers table
CREATE_FORM_BEFOREshopper::customer.create.form.beforeBefore the new customer form
CREATE_FORM_AFTERshopper::customer.create.form.afterAfter the new customer form
SHOW_HEADER_AFTERshopper::customer.show.header.afterBelow the customer detail header
SHOW_TABS_BEFOREshopper::customer.show.tabs.beforeBefore the customer detail tabs
SHOW_TABS_ENDshopper::customer.show.tabs.endAfter the last customer tab (use to add new tabs)
SHOW_CONTENT_BEFOREshopper::customer.show.content.beforeBefore the active tab content
SHOW_CONTENT_AFTERshopper::customer.show.content.afterAfter the active tab content

Collection Hooks

CollectionRenderHook targets the collection list and collection edit pages.
ConstantHook NamePosition
INDEX_TABLE_BEFOREshopper::collections.index.table.beforeAbove the collections table
INDEX_TABLE_AFTERshopper::collections.index.table.afterBelow the collections table
EDIT_FORM_BEFOREshopper::collection.edit.form.beforeBefore the collection edit form
EDIT_FORM_AFTERshopper::collection.edit.form.afterAfter the collection edit form

Catalog Hooks

CatalogRenderHook covers the catalog support pages: categories, brands, tags, attributes, and reviews.
ConstantHook NamePosition
CATEGORIES_TABLE_BEFOREshopper::categories.index.table.beforeAbove the categories table
CATEGORIES_TABLE_AFTERshopper::categories.index.table.afterBelow the categories table
BRANDS_TABLE_BEFOREshopper::brands.index.table.beforeAbove the brands table
BRANDS_TABLE_AFTERshopper::brands.index.table.afterBelow the brands table
TAGS_TABLE_BEFOREshopper::tags.index.table.beforeAbove the tags table
TAGS_TABLE_AFTERshopper::tags.index.table.afterBelow the tags table
ATTRIBUTES_TABLE_BEFOREshopper::attributes.index.table.beforeAbove the attributes table
ATTRIBUTES_TABLE_AFTERshopper::attributes.index.table.afterBelow the attributes table
REVIEWS_TABLE_BEFOREshopper::reviews.index.table.beforeAbove the reviews table
REVIEWS_TABLE_AFTERshopper::reviews.index.table.afterBelow the reviews table

Sales Hooks

SalesRenderHook covers the discounts and suppliers pages.
ConstantHook NamePosition
DISCOUNTS_TABLE_BEFOREshopper::discounts.index.table.beforeAbove the discounts table
DISCOUNTS_TABLE_AFTERshopper::discounts.index.table.afterBelow the discounts table
SUPPLIERS_TABLE_BEFOREshopper::suppliers.index.table.beforeAbove the suppliers table
SUPPLIERS_TABLE_AFTERshopper::suppliers.index.table.afterBelow the suppliers table

Example: Injecting an External Status Badge on Order Detail

A real-world scenario: your store syncs orders to a fulfillment service (ShipStation, ShipBob, etc.) and you want to display the external status directly on the order detail page, without modifying any Shopper files. Create a Livewire component:
use Livewire\Component;
use Livewire\Attributes\Locked;

final class ExternalOrderStatus extends Component
{
    #[Locked]
    public int $orderId;

    public function render(): \Illuminate\Contracts\View\View
    {
        $status = ExternalFulfillmentService::getStatus($this->orderId);

        return view('livewire.external-order-status', ['status' => $status]);
    }
}
Register the hook in your service provider:
use Shopper\Facades\Shopper;
use Shopper\View\OrderRenderHook;

public function boot(): void
{
    Shopper::renderHook(
        OrderRenderHook::DETAIL_SIDEBAR_AFTER,
        fn (): string => '<livewire:external-order-status :order-id="$order->id" />',
    );
}

Using Hook Constants vs. Raw Strings

Always use the class constants instead of raw hook name strings. They are checked by static analysis, will trigger IDE autocompletion, and won’t silently break if a hook name changes in a future release.
use Shopper\View\ProductRenderHook;

Shopper::renderHook(ProductRenderHook::EDIT_CONTENT_AFTER, fn () => '...');

Shopper::renderHook('shopper::product.edit.content.after', fn () => '...');