microweber-packages / microweber-multisite
Per-domain multisite isolation for Laravel/Microweber: resolves a site from the Host header (config/<domain>/microweber.php marker) and gives each site its own database, cache, session and media/userfiles tree on one shared codebase.
Package info
github.com/microweber-packages/microweber-multisite
pkg:composer/microweber-packages/microweber-multisite
Requires
Requires (Dev)
- orchestra/testbench: ^8.0|^9.0|^10.0
- phpunit/phpunit: ^10.0|^11.0
Suggests
None
Provides
None
Conflicts
None
Replaces
- microweber-packages/multisite: *
This package is auto-updated.
Last update: 2026-11-03 17:07:16 UTC
README
Per-domain multisite isolation for Laravel — and Microweber on top of it. One
checkout of the codebase serves N independent sites, each with its own
database, cache, session and media/userfiles tree, selected by the incoming
Host: header.
It is a standalone Laravel package (Spatie package-tools) and can be used in a
clean Laravel app as a proof of concept. It depends on
microweber-packages/microweber-env-writer to write each
domain's .env.<domain> credentials.
How a site is resolved
A site is a domain that has a marker file:
config/
├── domain-a.com/microweber.php ← marker (presence is the signal)
├── domain-b.com/microweber.php
└── app.php ← normal shared config
On each request MultisiteManager::detectSiteFromHost() takes the host,
strips www. and :port, lowercases it, rejects path traversal, and returns
the domain iff config/<domain>/microweber.php exists. No match → the app
runs as the default single site (zero behaviour change).
This is the same contract Laravel's environment detector already uses, so the
domain also becomes the Laravel environment and loads .env.<domain> (hence
the per-site database).
What gets isolated
| Subsystem | Default site | domain-a.com |
|---|---|---|
| Database | .env |
.env.domain-a.com (own DB_DATABASE) |
| Cache prefix | config('cache.prefix') |
mw_domain_a_com_cache |
| Cache files | storage/framework/cache |
storage/framework/cache/sites/domain-a.com |
| Session cookie | laravel_session |
mw_domain_a_com_session |
| Session files | storage/framework/sessions |
storage/framework/sessions/sites/domain-a.com |
| Userfiles / media | storage/app/public/userfiles |
storage/app/public/userfiles/domain-a.com |
Each subsystem is toggleable in config/multisite.php (isolate.*).
Two integration seams
-
Autoload-time constant (
src/helpers.php, registered via composerautoload.files). Runs before any service provider and before Microweber'sbootstrap.php, definingMW_USERFILES_FOLDER_NAME = "userfiles/<site>"so everyuserfiles_path()/media_base_path()call is already per-site. In a clean Laravel app this define is simply inert. -
Service provider (
MultisiteServiceProvider) applies the cache/session (and optional public-disk) config overrides duringregister(), before those managers are first resolved.
Clean-Laravel PoC
composer require microweber-packages/microweber-multisite
Enable media isolation without Microweber by turning on the public disk in
config/multisite.php:
'isolate' => ['cache' => true, 'session' => true, 'userfiles' => false, 'public_disk' => true],
Create config/<domain>/microweber.php (any file returning []), point the
domain at the app, and the Storage::disk('public'), cache and session for that
host are transparently namespaced.
Commands
php artisan multisite:list # show configured sites + their isolated paths
php artisan multisite:install domain-a.com \
--db-name=mw_domain_a --db-username=root --db-password=secret
multisite:install creates the config/<domain>/microweber.php marker and
writes .env.<domain> (via env-writer) with the DB credentials, cache prefix
and session cookie. Then create the DB and point DNS at the server.
Per-domain env folder (optional)
By default the .env.<domain> files sit in the application root next to .env.
To collect them in a folder instead, set env_path (config multisite.env_path):
# a sibling "envs/" folder (relative to the app base), or an absolute path export MULTISITE_ENV_PATH=envs php artisan multisite:install domain-a.com --db-name=mw_domain_a ... # → writes envs/.env.domain-a.com; booted from there automatically
- The value must be a real OS environment variable (web-server
SetEnv, dockerenvironment:, or a shellexport) — not a line in.env. It decides which env file to load, so it has to be known before any.envis read, exactly likeAPP_ENV. - Relative values resolve against the app base; absolute values are used as-is.
Keep the folder outside the
public/docroot (the app root and a siblingenvs/both are). - A relocated
.env.<domain>is a partial overlay: the shared base.envstill fills the keys it omits (APP_KEY,MAIL_*, …) while the per-site values win. - Leave it unset (the default) to keep every env file in the root — unchanged behaviour.
API
use MicroweberPackages\Multisite\Facades\Multisite; Multisite::current(); // 'domain-a.com' | null (default site) Multisite::isActive(); // bool Multisite::sites(); // ['domain-a.com', 'domain-b.com'] Multisite::userfilesFolder(); // 'userfiles/domain-a.com' Multisite::cachePrefix(); // 'mw_domain_a_com_cache' Multisite::envDir(); // '/app/envs' | null (root layout) Multisite::siteEnvFile('domain-a.com'); // absolute path to its .env.<domain>
Tests
cd packages/microweber-multisite && composer install && composer test # or from the project root: php vendor/bin/phpunit packages/microweber-multisite/tests