extending-shopper

star 15

Provides patterns for extending Shopper with custom sidebar items, component overrides, event listeners, and domain features like stock, pricing, and media. Use when customizing Shopper behavior.

shopperlabs By shopperlabs schedule Updated 3/4/2026

name: extending-shopper description: Provides patterns for extending Shopper with custom sidebar items, component overrides, event listeners, and domain features like stock, pricing, and media. Use when customizing Shopper behavior.

Extending Shopper

Sidebar Navigation

Add Item to Existing Group

// app/Sidebar/ShippingSidebar.php
namespace App\Sidebar;

use Shopper\Sidebar\AbstractAdminSidebar;
use Shopper\Sidebar\Contracts\Builder\Group;
use Shopper\Sidebar\Contracts\Builder\Item;
use Shopper\Sidebar\Contracts\Builder\Menu;

class ShippingSidebar extends AbstractAdminSidebar
{
    public function extendWith(Menu $menu): Menu
    {
        $menu->group(__('shopper::layout.sidebar.catalog'), function (Group $group): void {
            $group->weight(2); // Same weight as CatalogSidebar to merge

            $group->item(__('Shipping'), function (Item $item): void {
                $item->weight(5);
                $item->useSpa();
                $item->route('shopper.shipping.index');
                $item->setIcon('untitledui-truck-01');
                $item->setAuthorized($this->user->hasPermissionTo('browse_shipping'));
            });
        });

        return $menu;
    }
}

Register in AppServiceProvider:

use Shopper\Sidebar\SidebarBuilder;

public function boot(): void
{
    $this->app['events']->listen(SidebarBuilder::class, ShippingSidebar::class);
}

Create New Sidebar Group

$menu->group(__('Logistics'), function (Group $group): void {
    $group->weight(5); // After CustomerSidebar (weight 4)
    $group->setAuthorized();

    $group->item(__('Shipping'), function (Item $item): void {
        $item->weight(1);
        $item->useSpa();
        $item->route('shopper.shipping.index');
        $item->setIcon('untitledui-truck-01');
    });
});

Default Sidebar Groups

Group Weight Class
Dashboard 1 DashboardSidebar
Catalog 2 CatalogSidebar
Sales 3 SalesSidebar
Customers 4 CustomerSidebar

Item Options

  • $item->weight(int) - Position (lower = higher)
  • $item->useSpa() - Enable wire:navigate
  • $item->route('name') - Set route
  • $item->setIcon('untitledui-*') - Icon
  • $item->setAuthorized(bool) - Visibility condition

Override Components

Config files in config/shopper/components/:

// config/shopper/components/product.php
return [
    'pages' => [
        'product-index' => App\Livewire\Shopper\Products\Index::class,
        'product-edit' => \Shopper\Livewire\Pages\Product\Edit::class,
    ],
    'components' => [
        'products.form.edit' => App\Livewire\Shopper\Products\EditForm::class,
    ],
];

Extend the base component:

namespace App\Livewire\Shopper\Products;

use Shopper\Livewire\Pages\Product\Index as BaseIndex;

class Index extends BaseIndex
{
    public function table(Table $table): Table
    {
        return parent::table($table)
            ->columns([
                ...parent::table($table)->getColumns(),
                TextColumn::make('custom_field'),
            ]);
    }
}

Events

Listen to Shopper events:

// EventServiceProvider
protected $listen = [
    \Shopper\Core\Events\Products\ProductCreated::class => [YourListener::class],
    \Shopper\Core\Events\Products\ProductUpdated::class => [],
    \Shopper\Core\Events\Products\ProductDeleted::class => [],
    \Shopper\Core\Events\Orders\OrderCreated::class => [SendOrderConfirmation::class],
    \Shopper\Core\Events\Orders\OrderCompleted::class => [],
    \Shopper\Core\Events\Orders\OrderPaid::class => [],
    \Shopper\Core\Events\Orders\OrderCancel::class => [],
];

Stock Management

use Shopper\Core\Models\Inventory;

$product = Product::query()->find($id);
$inventory = Inventory::query()->where('is_default', true)->first();

$product->setStock(100, $inventory->id);
$product->decreaseStock($inventory->id, 5);
$currentStock = $product->getStock();

Pricing

Amounts stored in cents, supports multi-currency:

use Shopper\Core\Models\Currency;

$product->prices()->create([
    'currency_id' => Currency::where('code', 'USD')->first()->id,
    'amount' => 2999,         // $29.99
    'compare_amount' => 3999, // $39.99 (crossed-out)
    'cost_amount' => 1500,    // $15.00 (cost)
]);

Media Management

// Add thumbnail
$product->addMedia($file)
    ->toMediaCollection(config('shopper.media.storage.thumbnail_collection'));

// Add gallery images
$product->addMedia($file)
    ->toMediaCollection(config('shopper.media.storage.collection_name'));

// Get URL
$thumbnail = $product->getFirstMediaUrl(config('shopper.media.storage.thumbnail_collection'));

Feature Flags

if (\Shopper\Feature::enabled('review')) {
    // Show reviews
}

Configure in config/shopper/features.php.

Helper Functions

Function Purpose
shopper_table('products') Prefixed table name (sh_products)
shopper_setting('shop_name') Get shop setting
shopper_fallback_url() Fallback image URL
generate_number() Order number with prefix
shopper()->auth()->user() Authenticated admin

Custom Routes

// config/shopper/routes.php
return [
    'custom_file' => base_path('routes/shopper.php'),
];

// routes/shopper.php
use App\Livewire\Shopper\Shipping;

Route::get('shipping', Shipping::class)->name('shopper.shipping.index');
Install via CLI
npx skills add https://github.com/shopperlabs/demo.laravelshopper.dev --skill extending-shopper
Repository Details
star Stars 15
call_split Forks 6
navigation Branch main
article Path SKILL.md
More from Creator