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.
- PHP 8.3+
- Laravel 13
- Filament 5
composer require toadfix/minishopThen run the install command:
php artisan minishop:installThis 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.
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 choosingThe package ships a complete Livewire + Blade storefront (home, products, cart, checkout, and customer account). Publish it and enable the routes:
php artisan minishop:install --storefrontAdd to your .env:
MINISHOP_STOREFRONT=trueThe storefront is styled with Tailwind v4. Build the assets with your app's Vite setup:
npm install
npm run buildminishop: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.
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) |
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 (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.
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.comThe 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
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 |
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/userCatalog (public):
GET /api/v1/products
GET /api/v1/products/{product}
GET /api/v1/categories
GET /api/v1/categories/{category}
POST /api/v1/coupons/validateProduct 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"Minishop uses a driver-based payment system. The built-in drivers are stripe and cod (cash on delivery / no payment step).
- 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.
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;
}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.
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.
Use a real queue and run a worker:
QUEUE_CONNECTION=database # or redisphp artisan migrate # creates the jobs / failed_jobs tables
php artisan queue:work --tries=3 --max-time=3600Run 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.
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\CustomRendereruse 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.
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"The package ships with a full PHPUnit test suite. To run it from the package directory:
composer install
vendor/bin/phpunitOr via the host app:
php artisan test --compactMIT