> ## Documentation Index
> Fetch the complete documentation index at: https://docs.laravelshopper.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Control Panel

> Customize the Shopper control panel with your own assets, routes, and branding

The control panel may be customized in a number of different ways. You may add new pages, menus, a stylesheet, or maybe you just want to add some arbitrary Javascript.
When you need to add features to your Shopper administration, you can first set up some configurations

## Adding CSS and JS Assets

Shopper can load extra stylesheets and JavaScript files in the admin panel. Register them from a service provider using the `Shopper` facade:

```php theme={null}
use Shopper\Facades\Shopper;

public function boot(): void
{
    Shopper::styles([
        '/css/admin.css',
        'https://cdn.example.com/library.min.css',
    ])->scripts([
        'https://cdn.example.com/library.min.js',
        '/js/admin.js',
    ]);
}
```

Local paths (starting with `/`) are resolved from the `public/` directory. CDN URLs are loaded directly.

## Customize Shopper theme

Shopper ships with a pre-compiled CSS file (`dist/shopper.css`) that works out of the box. However, if you want to customize the admin panel styles using Tailwind CSS, you can build your own theme by importing Shopper's `theme.css` file.

Shopper provides a dedicated `theme.css` entry point that handles all internal imports (Tailwind, Filament, Shopper base styles, source detection, and plugins) so you don't have to wire them up manually.

### Setting up Tailwind CSS for your project

Install Tailwind CSS v4 with the Vite plugin and the required plugins:

```bash theme={null}
npm install -D tailwindcss @tailwindcss/vite @tailwindcss/forms @tailwindcss/typography
```

Add the `@tailwindcss/vite` plugin to your `vite.config.js`:

```js theme={null}
import tailwindcss from '@tailwindcss/vite';

export default defineConfig({
    plugins: [
        tailwindcss(),
        laravel({
            input: [
                'resources/css/app.css',
                'resources/css/shopper.css', // [!code highlight]
            ],
        }),
    ],
});
```

### Creating the theme file

Create a `resources/css/shopper.css` file and import Shopper's theme entry point:

```css theme={null}
@import '../../vendor/shopper/framework/resources/css/theme.css';
```

The `theme.css` file already includes Tailwind, Filament CSS, Shopper base styles and components, source detection for all required Blade views, and the `@tailwindcss/forms` and `@tailwindcss/typography` plugins. You don't need to configure any of that yourself.

To add your own source paths (custom Livewire components, app modules, etc.) or custom styles, add them after the import:

```css theme={null}
@import '../../vendor/shopper/framework/resources/css/theme.css';

/* Additional source paths for your project */
@source '../../resources/views/**/*.blade.php';
@source '../../app-modules/**/resources/views/**/*.blade.php';

/* Your custom styles */
```

### Load theme

In your `AppServiceProvider` or any other provider, register the Vite theme in the `boot` method:

```php theme={null}
use Shopper\Facades\Shopper;

public function boot(): void
{
    Shopper::registerViteTheme('resources/css/shopper.css');
}
```

### Branding Logo

By default, the Shopper logo is used on the login page and in the sidebar header. You have several options to replace it with your own brand.

#### Via configuration

The simplest approach is to set the `brand` key in your `config/shopper/admin.php` configuration file with a path to your logo image:

```php theme={null}
/*
|--------------------------------------------------------------------------
| Brand Logo
|--------------------------------------------------------------------------
|
| This will be displayed on the login page and in the sidebar's header.
| This is your site's logo. It will be loaded directly from the public folder
| Ex: '/images/logo.svg'
|
*/

'brand' => 'images/logo.svg',
```

This will load the image using the Laravel `asset()` helper function, so the file should be in your `public/` directory.

#### Via service provider

For more control, use the `brandLogo()` method on the Shopper facade. You can pass an HTML string, a closure that returns a string, or a closure that returns a `View` instance:

```php theme={null}
use Illuminate\Support\Facades\Blade;
use Shopper\Facades\Shopper;

public function boot(): void
{
    // Simple image tag
    Shopper::brandLogo('<img class="size-8" src="/images/logo.svg" alt="My Store" />');

    // With a Blade component
    Shopper::brandLogo(fn (): string => Blade::render('<x-application-logo class="size-8" />'));

    // With a Blade view
    Shopper::brandLogo(fn (): \Illuminate\Contracts\View\View => view('brand.logo'));
}
```

This takes priority over the config value, giving you full flexibility to render any HTML, such as an inline SVG, a Blade component, a dedicated view, or anything else.

<Info>
  The priority order is: `Shopper::brandLogo()` > `config('shopper.admin.brand')` > default Shopper logo.
</Info>

## Render hooks

Shopper allows you to render Blade content at specific points in the admin panel. This is useful for integrating third-party packages that require Blade components in the layout, such as [Flux UI](https://fluxui.dev), [Wire Elements Modal](https://github.com/wire-elements/modal), or [Laravel Notify](https://github.com/mckenziearts/laravel-notify), and for addons that need to inject content into specific pages.

<Info>
  If you are familiar with [Filament's render hooks](https://filamentphp.com/docs/5.x/support/render-hooks), you already know how this works. Shopper's render hooks follow the same concept.
</Info>

Render hooks are organized into scoped classes by business domain under the `Shopper\View` namespace:

| Class                  | Scope                                                     |
| ---------------------- | --------------------------------------------------------- |
| `LayoutRenderHook`     | Head, body, header, content, dashboard, account, settings |
| `ProductRenderHook`    | Product index, edit, variant                              |
| `OrderRenderHook`      | Order index, detail, shipments, abandoned carts           |
| `CustomerRenderHook`   | Customer index, create, show                              |
| `CollectionRenderHook` | Collection index, edit                                    |
| `CatalogRenderHook`    | Categories, brands, tags, attributes, reviews             |
| `SalesRenderHook`      | Discounts, suppliers                                      |

### Registering a render hook

To register a render hook, call the `Shopper::renderHook()` method from a service provider's `boot` method. The first argument is the hook constant, and the second is a closure that returns HTML:

```php theme={null}
use Illuminate\Support\Facades\Blade;
use Shopper\Facades\Shopper;
use Shopper\View\LayoutRenderHook;

public function boot(): void
{
    Shopper::renderHook(
        LayoutRenderHook::BODY_END,
        fn (): string => Blade::render('@livewire("wire-elements-modal")'),
    );
}
```

Since all methods return the `ShopperPanel` instance, you can chain multiple calls:

```php theme={null}
use Illuminate\Support\Facades\Blade;
use Shopper\Facades\Shopper;
use Shopper\View\LayoutRenderHook;

public function boot(): void
{
    Shopper::renderHook(LayoutRenderHook::HEAD_END, fn (): string => Blade::render('@fluxStyles'))
        ->renderHook(LayoutRenderHook::BODY_END, fn (): string => Blade::render('<flux:modal />'))
        ->renderHook(LayoutRenderHook::BODY_END, fn (): string => Blade::render('<x-notify::notify />'))
        ->registerViteTheme('resources/css/shopper.css')
        ->styles(['vendor/custom/lib.css'])
        ->scripts(['vendor/custom/lib.js']);
}
```

You can also use hooks from different scoped classes to inject content into specific pages:

```php theme={null}
use Illuminate\Support\Facades\Blade;
use Shopper\Facades\Shopper;
use Shopper\View\ProductRenderHook;

Shopper::renderHook(
    ProductRenderHook::EDIT_HEADER_AFTER,
    fn (): string => Blade::render('<livewire:my-addon.product-banner />'),
);
```

### Available render hooks

#### Layout

| Constant               | Class              | Description                                                      |
| ---------------------- | ------------------ | ---------------------------------------------------------------- |
| `HEAD_START`           | `LayoutRenderHook` | After the meta tags, before Filament styles and Shopper theme    |
| `HEAD_END`             | `LayoutRenderHook` | End of `<head>`, after all styles and scripts                    |
| `BODY_START`           | `LayoutRenderHook` | Start of `<body>`, before any content                            |
| `BODY_END`             | `LayoutRenderHook` | End of `<body>`, after Filament scripts                          |
| `HEADER_START`         | `LayoutRenderHook` | Start of the header bar content area                             |
| `HEADER_END`           | `LayoutRenderHook` | End of the header bar, before the site link and account dropdown |
| `CONTENT_START`        | `LayoutRenderHook` | Start of the main content area, after the sidebar                |
| `CONTENT_END`          | `LayoutRenderHook` | End of the main content area                                     |
| `DASHBOARD_START`      | `LayoutRenderHook` | Start of the dashboard page                                      |
| `DASHBOARD_END`        | `LayoutRenderHook` | End of the dashboard page                                        |
| `ACCOUNT_START`        | `LayoutRenderHook` | Before the account page content                                  |
| `ACCOUNT_END`          | `LayoutRenderHook` | After the account page content                                   |
| `SETTINGS_INDEX_START` | `LayoutRenderHook` | Before the settings grid                                         |
| `SETTINGS_INDEX_END`   | `LayoutRenderHook` | After the settings grid                                          |

#### Products

| Constant                | Class               | Description                                 |
| ----------------------- | ------------------- | ------------------------------------------- |
| `INDEX_TABLE_BEFORE`    | `ProductRenderHook` | Before the products table                   |
| `INDEX_TABLE_AFTER`     | `ProductRenderHook` | After the products table                    |
| `EDIT_HEADER_AFTER`     | `ProductRenderHook` | After the product edit page heading         |
| `EDIT_TABS_BEFORE`      | `ProductRenderHook` | Before the product edit tabs                |
| `EDIT_TABS_END`         | `ProductRenderHook` | Before the closing of the product edit tabs |
| `EDIT_CONTENT_BEFORE`   | `ProductRenderHook` | Before the product edit tab content         |
| `EDIT_CONTENT_AFTER`    | `ProductRenderHook` | After the product edit tab content          |
| `VARIANT_HEADER_AFTER`  | `ProductRenderHook` | After the variant page heading              |
| `VARIANT_MAIN_AFTER`    | `ProductRenderHook` | After the variant main column               |
| `VARIANT_SIDEBAR_AFTER` | `ProductRenderHook` | After the variant sidebar column            |

#### Orders

| Constant                       | Class             | Description                          |
| ------------------------------ | ----------------- | ------------------------------------ |
| `INDEX_TABLE_BEFORE`           | `OrderRenderHook` | Before the orders table              |
| `INDEX_TABLE_AFTER`            | `OrderRenderHook` | After the orders table               |
| `DETAIL_HEADER_AFTER`          | `OrderRenderHook` | After the order detail sticky header |
| `DETAIL_MAIN_BEFORE`           | `OrderRenderHook` | Before the order detail main content |
| `DETAIL_MAIN_AFTER`            | `OrderRenderHook` | After the order detail main column   |
| `DETAIL_SIDEBAR_BEFORE`        | `OrderRenderHook` | Before the order detail sidebar      |
| `DETAIL_SIDEBAR_AFTER`         | `OrderRenderHook` | After the order detail sidebar       |
| `SHIPMENTS_TABLE_BEFORE`       | `OrderRenderHook` | Before the shipments table           |
| `SHIPMENTS_TABLE_AFTER`        | `OrderRenderHook` | After the shipments table            |
| `ABANDONED_CARTS_TABLE_BEFORE` | `OrderRenderHook` | Before the abandoned carts table     |
| `ABANDONED_CARTS_TABLE_AFTER`  | `OrderRenderHook` | After the abandoned carts table      |

#### Customers

| Constant              | Class                | Description                                  |
| --------------------- | -------------------- | -------------------------------------------- |
| `INDEX_TABLE_BEFORE`  | `CustomerRenderHook` | Before the customers table                   |
| `INDEX_TABLE_AFTER`   | `CustomerRenderHook` | After the customers table                    |
| `CREATE_FORM_BEFORE`  | `CustomerRenderHook` | Before the create customer form              |
| `CREATE_FORM_AFTER`   | `CustomerRenderHook` | After the create customer form               |
| `SHOW_HEADER_AFTER`   | `CustomerRenderHook` | After the customer show page header          |
| `SHOW_TABS_BEFORE`    | `CustomerRenderHook` | Before the customer show tabs                |
| `SHOW_TABS_END`       | `CustomerRenderHook` | Before the closing of the customer show tabs |
| `SHOW_CONTENT_BEFORE` | `CustomerRenderHook` | Before the customer show tab content         |
| `SHOW_CONTENT_AFTER`  | `CustomerRenderHook` | After the customer show tab content          |

#### Collections

| Constant             | Class                  | Description                     |
| -------------------- | ---------------------- | ------------------------------- |
| `INDEX_TABLE_BEFORE` | `CollectionRenderHook` | Before the collections table    |
| `INDEX_TABLE_AFTER`  | `CollectionRenderHook` | After the collections table     |
| `EDIT_FORM_BEFORE`   | `CollectionRenderHook` | Before the collection edit form |
| `EDIT_FORM_AFTER`    | `CollectionRenderHook` | After the collection edit form  |

#### Catalog

| Constant                  | Class               | Description                 |
| ------------------------- | ------------------- | --------------------------- |
| `CATEGORIES_TABLE_BEFORE` | `CatalogRenderHook` | Before the categories table |
| `CATEGORIES_TABLE_AFTER`  | `CatalogRenderHook` | After the categories table  |
| `BRANDS_TABLE_BEFORE`     | `CatalogRenderHook` | Before the brands table     |
| `BRANDS_TABLE_AFTER`      | `CatalogRenderHook` | After the brands table      |
| `TAGS_TABLE_BEFORE`       | `CatalogRenderHook` | Before the tags table       |
| `TAGS_TABLE_AFTER`        | `CatalogRenderHook` | After the tags table        |
| `ATTRIBUTES_TABLE_BEFORE` | `CatalogRenderHook` | Before the attributes table |
| `ATTRIBUTES_TABLE_AFTER`  | `CatalogRenderHook` | After the attributes table  |
| `REVIEWS_TABLE_BEFORE`    | `CatalogRenderHook` | Before the reviews table    |
| `REVIEWS_TABLE_AFTER`     | `CatalogRenderHook` | After the reviews table     |

#### Sales

| Constant                 | Class             | Description                |
| ------------------------ | ----------------- | -------------------------- |
| `DISCOUNTS_TABLE_BEFORE` | `SalesRenderHook` | Before the discounts table |
| `DISCOUNTS_TABLE_AFTER`  | `SalesRenderHook` | After the discounts table  |
| `SUPPLIERS_TABLE_BEFORE` | `SalesRenderHook` | Before the suppliers table |
| `SUPPLIERS_TABLE_AFTER`  | `SalesRenderHook` | After the suppliers table  |

### Example: Wire Elements Modal

```php theme={null}
use Illuminate\Support\Facades\Blade;
use Shopper\Facades\Shopper;
use Shopper\View\LayoutRenderHook;

Shopper::renderHook(
    LayoutRenderHook::BODY_END,
    fn (): string => Blade::render('@livewire("wire-elements-modal")'),
);
```

### Example: Flux UI

```php theme={null}
use Illuminate\Support\Facades\Blade;
use Shopper\Facades\Shopper;
use Shopper\View\LayoutRenderHook;

Shopper::renderHook(LayoutRenderHook::HEAD_END, fn (): string => Blade::render('@fluxStyles'))
    ->renderHook(LayoutRenderHook::BODY_END, fn (): string => Blade::render('@fluxScripts'));
```

### Example: Laravel Notify

```php theme={null}
use Illuminate\Support\Facades\Blade;
use Shopper\Facades\Shopper;
use Shopper\View\LayoutRenderHook;

Shopper::renderHook(
    LayoutRenderHook::BODY_END,
    fn (): string => Blade::render('<x-notify::notify />'),
);
```

## Adding control panel routes

If you need to have custom routes for the control panel:

1. Create a routes file. Name it whatever you want, for example: `routes/shopper.php`
2. Then add this to your `shopper/routes.php` file so that all routes are dynamically loaded:
   ```php theme={null}
    'custom_file' => base_path('routes/shopper.php'),
   ```
3. If you want to add middleware to further control access to the routes available in this file you can add in the key `middleware` of the `shopper/routes.php` file

   ```php theme={null}
   'middleware' => [
     'my-custom-middleware',
     'permission:can-access',
   ],
   ```
