Kay Smits
All projects

Multisite e-commerce

One codebase serving several shops, each with its own catalogue and payment settings.

Role
Platform and deployment
Period
2026
Stack
  • Laravel
  • Inertia
  • React
  • Docker
  • Coolify

A shop platform where a store is not a copy of the code but a row in the database. Adding a shop takes no deploy.

The commerce platform sign-in screen

The tenant is the very first migration

The tenants table carries timestamp 0000, ahead of users. Every model after it therefore has a tenant column, with a global scope underneath that filters every query. Forgetting to filter is not possible: you have to say explicitly that you are switching the scope off, and only the superadmin does. With no active tenant it does not filter, so the console and seeders keep working.

Three ways in, three ways to resolve the tenant

A tenant admin is recognised by their account, a superadmin picks a tenant to look into, and a storefront identifies itself with an API key. Each path has its own middleware, and only after that is the tenant fixed. Deliberately not one clever function covering every case: that one would have needed an exception by the fourth case.

Headless: the shop is separate from the platform

The public API serves settings, products, categories, menus, pages, cart, shipping methods, payment methods, discounts, forms and checkout. Every endpoint has its own limit, from twelve checkout attempts a minute up to sixty cart lines. That makes the storefront a separate thing: aussieboots.lich.software is a static site that only calls that API.

Payments behind one contract

Charging, handling the webhook and refunding sit in one interface, with Mollie, Stripe and manual as implementations. Which provider a shop uses, and with which keys, lives per tenant in the database rather than in environment variables. A new shop with its own Mollie account therefore costs no deploy, and an extra provider is one class.

Tested through the browser

Alongside unit and feature tests in PHPUnit there is a Playwright suite that actually clicks through the admin. That pairing exists for a reason: a colour sitting correctly in the database does not prove the right shop displays it. The end-to-end tests check exactly that, with two shops side by side and a superadmin switching between them.

What a shop owner can do alone

Products with variants and option types, categories, stock, discounts and sales, shipping methods, orders with status history, customers, invoices, and the storefront itself: pages with blocks, menus per location, media and forms. Above that the superadmin sees only tenants and platform users.

What is in it

  • Multi-tenant One codebase, a shop is a row; separation through a global scope on every query.
  • Public storefront API Fourteen endpoints behind an API key, each with its own rate limit.
  • Products Variants, option types and values, images, categories.
  • Ordering Token-based cart, checkout, orders with status history and invoices.
  • Payments Mollie, Stripe and manual behind one contract, with webhooks and refunds.
  • Discounts Codes with a preview calculation up front, plus per-product sales.
  • CMS alongside Pages with blocks, menus per location, media and forms with reCAPTCHA.
  • Access Two-factor, email verification and a separate superadmin role.
  • Tests PHPUnit for unit and feature, Playwright driving the admin in a real browser.

Screens