Magento Multi-Store Setup
Overview
Magento's multi-store architecture has three levels: Website → Store → Store View. A Website groups stores with a shared customer base and order flow. A Store (under a Website) has its own root category and URL structure. Store Views (under a Store) typically represent languages or locales. Configuration values can be set at Global, Website, or Store View scope — lower scopes override higher ones. Adobe Commerce (B2B) adds Shared Catalogs for per-company product/price visibility control.
When to Use This Skill
- When running multiple brands or country-specific storefronts from a single Magento installation
- When setting different base currencies, tax configurations, or payment methods per website
- When creating a B2B portal alongside a B2C store with different product visibility
- When implementing localized store views for multiple languages under the same product catalog
- When configuring separate checkout flows, shipping methods, or payment gateways per website
- When managing shared product catalog with website-specific pricing and visibility overrides
Core Instructions
Create the Website → Store → Store View hierarchy
Note: Core Magento does not ship bin/magento store:website:create, store:group:create, or store:store:create CLI commands. Create websites, stores, and store views either through Admin → Stores → All Stores or programmatically in PHP (shown below). Some third-party modules add CLI equivalents, but they are not part of the core.
Via PHP programmatically (primary method):
<?php
// Create website via DataObject
use Magento\Store\Model\Website;
use Magento\Store\Model\Group;
use Magento\Store\Model\Store;
$website = $objectManager->create(Website::class);
$website->setCode('uk_site')
->setName('UK Website')
->setDefaultGroupId(0) // Set after creating group
->save();
$storeGroup = $objectManager->create(Group::class);
$storeGroup->setWebsiteId($website->getId())
->setName('UK Store')
->setRootCategoryId(3) // Your UK root category ID
->save();
$storeView = $objectManager->create(Store::class);
$storeView->setWebsiteId($website->getId())
->setGroupId($storeGroup->getId())
->setCode('uk_en')
->setName('UK English')
->setIsActive(1)
->save();
Configure nginx for multi-website routing
# /etc/nginx/sites-available/magento-multi-store.conf
# Map host to Magento store code (MAGE_RUN_CODE + MAGE_RUN_TYPE)
map $http_host $MAGE_RUN_CODE {
hostnames;
default "";
www.mystore.com ""; # Default (global config)
uk.mystore.com uk_en; # UK store view
de.mystore.com de_de; # German store view
b2b.mystore.com b2b_en; # B2B website
}
map $http_host $MAGE_RUN_TYPE {
hostnames;
default "";
www.mystore.com "";
uk.mystore.com "store"; # Route to store view
de.mystore.com "store";
b2b.mystore.com "website"; # Route to website (different customer base)
}
server {
listen 443 ssl http2;
server_name ~^(.+\.)?mystore\.com$;
root /var/www/magento/pub;
index index.php;
location ~ \.php$ {
fastcgi_pass unix:/var/run/php/php8.2-fpm.sock;
fastcgi_param MAGE_RUN_CODE $MAGE_RUN_CODE;
fastcgi_param MAGE_RUN_TYPE $MAGE_RUN_TYPE;
include fastcgi_params;
}
}
Set scoped configuration values
Configuration can be set at global, website, or store view scope:
# Set base URL per website
bin/magento config:set --scope=websites --scope-code=uk_site web/secure/base_url "https://uk.mystore.com/"
bin/magento config:set --scope=websites --scope-code=uk_site web/unsecure/base_url "https://uk.mystore.com/"
# Set currency per website
bin/magento config:set --scope=websites --scope-code=uk_site currency/options/base GBP
bin/magento config:set --scope=websites --scope-code=uk_site currency/options/default GBP
bin/magento config:set --scope=websites --scope-code=uk_site currency/options/allow "GBP,EUR"
# Set locale per store view
bin/magento config:set --scope=stores --scope-code=de_de general/locale/code de_DE
bin/magento config:set --scope=stores --scope-code=de_de general/locale/timezone "Europe/Berlin"
# Disable a payment method for specific website
bin/magento config:set --scope=websites --scope-code=uk_site payment/checkmo/active 0
Programmatically in PHP:
<?php
use Magento\Framework\App\Config\Storage\WriterInterface;
use Magento\Store\Model\ScopeInterface;
class ScopeConfigManager
{
public function __construct(
private readonly WriterInterface $configWriter,
private readonly \Magento\Framework\App\Cache\TypeListInterface $cacheTypeList
) {}
public function setScopedValue(
string $path,
mixed $value,
string $scope,
int $scopeId
): void {
$this->configWriter->save($path, $value, $scope, $scopeId);
// Flush config cache after write
$this->cacheTypeList->cleanType('config');
}
public function setWebsiteShippingOrigin(string $websiteCode, array $originData): void {
$website = \Magento\Framework\App\ObjectManager::getInstance()
->create(\Magento\Store\Model\Website::class)
->load($websiteCode, 'code');
$this->setScopedValue(
'shipping/origin/country_id',
$originData['country'],
ScopeInterface::SCOPE_WEBSITES,
(int)$website->getId()
);
}
}
Manage website-specific product assignment and pricing
Products can be assigned to specific websites while sharing the global catalog:
<?php
// Assign a product to specific websites
use Magento\Catalog\Model\ResourceModel\Product as ProductResource;
class ProductWebsiteAssignment
{
public function __construct(
private readonly ProductResource $productResource,
private readonly \Magento\Store\Model\StoreManagerInterface $storeManager
) {}
public function assignProductToWebsite(int $productId, string $websiteCode): void {
$website = $this->storeManager->getWebsite($websiteCode);
$this->productResource->websiteToProducts([
['product_id' => $productId, 'website_id' => $website->getId()],
]);
}
public function setWebsitePrice(int $productId, string $websiteCode, float $price): void {
// Use tier prices with website scope for website-specific pricing
$tierPriceResource = \Magento\Framework\App\ObjectManager::getInstance()
->create(\Magento\Catalog\Model\ResourceModel\Product\Attribute\Backend\Tierprice::class);
// Or use price scope: Admin → Config → Catalog → Price → Catalog Price Scope = Website
}
}
Enable website-scoped pricing:
bin/magento config:set catalog/price/scope 1 # 0 = Global, 1 = Website
bin/magento indexer:reindex catalog_product_price
Configure Adobe Commerce B2B Shared Catalogs
Shared Catalogs (B2B feature) allow per-company product and pricing visibility:
<?php
// Assign a company to a custom shared catalog
use Magento\SharedCatalog\Api\SharedCatalogManagementInterface;
use Magento\SharedCatalog\Api\Data\SharedCatalogInterface;
class SharedCatalogManager
{
public function __construct(
private readonly SharedCatalogManagementInterface $sharedCatalogManagement,
private readonly \Magento\SharedCatalog\Api\SharedCatalogRepositoryInterface $catalogRepository,
private readonly \Magento\Company\Api\CompanyRepositoryInterface $companyRepository
) {}
public function assignCompanyToCatalog(int $companyId, int $sharedCatalogId): void {
$sharedCatalog = $this->catalogRepository->get($sharedCatalogId);
$company = $this->companyRepository->get($companyId);
$company->getExtensionAttributes()->getQuoteConfig()?->setCustomerGroupId(
$sharedCatalog->getCustomerGroupId()
);
$this->companyRepository->save($company);
}
}
# CLI: Assign products to a shared catalog (by SKU list from CSV)
bin/magento company:catalog:assign --catalog-id=2 --products-file=products.csv
# Set catalog-specific pricing via import
bin/magento import:start --entity=shared_catalog_product_price --behavior=add_update --import-file=prices.csv
Examples
Automated multi-website deployment configuration
<?php
// Console command to provision a new website from config array
namespace MyVendor\MultiStore\Console\Command;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class ProvisionWebsiteCommand extends Command
{
protected function configure(): void
{
$this->setName('mystore:website:provision')
->setDescription('Provision a new website from config');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$websites = [
[
'code' => 'au_site',
'name' => 'Australia',
'domain' => 'au.mystore.com',
'currency' => 'AUD',
'locale' => 'en_AU',
'timezone' => 'Australia/Sydney',
'root_category_id' => 5,
],
];
foreach ($websites as $config) {
$output->writeln("Creating website: {$config['code']}");
// Execute creation commands...
$this->createWebsite($config, $output);
}
return Command::SUCCESS;
}
private function createWebsite(array $config, OutputInterface $output): void
{
// Implementation: create Website → Group → StoreView → set config
}
}
Read scoped configuration in a custom module
<?php
use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Store\Model\ScopeInterface;
class StoreAwareConfig
{
public function __construct(
private readonly ScopeConfigInterface $scopeConfig
) {}
public function getWebsiteConfig(string $path, ?string $websiteCode = null): mixed
{
return $this->scopeConfig->getValue(
$path,
ScopeInterface::SCOPE_WEBSITE,
$websiteCode // null = current website
);
}
public function getStoreConfig(string $path, ?string $storeCode = null): mixed
{
return $this->scopeConfig->getValue(
$path,
ScopeInterface::SCOPE_STORE,
$storeCode // null = current store view
);
}
// Usage
public function getShippingOriginCountry(): string
{
return (string)$this->getStoreConfig('shipping/origin/country_id');
}
}
Best Practices
- Use store code routing (
MAGE_RUN_TYPE=store) for language variants and website routing (MAGE_RUN_TYPE=website) only when you need separate customer bases, orders, and payment methods
- Enable website-scoped pricing before going live — switching from global to website scope later requires a full price index rebuild and may break existing catalog rules
- Set
MAGE_RUN_CODE and MAGE_RUN_TYPE at the nginx/Apache level, not in index.php — this keeps routing config in the infrastructure layer and enables easier replication
- Test each website's checkout independently — payment gateways, tax classes, and shipping methods are all configurable per website; a working checkout on one website does not guarantee others work
- Flush configuration cache after every
config:set — scoped config changes are cached; run bin/magento cache:clean config after automated provisioning scripts
- Use separate Redis databases per website in high-traffic setups — a slow reindex on one website can saturate shared cache storage and affect all websites
- Document the website/store/store-view ID mapping — IDs change between environments; use codes (
uk_en) not numeric IDs in all configuration scripts
Common Pitfalls
| Problem |
Solution |
| New website shows global prices ignoring website config |
Enable website-scoped pricing: bin/magento config:set catalog/price/scope 1 then reindex catalog_product_price |
nginx MAGE_RUN_CODE not passed to PHP |
Verify fastcgi_param MAGE_RUN_CODE is inside the location ~ \.php$ block and not outside it — a common placement mistake |
| Customer from one website can log in to another |
Websites with MAGE_RUN_TYPE=website share customer pools by default unless you enable customer account sharing scoped per website: Admin → Config → Customer → Account Sharing |
| Product visible on wrong website |
Check product's "Product in Websites" attribute in Admin → Catalog; products must be explicitly assigned to each website they should appear on |
| Scoped config not taking effect |
Config values are cached — always run bin/magento cache:clean config after changes; also verify the scope code spelling matches exactly |
| B2B shared catalog not filtering products |
The customer must be assigned to a company with the correct catalog; guest and non-company customers see only the public shared catalog |
Related Skills
- @magento-module-development
- @magento-graphql
- @magento-indexing-caching
- @international-ecommerce
- @b2b-commerce
1---2name: magento-multi-store3description: Configure multiple websites and store views in Magento with shared or scoped catalogs, separate URL structures, and store-specific settings4---56# Magento Multi-Store Setup78## Overview910Magento's multi-store architecture has three levels: Website → Store → Store View. A Website groups stores with a shared customer base and order flow. A Store (under a Website) has its own root category and URL structure. Store Views (under a Store) typically represent languages or locales. Configuration values can be set at Global, Website, or Store View scope — lower scopes override higher ones. Adobe Commerce (B2B) adds Shared Catalogs for per-company product/price visibility control.1112## When to Use This Skill1314- When running multiple brands or country-specific storefronts from a single Magento installation15- When setting different base currencies, tax configurations, or payment methods per website16- When creating a B2B portal alongside a B2C store with different product visibility17- When implementing localized store views for multiple languages under the same product catalog18- When configuring separate checkout flows, shipping methods, or payment gateways per website19- When managing shared product catalog with website-specific pricing and visibility overrides2021## Core Instructions22231. **Create the Website → Store → Store View hierarchy**2425 > **Note:** Core Magento does not ship `bin/magento store:website:create`, `store:group:create`, or `store:store:create` CLI commands. Create websites, stores, and store views either through **Admin → Stores → All Stores** or programmatically in PHP (shown below). Some third-party modules add CLI equivalents, but they are not part of the core.2627 Via PHP programmatically (primary method):2829 ```php30 <?php31 // Create website via DataObject32 use Magento\Store\Model\Website;33 use Magento\Store\Model\Group;34 use Magento\Store\Model\Store;3536 $website = $objectManager->create(Website::class);37 $website->setCode('uk_site')38 ->setName('UK Website')39 ->setDefaultGroupId(0) // Set after creating group40 ->save();4142 $storeGroup = $objectManager->create(Group::class);43 $storeGroup->setWebsiteId($website->getId())44 ->setName('UK Store')45 ->setRootCategoryId(3) // Your UK root category ID46 ->save();4748 $storeView = $objectManager->create(Store::class);49 $storeView->setWebsiteId($website->getId())50 ->setGroupId($storeGroup->getId())51 ->setCode('uk_en')52 ->setName('UK English')53 ->setIsActive(1)54 ->save();55 ```56572. **Configure nginx for multi-website routing**5859 ```nginx60 # /etc/nginx/sites-available/magento-multi-store.conf6162 # Map host to Magento store code (MAGE_RUN_CODE + MAGE_RUN_TYPE)63 map $http_host $MAGE_RUN_CODE {64 hostnames;65 default "";66 www.mystore.com ""; # Default (global config)67 uk.mystore.com uk_en; # UK store view68 de.mystore.com de_de; # German store view69 b2b.mystore.com b2b_en; # B2B website70 }7172 map $http_host $MAGE_RUN_TYPE {73 hostnames;74 default "";75 www.mystore.com "";76 uk.mystore.com "store"; # Route to store view77 de.mystore.com "store";78 b2b.mystore.com "website"; # Route to website (different customer base)79 }8081 server {82 listen 443 ssl http2;83 server_name ~^(.+\.)?mystore\.com$;8485 root /var/www/magento/pub;86 index index.php;8788 location ~ \.php$ {89 fastcgi_pass unix:/var/run/php/php8.2-fpm.sock;90 fastcgi_param MAGE_RUN_CODE $MAGE_RUN_CODE;91 fastcgi_param MAGE_RUN_TYPE $MAGE_RUN_TYPE;92 include fastcgi_params;93 }94 }95 ```96973. **Set scoped configuration values**9899 Configuration can be set at global, website, or store view scope:100101 ```bash102 # Set base URL per website103 bin/magento config:set --scope=websites --scope-code=uk_site web/secure/base_url "https://uk.mystore.com/"104 bin/magento config:set --scope=websites --scope-code=uk_site web/unsecure/base_url "https://uk.mystore.com/"105106 # Set currency per website107 bin/magento config:set --scope=websites --scope-code=uk_site currency/options/base GBP108 bin/magento config:set --scope=websites --scope-code=uk_site currency/options/default GBP109 bin/magento config:set --scope=websites --scope-code=uk_site currency/options/allow "GBP,EUR"110111 # Set locale per store view112 bin/magento config:set --scope=stores --scope-code=de_de general/locale/code de_DE113 bin/magento config:set --scope=stores --scope-code=de_de general/locale/timezone "Europe/Berlin"114115 # Disable a payment method for specific website116 bin/magento config:set --scope=websites --scope-code=uk_site payment/checkmo/active 0117 ```118119 Programmatically in PHP:120121 ```php122 <?php123 use Magento\Framework\App\Config\Storage\WriterInterface;124 use Magento\Store\Model\ScopeInterface;125126 class ScopeConfigManager127 {128 public function __construct(129 private readonly WriterInterface $configWriter,130 private readonly \Magento\Framework\App\Cache\TypeListInterface $cacheTypeList131 ) {}132133 public function setScopedValue(134 string $path,135 mixed $value,136 string $scope,137 int $scopeId138 ): void {139 $this->configWriter->save($path, $value, $scope, $scopeId);140 // Flush config cache after write141 $this->cacheTypeList->cleanType('config');142 }143144 public function setWebsiteShippingOrigin(string $websiteCode, array $originData): void {145 $website = \Magento\Framework\App\ObjectManager::getInstance()146 ->create(\Magento\Store\Model\Website::class)147 ->load($websiteCode, 'code');148149 $this->setScopedValue(150 'shipping/origin/country_id',151 $originData['country'],152 ScopeInterface::SCOPE_WEBSITES,153 (int)$website->getId()154 );155 }156 }157 ```1581594. **Manage website-specific product assignment and pricing**160161 Products can be assigned to specific websites while sharing the global catalog:162163 ```php164 <?php165 // Assign a product to specific websites166 use Magento\Catalog\Model\ResourceModel\Product as ProductResource;167168 class ProductWebsiteAssignment169 {170 public function __construct(171 private readonly ProductResource $productResource,172 private readonly \Magento\Store\Model\StoreManagerInterface $storeManager173 ) {}174175 public function assignProductToWebsite(int $productId, string $websiteCode): void {176 $website = $this->storeManager->getWebsite($websiteCode);177 $this->productResource->websiteToProducts([178 ['product_id' => $productId, 'website_id' => $website->getId()],179 ]);180 }181182 public function setWebsitePrice(int $productId, string $websiteCode, float $price): void {183 // Use tier prices with website scope for website-specific pricing184 $tierPriceResource = \Magento\Framework\App\ObjectManager::getInstance()185 ->create(\Magento\Catalog\Model\ResourceModel\Product\Attribute\Backend\Tierprice::class);186 // Or use price scope: Admin → Config → Catalog → Price → Catalog Price Scope = Website187 }188 }189 ```190191 Enable website-scoped pricing:192193 ```bash194 bin/magento config:set catalog/price/scope 1 # 0 = Global, 1 = Website195 bin/magento indexer:reindex catalog_product_price196 ```1971985. **Configure Adobe Commerce B2B Shared Catalogs**199200 Shared Catalogs (B2B feature) allow per-company product and pricing visibility:201202 ```php203 <?php204 // Assign a company to a custom shared catalog205 use Magento\SharedCatalog\Api\SharedCatalogManagementInterface;206 use Magento\SharedCatalog\Api\Data\SharedCatalogInterface;207208 class SharedCatalogManager209 {210 public function __construct(211 private readonly SharedCatalogManagementInterface $sharedCatalogManagement,212 private readonly \Magento\SharedCatalog\Api\SharedCatalogRepositoryInterface $catalogRepository,213 private readonly \Magento\Company\Api\CompanyRepositoryInterface $companyRepository214 ) {}215216 public function assignCompanyToCatalog(int $companyId, int $sharedCatalogId): void {217 $sharedCatalog = $this->catalogRepository->get($sharedCatalogId);218 $company = $this->companyRepository->get($companyId);219 $company->getExtensionAttributes()->getQuoteConfig()?->setCustomerGroupId(220 $sharedCatalog->getCustomerGroupId()221 );222 $this->companyRepository->save($company);223 }224 }225 ```226227 ```bash228 # CLI: Assign products to a shared catalog (by SKU list from CSV)229 bin/magento company:catalog:assign --catalog-id=2 --products-file=products.csv230231 # Set catalog-specific pricing via import232 bin/magento import:start --entity=shared_catalog_product_price --behavior=add_update --import-file=prices.csv233 ```234235## Examples236237### Automated multi-website deployment configuration238239```php240<?php241// Console command to provision a new website from config array242namespace MyVendor\MultiStore\Console\Command;243244use Symfony\Component\Console\Command\Command;245use Symfony\Component\Console\Input\InputInterface;246use Symfony\Component\Console\Output\OutputInterface;247248class ProvisionWebsiteCommand extends Command249{250 protected function configure(): void251 {252 $this->setName('mystore:website:provision')253 ->setDescription('Provision a new website from config');254 }255256 protected function execute(InputInterface $input, OutputInterface $output): int257 {258 $websites = [259 [260 'code' => 'au_site',261 'name' => 'Australia',262 'domain' => 'au.mystore.com',263 'currency' => 'AUD',264 'locale' => 'en_AU',265 'timezone' => 'Australia/Sydney',266 'root_category_id' => 5,267 ],268 ];269270 foreach ($websites as $config) {271 $output->writeln("Creating website: {$config['code']}");272 // Execute creation commands...273 $this->createWebsite($config, $output);274 }275276 return Command::SUCCESS;277 }278279 private function createWebsite(array $config, OutputInterface $output): void280 {281 // Implementation: create Website → Group → StoreView → set config282 }283}284```285286### Read scoped configuration in a custom module287288```php289<?php290use Magento\Framework\App\Config\ScopeConfigInterface;291use Magento\Store\Model\ScopeInterface;292293class StoreAwareConfig294{295 public function __construct(296 private readonly ScopeConfigInterface $scopeConfig297 ) {}298299 public function getWebsiteConfig(string $path, ?string $websiteCode = null): mixed300 {301 return $this->scopeConfig->getValue(302 $path,303 ScopeInterface::SCOPE_WEBSITE,304 $websiteCode // null = current website305 );306 }307308 public function getStoreConfig(string $path, ?string $storeCode = null): mixed309 {310 return $this->scopeConfig->getValue(311 $path,312 ScopeInterface::SCOPE_STORE,313 $storeCode // null = current store view314 );315 }316317 // Usage318 public function getShippingOriginCountry(): string319 {320 return (string)$this->getStoreConfig('shipping/origin/country_id');321 }322}323```324325## Best Practices326327- **Use store code routing (`MAGE_RUN_TYPE=store`) for language variants** and website routing (`MAGE_RUN_TYPE=website`) only when you need separate customer bases, orders, and payment methods328- **Enable website-scoped pricing** before going live — switching from global to website scope later requires a full price index rebuild and may break existing catalog rules329- **Set `MAGE_RUN_CODE` and `MAGE_RUN_TYPE` at the nginx/Apache level**, not in `index.php` — this keeps routing config in the infrastructure layer and enables easier replication330- **Test each website's checkout independently** — payment gateways, tax classes, and shipping methods are all configurable per website; a working checkout on one website does not guarantee others work331- **Flush configuration cache after every `config:set`** — scoped config changes are cached; run `bin/magento cache:clean config` after automated provisioning scripts332- **Use separate Redis databases per website in high-traffic setups** — a slow reindex on one website can saturate shared cache storage and affect all websites333- **Document the website/store/store-view ID mapping** — IDs change between environments; use codes (`uk_en`) not numeric IDs in all configuration scripts334335## Common Pitfalls336337| Problem | Solution |338|---------|----------|339| New website shows global prices ignoring website config | Enable website-scoped pricing: `bin/magento config:set catalog/price/scope 1` then reindex `catalog_product_price` |340| nginx `MAGE_RUN_CODE` not passed to PHP | Verify `fastcgi_param MAGE_RUN_CODE` is inside the `location ~ \.php$` block and not outside it — a common placement mistake |341| Customer from one website can log in to another | Websites with `MAGE_RUN_TYPE=website` share customer pools by default unless you enable customer account sharing scoped per website: `Admin → Config → Customer → Account Sharing` |342| Product visible on wrong website | Check product's "Product in Websites" attribute in Admin → Catalog; products must be explicitly assigned to each website they should appear on |343| Scoped config not taking effect | Config values are cached — always run `bin/magento cache:clean config` after changes; also verify the scope code spelling matches exactly |344| B2B shared catalog not filtering products | The customer must be assigned to a company with the correct catalog; guest and non-company customers see only the public shared catalog |345346## Related Skills347348- @magento-module-development349- @magento-graphql350- @magento-indexing-caching351- @international-ecommerce352- @b2b-commerce