Skip to main content
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 located in the public/ directory.

Via configuration

You may register assets to be loaded in the Control Panel using the scripts and stylesheets keys of the resources in the config/shopper/admin.php config file. This will accept an array of links. You can load the links locally or using cdn. They will be automatically loaded in the control panel
'resources' => [
  'stylesheets' => [
      '/css/admin.css',
      'https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css',
    ],
  'scripts' => [
    'https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.min.js',
    '/js/admin.js',
  ],
],
These commands will make Shopper expect files at public/css/admin.css and public/js/admin.js respectively for local links.

Via service provider

You can also register additional stylesheets and scripts programmatically from any service provider using the fluent API:
use Shopper\Facades\Shopper;

public function boot(): void
{
    Shopper::styles([
        '/css/admin.css',
        'https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css',
    ])->scripts([
        'https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.min.js',
        '/js/admin.js',
    ]);
}
Both approaches can be used together. Assets registered via the config file and via the service provider are merged automatically.

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:
npm install -D tailwindcss @tailwindcss/vite @tailwindcss/forms @tailwindcss/typography
Add the @tailwindcss/vite plugin to your vite.config.js:
import tailwindcss from '@tailwindcss/vite';

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

Creating the theme file

Create a resources/css/shopper.css file and import Shopper’s theme entry point:
@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:
@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:
use Shopper\Facades\Shopper;

public function boot(): void
{
    Shopper::registerViteTheme('resources/css/shopper.css');
}
By default, the Laravel 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:
/*
|--------------------------------------------------------------------------
| 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:
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 — an inline SVG, a Blade component, a dedicated view, or anything else.
The priority order is: Shopper::brandLogo() > config('shopper.admin.brand') > default Shopper logo.

Render hooks

Shopper allows you to render Blade content at specific points in the admin panel layout. This is useful for integrating third-party packages that require Blade components in the layout, such as Flux UI, Wire Elements Modal, or Laravel Notify.
If you are familiar with Filament’s render hooks, you already know how this works. Shopper’s render hooks follow the same concept.

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 position (using the RenderHook enum), and the second is a closure that returns HTML:
use Illuminate\Support\Facades\Blade;
use Shopper\Enum\RenderHook;
use Shopper\Facades\Shopper;

public function boot(): void
{
    Shopper::renderHook(
        RenderHook::BodyEnd,
        fn (): string => Blade::render('@livewire("wire-elements-modal")'),
    );
}
Since all methods return the ShopperPanel instance, you can chain multiple calls:
use Illuminate\Support\Facades\Blade;
use Shopper\Enum\RenderHook;
use Shopper\Facades\Shopper;

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

Available render hooks

HookEnumDescription
Head StartRenderHook::HeadStartAfter the meta tags, before Filament styles and Shopper theme
Head EndRenderHook::HeadEndEnd of <head>, after all styles and scripts
Body StartRenderHook::BodyStartStart of <body>, before any content
Body EndRenderHook::BodyEndEnd of <body>, after Filament scripts
Content StartRenderHook::ContentStartStart of the main content area (inside the layout, after the sidebar)
Content EndRenderHook::ContentEndEnd of the main content area

Example: Wire Elements Modal

use Illuminate\Support\Facades\Blade;
use Shopper\Enum\RenderHook;
use Shopper\Facades\Shopper;

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

Example: Flux UI

use Illuminate\Support\Facades\Blade;
use Shopper\Enum\RenderHook;
use Shopper\Facades\Shopper;

Shopper::renderHook(RenderHook::HeadEnd, fn (): string => Blade::render('@fluxStyles'))
    ->renderHook(RenderHook::BodyEnd, fn (): string => Blade::render('@fluxScripts'));

Example: Laravel Notify

use Illuminate\Support\Facades\Blade;
use Shopper\Enum\RenderHook;
use Shopper\Facades\Shopper;

Shopper::renderHook(
    RenderHook::BodyEnd,
    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:
     '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
    'middleware' => [
      'my-custom-middleware',
      'permission:can-access',
    ],