Developer Architecture and Multi-tenancy

Architecture and Multi-tenancy

One Laravix installation runs any number of websites. This article explains the multi-tenant architecture — how sites are resolved, how users and permissions relate to sites, and how a request travels from the browser to a rendered page.

Sites are tenants

The central model is Laravix\Cms\Models\Site. Every site owns its content, media, taxonomies, settings, navigation, users (through roles) and API tokens — nothing leaks between sites. In the Filament admin panel the site is the tenant: URLs look like /admin/{siteId}/... and the tenant switcher in the top-left corner moves between sites.

A site has:

  • name — the public name,
  • domain — the hostname the site answers on (unique across the installation),
  • modetheme (Laravix renders the frontend with Blade) or headless (content is served via the REST API),
  • theme — the active theme directory name (theme mode),
  • locales — additional content languages beyond the default one.

Domain-based resolution

The frontend resolves the site from the request's hostname: SiteResolver matches $request->getHost() against the domain column. No match means 404. This is why the site's domain field must exactly equal the address visitors use — including on localhost.

For multiple sites, point all domains at the same application (same document root) and create one site per domain in the admin.

Tip: With multiple production domains, set CMS_CROSS_DOMAIN_SWITCHER=true so the admin's tenant switcher links to each site's own domain (https://{domain}/admin/{id}).

Users, roles and the super admin

Users are global; access is granted per site through the site_user pivot with a role:

Role Capabilities on the site
viewer Read-only access to content, media, taxonomies
editor Create and update content, media, taxonomies
admin Everything, including deleting, settings, navigation, users, activity log, bin

A user with is_super_admin = true bypasses all of this: access to every site, plus super-admin-only areas — the Sites resource and site registration. Enforcement lives in policies (ContentPolicy, MediaPolicy, TaxonomyPolicy) and canViewAny() overrides on the management resources.

Users can log into the panel if they are super admins or members of at least one site.

Request lifecycle (theme mode)

  1. HandleRedirects middleware checks the path against the redirect table and issues a 301/302/307/308 if matched.
  2. Plugin routes registered through RouteRegistry run next (e.g. /docs/...), then the CMS catch-all route cms.show takes everything else.
  3. CmsController resolves the site by hostname, detects a locale prefix in the URL, and loads the published content by slug (ContentResolver).
  4. PageDataBuilder assembles everything the view needs — settings, navigation, media map, translations, hydrated builder HTML.
  5. The view themes.{theme}::{type}.show renders (fallback: themes.{theme}::default).

In headless mode the frontend is disabled (404) and content is served from /api/v1 instead — see Headless Mode and API Tokens.

Extension surface

Laravix is extended through registries — static registration points for content types, fields, blocks, settings, taxonomies, navigations, routes and Filament plugins. Your application's App\Filament\Resources, Pages and Widgets are discovered into the panel automatically, and App\Providers\Filament\AdminPanelProvider extends Laravix\Cms\Filament\BaseAdminPanelProvider, so you can also override panel configuration. Details in Plugin Development and Registries Reference.

Background machinery

  • Schedulerlaravix:publish-scheduled runs every minute to publish scheduled content.
  • Queue — image variants are generated by the queued GenerateImageVariants job.
  • EventsContentCreated, ContentUpdated, ContentPublished, ContentUnpublished, ContentDeleted, ContentRestored (namespace Laravix\Cms\Events) let you hook into the content lifecycle.
  • Audit — changes are recorded per site via the activity log; soft-deleted records land in the recoverable Bin.
Laravix Documentation · 14.07.2026
Star on GitHub