Magento Multi-Store Setup Guide
One codebase, multiple brands β set up correctly, including the parts every other tutorial gets wrong.
What "multi-store" actually means
Magento can run several independent storefronts β different brands, domains, even catalogues β from a single codebase and database. The piece that trips people up is the hierarchy. Get these three levels straight and the rest is mechanical:
| Level | Represents | Has its own⦠|
|---|---|---|
| Website | A brand / domain boundary | Domain, customer base, pricing, payment scope |
| Store (group) | A catalogue boundary within a website | Root category (product tree) |
| Store View | A presentation of a store | Language, theme, base URLs |
Why most tutorials are wrong: scopes live in the database
Older guides tell you to edit app/etc/config.php or env.php to add
stores. On current Magento that's incorrect β websites, stores and store views are
database records, created in the admin (or programmatically). config.php
only records which modules are enabled. If a tutorial has you hand-editing config files to
add a website, it predates 2.4 and will lead you astray.
setup:install or log into the admin without two-factor
authentication configured. Plan for it up front β it is not optional and cannot be skipped on a real install.Step-by-step admin setup
All four steps live under Stores → Settings → All Stores.
1. Create the website
- Create Website. Give it a Name and a Code β lowercase, no spaces,
no leading digit (e.g.
brand_a). Remember this code: it goes into your web-server config verbatim.
2. Create the store (group)
- Create Store. Choose the website, give it a Name, a Code, and a Root Category.
code to store groups. Leave it blank and the
save fails with a baffling, field-less message:
Magento\Framework\Validator\Exception:
Invalid type given. String, integer or float expected
It names no field β but it's the empty store-group code failing validation. Older 2.2/2.3 tutorials
never mention this column, which is why this one error wastes so many hours. Always set a code (e.g. brand_a_store).3. Create the store view
- Create Store View. Choose the store, give it a Name, a Code, set Status = Enabled. The view is what carries language, theme and URLs.
4. Set per-website base URLs
- Go to Stores → Configuration, switch the scope selector (top-left) to the target website, then Web → Base URLs (and Base URLs (Secure)).
- Untick "Use Default Value" and set the secure base URL to that brand's domain, e.g.
https://brand-a.example/.
5. Assign a theme per store view
- Content → Design → Configuration, edit the target store view's row, set its theme. Themes are assigned per store view, so each brand can look completely different.
Wiring your web server (nginx)
The web server has to tell Magento which website a request belongs to, using the
MAGE_RUN_CODE variable. Map each hostname to its website code:
map $http_host $MAGE_RUN_CODE {
default "";
brand-a.example "brand_a";
www.brand-a.example "brand_a";
brand-b.example "brand_b";
www.brand-b.example "brand_b";
}
map $http_host $MAGE_RUN_TYPE {
default "website";
}
Then β and this is the part guides omit β the two variables must be forwarded to
PHP in the location block that runs index.php / static.php:
location ~ ^/(index|get|static|errors/report|health_check)\.php$ {
fastcgi_param MAGE_RUN_CODE $MAGE_RUN_CODE; # required
fastcgi_param MAGE_RUN_TYPE $MAGE_RUN_TYPE; # required
# ...your usual fastcgi_pass + params...
}
fastcgi_param lines. Magento silently falls back to the default
website and every domain shows the same storefront. If all your brands look identical, check this first.Validate and reload: nginx -t && nginx -s reload. The host on the left of the
map must match the website code on the right exactly.
DNS, SSL & testing before cutover
- Each brand domain needs its own TLS certificate. Issue one per domain (e.g. via Let's Encrypt) before going live.
- Test a domain before you point its DNS, by resolving it to your server manually:
Acurl -sk --resolve brand-a.example:443:YOUR.SERVER.IP \ -o /dev/null -w "%{http_code}\n" https://brand-a.example/200or302means the store resolves correctly. A500usually means a website-code mismatch β check the exception log.
Common failures & fixes
| Symptom | Cause & fix |
|---|---|
| "The website with code X wasn't found" | The nginx MAGE_RUN_CODE doesn't match the website code in admin. Make them identical. |
| All domains show the same store | The two fastcgi_param lines are missing from the PHP location block. |
| Store view doesn't appear after creating it | Flush the cache: bin/magento cache:flush. |
| "Invalid type given. String, integer or float expected" | The store-group code is empty. Set a unique code. |
| New store view is unstyled / 404s on assets | Deploy static content for it: bin/magento setup:static-content:deploy <locales>. |
Go-live checklist
- Website, store group (with code!), and store view created for each brand.
- Per-website secure base URLs set, with the scope selector switched correctly.
- Theme assigned per store view.
- nginx host →
MAGE_RUN_CODEmap complete (apex and www), and the twofastcgi_paramlines present. - A TLS certificate issued per domain.
- Each domain tested with
--resolveand returning 200/302 before DNS cutover. bin/magento cache:flushandindexer:reindexrun; static content deployed for all locales.
Changelog
This guide is itself a Lurox page. Every revision is a published version in Lurox's built-in content history, so we can roll any guide back to an earlier version at any time. The public changelog below tracks what changed and when.
30 June 2026
- Initial publication. Covers the website/store/view hierarchy, the database-first model, the required store-group code, the nginx MAGE_RUN_CODE wiring, and a go-live checklist. Verified against Magento 2.4.8.
Explore the rest of the library
Browse every Cabbage Patch Studios user guide in one place.
All User Guides