Shopper Documentation
repository·2.x·Indexed 22 days ago
https://github.com/shopperlabs/shopperA headless e-commerce admin panel built for Laravel 11.0+. Shopper provides a robust foundation for online store applications, featuring a customizable sidebar system with Livewire and Blade components, an Alpine.js state management store, and TypeScript definitions via the @shopper/types package derived from its OpenAPI Spec.
What's inside Shopper
- Shopper is a Headless e-commerce Admin Panel built for Laravel 11.0+. It provides the necessary components to build an online store application, using Laravel as its foundation PHP framework.
How the Swappable Model Pattern works
2.xShopper uses a 'Swappable Model Pattern' that allows you to replace core Shopper models with your own custom implementations. This is achieved by mapping model keys to specific classes in the
config/shopper/models.phpconfiguration file.To ensure compatibility when swapping models, your custom model must:
- Implement the corresponding Shopper Model Contract.
- Use the
HasModelContracttrait. - Implement the
configuredClass()method to return the configured class from the config file.
When you swap a model, all internal Shopper logic that resolves models via the contract or the
configuredClass()method will automatically use your custom implementation.Understand the Shopper Monorepo Structure
2.xThe Shopper project is organized into several specialized packages. Knowing which namespace to use is critical for locating logic and defining new components:
Package Namespace Purpose packages/adminShopper\Livewire components, views, routes, assets packages/coreShopper\Core\Models, Actions, Enums, Contracts packages/cartShopper\Cart\Cart management, pipeline-based calculation packages/paymentShopper\Payment\Payment processing, driver architecture packages/shippingShopper\Shipping\Shipping providers, driver architecture packages/sidebarShopper\Sidebar\Sidebar navigation builder packages/stripeShopper\Stripe\Stripe payment driver packages/types— TypeScript type definitions (NPM package) packages/upgrade— Upgrade utilities Configure Sidebar CSS Variables and Attributes
2.xUse the provided helper functions to inject sidebar dimensions and attributes into your layout's CSS and HTML. This ensures your custom CSS stays in sync with the package configuration.
<style> :root { --sidebar-width: {{ \Shopper\Sidebar\sidebar_width() }}; --sidebar-collapsed-width: {{ \Shopper\Sidebar\sidebar_collapsed_width() }}; } </style> <body data-sidebar-breakpoint="{{ \Shopper\Sidebar\sidebar_breakpoint() }}" data-sidebar-collapsible="{{ \Shopper\Sidebar\sidebar_is_collapsible() ? 'true' : 'false' }}" >Create a custom Swappable Model
2.xTo create a custom model that replaces a core Shopper model, follow these three steps:
1. Implement the Contract
Your model must implement the interface defined in
Shopper\Core\Models\Contracts\{ModelName}.2. Define the Model Class
Create your class, implement the contract, and include the following:
- Use the
HasModelContracttrait. - Implement
configuredClass()to return the value fromconfig('shopper.models.{key}', static::class). - Use
config()for all relationships to ensure they point to the swapped models.
3. Register the Model
Add your new class to
config/shopper/models.phpusing the appropriate key.Example Implementation:
/** * @property-read int $id * @property-read string $name * @property-read bool $is_default */ class Warehouse extends Model implements WarehouseContract { use HasFactory; use HasModelContract; protected $guarded = []; public static function configuredClass(): string { return config('shopper.models.warehouse', static::class); } public function getTable(): string { return shopper_table('warehouses'); } public function isDefault(): bool { return $this->is_default; } /** * @return HasMany<Inventory, $this> */ public function inventories(): HasMany { return $this->hasMany(config('shopper.models.inventory'), 'warehouse_id'); } protected function casts(): array { return ['is_default' => 'boolean']; } }Registration in
config/shopper/models.php:'warehouse' => Models\Warehouse::class,// 1. Contract interface Warehouse { public function isDefault(): bool; public function inventories(): HasMany; } // 2. Model class Warehouse extends Model implements WarehouseContract { use HasFactory; use HasModelContract; protected $guarded = []; public static function configuredClass(): string { return config('shopper.models.warehouse', static::class); } public function getTable(): string { return shopper_table('warehouses'); } public function isDefault(): bool { return $this->is_default; } /** * @return HasMany<Inventory, $this> */ public function inventories(): HasMany { return $this->hasMany(config('shopper.models.inventory'), 'warehouse_id'); } protected function casts(): array { return ['is_default' => 'boolean']; } } // 3. Register 'warehouse' => Models\Warehouse::class,- Use the
Install Shopper via Composer and Artisan
2.xTo install the Shopper framework in your Laravel project, first require the package via Composer, then run the Shopper installation command using Artisan. This setup is designed for Laravel 11.0+.
composer require shopper/frameworkphp artisan shopper:installCreate Migrations using Shopper Helpers
2.xWhen creating migrations, extend
\Shopper\Core\Helpers\Migration. This provides helper methods to add common Shopper fields like SEO, shipping, and soft deletes automatically.return new class extends \Shopper\Core\Helpers\Migration { public function up(): void { Schema::create($this->getTableName('products'), function (Blueprint $table): void { $this->addCommonFields($table, hasSoftDelete: true); $this->addSeoFields($table); $this->addShippingFields($table); $this->addForeignKey($table, 'brand_id', $this->getTableName('brands')); }); } };Install Laravel Sidebar
2.xTo install the Laravel Sidebar package, require it via Composer, publish the configuration, and register the necessary middleware to resolve sidebars in your application.Override Shopper components and pages
2.xYou can override core Shopper pages or specific components by defining custom Livewire classes in configuration files located in
config/shopper/components/.1. Configure Overrides: Create a file like
config/shopper/components/product.phpto map core keys to your custom classes.2. Extend Base Components: When creating your custom class, extend the original Shopper Livewire component to maintain core functionality while adding your own logic (e.g., adding columns to a table).
// 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, ], ];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'), ]); } }Register Sidebar Middleware
2.xYou must add the
\Shopper\Sidebar\Middleware\ResolveSidebars::classmiddleware to your route group or global middleware stack to ensure sidebars are correctly resolved during the request lifecycle.// In a route group Route::middleware(['web', \Shopper\Sidebar\Middleware\ResolveSidebars::class]) ->group(function () { // Your routes }); // Or in bootstrap/app.php (Laravel 11+) ->withMiddleware(function (Middleware $middleware) { $middleware->appendToGroup('web', [ \Shopper\Sidebar\Middleware\ResolveSidebars::class, ]); })Render Sidebar using Livewire or Blade
2.xThe package offers two rendering strategies:
- Livewire Component: A ready-to-use component with built-in Alpine.js state management, collapse functionality, and LocalStorage persistence.
- Blade Components: For full layout control, use the
SidebarRendererdirectly in your views. The rendered HTML is available via the$sidebarvariable.
{{-- Option 1: Livewire Component --}} @livewire('sidebar', [ 'sidebarClass' => \App\Sidebar\AdminSidebar::class, 'class' => 'your-sidebar-classes', 'collapsible' => true, ]) {{-- Option 2: Blade Components --}} <aside class="sidebar" x-bind:class="{ 'sidebar-collapsed': $store.sidebar.isCollapsed }" > <nav class="sidebar-nav"> {!! $sidebar !!} </nav> </aside>Resolve Shopper models via Contracts or Classes
2.xWhen interacting with Shopper models, you can resolve them using their Contract (recommended for compatibility with the swapping pattern) or via the class name (where static calls are proxied to the configured class).
Recommended approach (via Contract): Use the
resolve()helper with the Contract interface to ensure you are always working with the currently configured model implementation.Alternative approach (via Class): You can call static methods directly on the base class, as these calls are proxied to the configured class.
use Shopper\Core\Models\Contracts\Product as ProductContract; // Via contract (recommended) resolve(ProductContract::class)::query()->get(); // Via class (static calls are proxied) Product::query()->get(); Product::find(1);