toadfix / minishop
Drop-in ecommerce package for Laravel with Filament v5 admin panel and Sanctum API
Requires
- php: ^8.2
- barryvdh/laravel-dompdf: ^3.1
- filament/filament: ^5.0
- laravel/ai: ^0.2.8
- laravel/framework: ^13.0
- laravel/sanctum: ^4.0
- spatie/laravel-permission: ^7.2
- stripe/stripe-php: ^19.3
Requires (Dev)
- fakerphp/faker: ^1.23
- laravel/pint: ^1.24
- orchestra/testbench: ^11.0
- phpunit/phpunit: ^12.0
Suggests
- inertiajs/inertia-laravel: Required only if enabling the built-in Inertia storefront (MINISHOP_STOREFRONT=true)
- laravel/fortify: Recommended for web authentication (register/login flows)
This package is auto-updated.
Last update: 2026-07-17 13:29:00 UTC
README
A drop-in ecommerce package for Laravel 13 applications. Ships with a Filament v5 admin panel, a Livewire storefront, Stripe payments, a Sanctum REST API, and Canadian tax/shipping support.
Requirements
- PHP 8.3+
- Laravel 13
- Filament 5
Installation
composer require toadfix/minishop
Then run the install command:
php artisan minishop:install
This will:
- Publish the config file to
config/minishop.php - Publish and run the migrations (re-running the installer is safe — already-published migrations are skipped)
- Point
AUTH_MODELin your.envatMinishop\Models\User(the model carries the roles, Sanctum and Fortify traits the package needs) - Seed the required roles and permissions
- Optionally create an initial admin user with the
super-adminrole (pass--no-adminto skip)
The admin panel is available at /dashboard and authenticates with Filament's built-in login.
Local package development
If you are working on the package itself, scripts/fresh-host.sh scaffolds a
throwaway Laravel host app as a sibling directory, wires the package in via a
Composer path repository (symlinked, so edits are live) and runs the installer:
scripts/fresh-host.sh # creates ../minishop-app scripts/fresh-host.sh ../sandbox # or a directory of your choosing
Livewire storefront (optional)
The package ships a complete Livewire + Blade storefront (home, products, cart, checkout, and customer account). Publish it and enable the routes:
php artisan minishop:install --storefront
Add to your .env:
MINISHOP_STOREFRONT=true
The storefront is styled with Tailwind v4. Build the assets with your app's Vite setup:
npm install npm run build
minishop:install --storefront publishes:
resources/views/storefront/— the page views (override any of them freely).resources/css/storefront.css— the Tailwind entrypoint. Add it to yourvite.config.jsinputs (alongside your existing CSS) and reference it with@vite(['resources/css/storefront.css'])— the shipped layout already does.
Until the assets are built, the layout falls back to the Tailwind Play CDN so the storefront is usable out of the box.
Email verification. Customer accounts must confirm their email before
reaching the /account area — registration sends a verification link (via
Fortify's email-verification feature, enabled by default) and the package ships
the storefront notice page, so make sure your app has a working mail driver. The
admin user created by minishop:install is marked verified automatically.
Upgrading: publishing copies the storefront views into your app, and your copies take precedence over the package's. After upgrading the package, the shipped views may have changed — re-publish to pick up the new versions:
php artisan vendor:publish --tag=minishop-storefront --force
--forceoverwrites the published views, so re-apply any local edits afterwards (or diff before publishing). If you haven't customised the views, deletingresources/views/storefront/and re-publishing is the cleanest path.
Configuration
The published config file is at config/minishop.php. Key options:
| Key | Env variable | Default | Description |
|---|---|---|---|
load_storefront_routes |
MINISHOP_STOREFRONT |
false |
Enable the built-in storefront routes |
renderer |
MINISHOP_RENDERER |
blade |
Storefront renderer: blade (Livewire) or a custom FQCN |
default_payment_gateway |
MINISHOP_DEFAULT_GATEWAY |
stripe |
Default payment driver |
panel_path |
MINISHOP_PANEL_PATH |
dashboard |
URL path for the Filament admin panel |
image_disk |
MINISHOP_IMAGE_DISK |
public |
Filesystem disk for product images |
low_stock_notification_email |
MINISHOP_LOW_STOCK_EMAIL |
— | Email address for low-stock alerts |
analytics.ga4_measurement_id |
MINISHOP_GA4_ID |
— | Google Analytics 4 Measurement ID (e.g. G-XXXXXXX) |
Analytics
Set MINISHOP_GA4_ID to your Google Analytics 4 Measurement ID and the
storefront injects gtag.js on every page and fires a GA4 purchase event
on the order-confirmation page (with transaction_id, value, currency,
shipping, tax, and line items). Leave it unset to disable tracking
entirely. The snippets live in publishable Blade partials
(resources/views/vendor/minishop/analytics/*) if you need to customise them or
add another provider.
Product search
Product search (storefront, Livewire list, and the REST API) runs through
Laravel Scout. It defaults to Scout's portable
database engine — no external service required, works on SQLite/MySQL/
Postgres, and matches across product name, description, and SKU. To scale up,
set SCOUT_DRIVER (e.g. meilisearch or algolia) and configure that engine;
Minishop\Models\Product is already Searchable. Facet filters (category, tag,
price range, stock) are applied on top of the search results.
Environment Variables
Add these to your .env as needed:
# Storefront MINISHOP_STOREFRONT=true MINISHOP_RENDERER=blade # Stripe STRIPE_KEY=pk_live_... STRIPE_SECRET=sk_live_... STRIPE_WEBHOOK_SECRET=whsec_... # Canada Post (optional) CANADA_POST_USERNAME= CANADA_POST_PASSWORD= CANADA_POST_CUSTOMER_NUMBER= # Notifications MINISHOP_LOW_STOCK_EMAIL=store@example.com
Admin Panel
The Filament admin panel is available at /dashboard (or the path configured in MINISHOP_PANEL_PATH).
Available panels:
- Dashboard — analytics widgets: revenue (this month + 30-day chart, gated to admins), orders this month, new customers, a needs-attention counter, orders-by-status, latest orders, and low-stock products
- Products — full CRUD with options, variants, and images (managed inline as relation managers); bulk delete
- Categories & Tags — full CRUD
- Orders — lifecycle management, bulk status updates, and downloadable invoice PDFs (also emailed to the customer on confirmation)
- Customers — profiles and order history
- Coupons — percentage and fixed discounts with expiry and usage limits
- Shipping Methods — flat-rate and carrier-calculated methods
- Tax Zones — zone-based tax rates
- Returns — approve/reject/receive/refund workflow
- Users — admin user management with roles
- Settings — currency & locale, tax rate/mode, GST number, active payment gateway, inventory and promotion options
- Activity Log — admin action history
Roles included:
super-admin— full access (bypasses all permission checks)admin— full management of every resource, including usersmanager— products and orders (no deletes), returns processing, and read access to categories/customers/tax zones/tags; no user, coupon, shipping, or settings managementcustomer— assigned automatically on storefront registration; no admin access
Storefront Routes
When MINISHOP_STOREFRONT=true, the following routes are registered:
| Method | URI | Name |
|---|---|---|
| GET | / |
storefront.home |
| GET | /products |
storefront.products.index |
| GET | /products/{product} |
storefront.products.show |
| GET | /cart |
storefront.cart.show |
| GET | /checkout |
storefront.checkout.create |
| POST | /checkout |
storefront.checkout.store |
| GET | /order-confirmation/{order} |
storefront.order.confirmation |
| GET | /checkout/pay/{order} |
storefront.checkout.payment.show |
| POST | /checkout/pay/{order}/stripe |
storefront.checkout.payment.stripe |
| POST | /webhooks/stripe |
webhooks.stripe |
| POST | /webhooks/{gateway} |
webhooks.gateway |
The cart is also driven by JSON sub-routes (storefront.cart.items.store,
.items.update, .items.destroy, .clear, .sync) used by the Livewire
components.
Account routes (require authentication):
| Method | URI | Name |
|---|---|---|
| GET | /account |
account.dashboard |
| GET | /account/orders |
account.orders.index |
| GET | /account/orders/{order} |
account.orders.show |
| GET | /account/address |
account.address.edit |
| PUT | /account/address |
account.address.update |
| GET | /account/payment |
account.payment.index |
REST API
The Sanctum API is available at /api/v1/. Catalog and cart endpoints are public; orders and the authenticated-user endpoint require a bearer token.
Authentication:
POST /api/v1/auth/register POST /api/v1/auth/login POST /api/v1/auth/logout GET /api/v1/user
Catalog (public):
GET /api/v1/products GET /api/v1/products/{product} GET /api/v1/categories GET /api/v1/categories/{category} POST /api/v1/coupons/validate
Product responses include each image's resolved url (and raw path).
Cart:
GET /api/v1/cart POST /api/v1/cart/items PATCH /api/v1/cart/items/{cartItem} DELETE /api/v1/cart/items/{cartItem}
Orders (require a bearer token):
GET /api/v1/orders GET /api/v1/orders/{order}
Example — register and fetch orders:
# Register curl -X POST http://your-app.test/api/v1/auth/register \ -H "Content-Type: application/json" \ -d '{"name":"Jane","email":"jane@example.com","password":"secret","password_confirmation":"secret"}' # Fetch orders (use the token from the register response) curl http://your-app.test/api/v1/orders \ -H "Authorization: Bearer YOUR_TOKEN"
Payment Gateways
Minishop uses a driver-based payment system. The built-in drivers are stripe and cod (cash on delivery / no payment step).
Stripe setup
- Add your Stripe keys to
.env(see Environment Variables above). - Set
MINISHOP_DEFAULT_GATEWAY=stripeor select Stripe in the admin Settings panel. - Configure the webhook in the Stripe Dashboard to point at
https://your-app.test/webhooks/stripewith thepayment_intent.succeededevent.
Adding a custom gateway
Implement Minishop\Payments\Contracts\PaymentGatewayContract and register the driver in a service provider:
use Minishop\Payments\Facades\Payment as MinishopPayment; MinishopPayment::extend('paypal', function ($app) { return new PayPalGateway(config('services.paypal')); });
The contract requires:
interface PaymentGatewayContract { public function name(): string; public function initiate(Order $order, Request $request): JsonResponse|RedirectResponse; public function handleWebhook(Request $request): Response; public function requiresPaymentStep(): bool; }
Queues & background work
Minishop pushes outbound work onto the queue rather than blocking the request:
- Order confirmation emails — sent after checkout (COD) and after the Stripe webhook confirms payment. These render the invoice PDF as an attachment, which is CPU work you don't want inline in a webhook.
- Order status emails — sent when an order moves to shipped/delivered/ cancelled/refunded.
- Low-stock alerts — a queued notification to
MINISHOP_LOW_STOCK_EMAIL.
Out of the box
With Laravel's default QUEUE_CONNECTION=sync, these run inline — everything
works with no worker, but checkout and webhook responses pay the cost of sending
mail and rendering PDFs. Fine for local/dev and low volume.
In production
Use a real queue and run a worker:
QUEUE_CONNECTION=database # or redis
php artisan migrate # creates the jobs / failed_jobs tables
php artisan queue:work --tries=3 --max-time=3600
Run the worker under a process supervisor (Supervisor, systemd, or your
platform's worker process) so it restarts on failure and after deploys
(php artisan queue:restart). Inspect and replay failures with
php artisan queue:failed and php artisan queue:retry all.
Gotcha: on a non-
syncconnection, emails only send when a worker is running. If order confirmations aren't arriving in production, check that aqueue:workprocess is up. Stripe webhook handling is idempotent (deduped on the Stripe event id), so retried deliveries never double-send.
Custom Storefront Renderer
To use your own frontend (Inertia, a custom Blade theme, an SPA API bridge,
etc.) instead of the built-in Livewire storefront, implement
Minishop\Rendering\StorefrontRendererContract and set the FQCN in your config:
MINISHOP_RENDERER=App\Rendering\CustomRenderer
use Minishop\Rendering\StorefrontRendererContract; class CustomRenderer implements StorefrontRendererContract { public function render(string $view, array $data = []): mixed { // $view is a slash-separated path, e.g. 'storefront/Products/Index'. // Map it to your own templates, an Inertia page, an API payload, etc. return view('shop.'.str_replace('/', '.', strtolower($view)), $data); } }
The package no longer ships an Inertia renderer — the default (and only built-in) renderer is
blade, which powers the Livewire storefront. A custom renderer is only needed if you are replacing that frontend entirely.
Seeders
To seed demo data (roles, categories, products, shipping methods, Canadian tax zones, coupons, sample orders), run the package's aggregate seeder — php artisan db:seed alone runs your host app's DatabaseSeeder, not Minishop's:
php artisan db:seed --class="Minishop\Database\Seeders\MinishopSeeder"
To seed only roles and permissions (required for the app to function):
php artisan db:seed --class="Minishop\Database\Seeders\RoleAndPermissionSeeder"
Testing
The package ships with a full PHPUnit test suite. To run it from the package directory:
composer install vendor/bin/phpunit
Or via the host app:
php artisan test --compact
License
MIT