
Magento Multi Store
- 62 installs
- 41 repo stars
- Updated March 13, 2026
- finsilabs/awesome-ecommerce-skills
Configure multiple Magento websites and store views with shared or scoped catalogs, separate URL structures, and store-specific settings.
About
Explains Magento's website/store/store-view hierarchy, catalog scoping, URL structures, and per-store configuration. A developer uses it to run several storefronts or locales from one Magento installation.
- Website, store, and store-view scope configuration
- Scoped catalogs and store-specific URL and settings setup
Magento Multi Store by the numbers
- 62 all-time installs (skills.sh)
- Ranked #3,145 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
- Data as of Aug 3, 2026 (Skillselion catalog sync)
npx skills add https://github.com/finsilabs/awesome-ecommerce-skills --skill magento-multi-storeAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 62 |
|---|---|
| repo stars | ★ 41 |
| Last updated | March 13, 2026 |
| Repository | finsilabs/awesome-ecommerce-skills ↗ |
What it does
Configure multiple Magento websites and store views with shared or scoped catalogs, separate URL structures, and store-specific settings.
Files
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
1. Create the Website → Store → Store View hierarchy
Note: Core Magento does not shipbin/magento store:website:create,store:group:create, orstore:store:createCLI 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();2. 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;
}
}3. 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 0Programmatically 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()
);
}
}4. 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_price5. 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.csvExamples
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 configafter 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
{
"context": "Tests whether the agent uses Magento CLI commands to create the website/store/store-view hierarchy, applies scoped configuration with correct scope flags, enables website-scoped pricing with the required reindex step, flushes the config cache after config changes, and references stores by code rather than numeric ID.",
"type": "weighted_checklist",
"checklist": [
{
"name": "store:website:create used",
"max_score": 8,
"description": "Script calls 'bin/magento store:website:create' (not a PHP script or admin UI instructions) to create the website"
},
{
"name": "store:group:create used",
"max_score": 8,
"description": "Script calls 'bin/magento store:group:create' to create the store group"
},
{
"name": "store:store:create used",
"max_score": 8,
"description": "Script calls 'bin/magento store:store:create' to create the store view"
},
{
"name": "Website-scope config flags",
"max_score": 8,
"description": "config:set commands for website-level settings use '--scope=websites' and '--scope-code=<website_code>' flags"
},
{
"name": "Store-scope config flags",
"max_score": 8,
"description": "config:set commands for store-view-level settings (locale, timezone) use '--scope=stores' and '--scope-code=<store_view_code>' flags"
},
{
"name": "Cache flush after config changes",
"max_score": 12,
"description": "Script runs 'bin/magento cache:clean config' (or equivalent cache flush) after setting configuration values"
},
{
"name": "Website-scoped pricing enabled",
"max_score": 12,
"description": "Script sets 'catalog/price/scope' to 1 (e.g., 'bin/magento config:set catalog/price/scope 1') to enable website-level pricing"
},
{
"name": "Price index reindex after pricing scope change",
"max_score": 10,
"description": "Script runs 'bin/magento indexer:reindex catalog_product_price' after enabling website-scoped pricing"
},
{
"name": "String codes used throughout",
"max_score": 8,
"description": "All config:set scope references and store creation commands use string codes (e.g., uk_site, uk_en) rather than numeric IDs for website/store/store-view references"
},
{
"name": "Base URLs set per website",
"max_score": 8,
"description": "Script sets both web/secure/base_url and web/unsecure/base_url at website scope"
},
{
"name": "Currency configured at website scope",
"max_score": 10,
"description": "Script sets currency/options/base and/or currency/options/default at website scope (--scope=websites)"
}
]
}
Automated Country Website Provisioning for Magento
Problem/Feature Description
A global e-commerce company is expanding rapidly and needs to launch new country-specific storefronts every quarter. Each new country gets its own Magento website with a dedicated subdomain, local currency, locale, and timezone. The operations team currently provisions new websites by clicking through the Magento admin UI, which is error-prone and takes 30–45 minutes per country. They want a repeatable shell script that can be run once a root category for the new country has been created in the catalog.
The company has recently run into problems where products on new websites were still showing global (USD) prices instead of local prices, causing customer complaints. This issue needs to be baked into the provisioning script so it doesn't happen again. The team also found that after running config changes, the new settings sometimes don't appear until after manual cache intervention — the script must handle this automatically.
The script will be run by engineers who know the new website's code, name, domain, currency, locale, timezone, and root category ID ahead of time. They maintain a policy of referencing stores and websites by their string codes (not internal numeric IDs) in all scripts, since IDs differ across staging and production environments.
Output Specification
Produce a shell script named provision-website.sh that accepts the following parameters (either as positional arguments or as environment variables — document the calling convention in the script header):
- Website code (e.g.,
au_site) - Website name (e.g.,
Australia) - Store code (e.g.,
au_store) - Store view code (e.g.,
au_en) - Root category ID (integer)
- Domain (e.g.,
au.mystore.com) - Currency code (e.g.,
AUD) - Locale code (e.g.,
en_AU) - Timezone (e.g.,
Australia/Sydney)
The script should fully provision the new website, configure scoped settings, and leave the system in a working state. Assume the script is run from the Magento root directory (where bin/magento is located). Add inline comments explaining non-obvious steps.
{
"context": "Tests whether the agent correctly configures nginx for Magento multi-store routing, using map directives for MAGE_RUN_CODE and MAGE_RUN_TYPE, placing fastcgi_param directives inside the PHP location block, selecting the correct run type (store vs website) based on customer base separation, and keeping routing config at the infrastructure layer.",
"type": "weighted_checklist",
"checklist": [
{
"name": "map directive for MAGE_RUN_CODE",
"max_score": 10,
"description": "Uses an nginx 'map' block (map $http_host $MAGE_RUN_CODE) to map hostnames to Magento store/website codes, rather than hardcoding per server block or setting in application code"
},
{
"name": "map directive for MAGE_RUN_TYPE",
"max_score": 10,
"description": "Uses a separate nginx 'map' block (map $http_host $MAGE_RUN_TYPE) to map hostnames to the routing type"
},
{
"name": "fastcgi_param inside PHP location block",
"max_score": 13,
"description": "Both fastcgi_param MAGE_RUN_CODE and fastcgi_param MAGE_RUN_TYPE directives are placed inside the 'location ~ \\.php$' block, not outside it at the server level"
},
{
"name": "Language storefronts use store routing",
"max_score": 10,
"description": "UK and German storefronts are mapped to MAGE_RUN_TYPE 'store', not 'website'"
},
{
"name": "B2B portal uses website routing",
"max_score": 10,
"description": "B2B portal is mapped to MAGE_RUN_TYPE 'website'"
},
{
"name": "hostnames keyword in map blocks",
"max_score": 6,
"description": "map blocks include the 'hostnames' keyword to enable hostname-based matching"
},
{
"name": "Notes explain store vs website distinction",
"max_score": 10,
"description": "Documentation explains that 'store' routing is chosen for language variants that share a customer base, while 'website' routing is chosen for the B2B portal because it requires a separate customer base"
},
{
"name": "Default empty values in map",
"max_score": 7,
"description": "map blocks include a 'default' entry mapping to an empty string for unrecognized hosts"
},
{
"name": "No MAGE_RUN_CODE in index.php",
"max_score": 12,
"description": "The configuration does NOT set MAGE_RUN_CODE or MAGE_RUN_TYPE inside a PHP file (index.php or similar) — routing is handled entirely at the nginx level"
},
{
"name": "MAGE_RUN_CODE uses store view codes for store-type routes",
"max_score": 12,
"description": "For domains mapped to MAGE_RUN_TYPE 'store', the corresponding MAGE_RUN_CODE value is a store view code (not a website code)"
}
]
}
nginx Configuration for Multi-Brand Magento Storefront
Problem/Feature Description
A retail group operates a single Magento installation that needs to serve three distinct web properties from one server: a UK English storefront at uk.fashiongroup.com, a German-language storefront at de.fashiongroup.com, and a B2B wholesale portal at b2b.fashiongroup.com. The UK and German storefronts share the same customer database and order history — shoppers who registered on one can log in to the other — but the B2B portal serves an entirely separate corporate customer base with its own order management and payment terms.
The current nginx configuration only serves a single domain. The infrastructure team needs a production-ready nginx server block that routes each incoming request to the correct Magento context, with all environment variables set correctly so that Magento serves the right store without any changes to application code. The team has had previous issues where environment variables set in the wrong location weren't picked up by PHP-FPM, so correctness of placement is critical.
Output Specification
Produce a single nginx configuration file named magento-multi-store.conf that can be dropped into /etc/nginx/sites-available/. The file should:
- Handle all three domains listed above
- Route each domain to the correct Magento context using appropriate environment variable values
- Include a working PHP location block with FastCGI configuration passing the required Magento routing variables to PHP-FPM (assume socket path:
/var/run/php/php8.2-fpm.sock) - Set the document root to
/var/www/magento/pub
Write a brief routing-notes.md explaining the routing strategy chosen for each domain and why the B2B portal uses a different routing approach than the language storefronts.
{
"context": "Tests whether the agent uses the correct Magento PHP APIs for reading and writing scoped configuration (WriterInterface, ScopeConfigInterface with ScopeInterface constants) and correctly assigns products to a website, including cache invalidation after config writes.",
"type": "weighted_checklist",
"checklist": [
{
"name": "WriterInterface for config writes",
"max_score": 12,
"description": "Config writes use Magento\\Framework\\App\\Config\\Storage\\WriterInterface (via $configWriter->save()) rather than direct database writes or other mechanisms"
},
{
"name": "SCOPE_WEBSITE constant for website reads",
"max_score": 10,
"description": "Reading website-scoped config uses ScopeInterface::SCOPE_WEBSITE (from Magento\\Store\\Model\\ScopeInterface), not a raw string"
},
{
"name": "SCOPE_STORE constant for store reads",
"max_score": 10,
"description": "Reading store-view-scoped config uses ScopeInterface::SCOPE_STORE (from Magento\\Store\\Model\\ScopeInterface), not a raw string"
},
{
"name": "ScopeConfigInterface for reads",
"max_score": 10,
"description": "Config reads use Magento\\Framework\\App\\Config\\ScopeConfigInterface (via $scopeConfig->getValue()) rather than direct database or other access"
},
{
"name": "Config cache flushed after write",
"max_score": 12,
"description": "After writing config values, the code cleans the config cache (e.g., CacheTypeListInterface::cleanType('config') or equivalent)"
},
{
"name": "Product assigned via ProductResource",
"max_score": 12,
"description": "Product-to-website assignment uses ProductResource (Magento\\Catalog\\Model\\ResourceModel\\Product) and its websiteToProducts() method or equivalent API, not direct SQL"
},
{
"name": "Website-specific pricing approach",
"max_score": 10,
"description": "Website-specific price setting references tier prices with website scope OR the catalog price scope setting, NOT a simple direct price attribute assignment"
},
{
"name": "Dependency injection used",
"max_score": 8,
"description": "Classes receive dependencies (WriterInterface, ScopeConfigInterface, ProductResource, etc.) via constructor injection rather than using ObjectManager directly"
},
{
"name": "StoreManagerInterface used for website lookup",
"max_score": 8,
"description": "Website or store objects are retrieved via StoreManagerInterface (e.g., getWebsite($code)) rather than hardcoded IDs"
},
{
"name": "String codes for scope references",
"max_score": 8,
"description": "Scope references in config reads/writes use string codes (e.g., 'wholesale_site') not hardcoded numeric IDs"
}
]
}
PHP Data Setup Script for New Website Launch
Problem/Feature Description
An e-commerce platform running Adobe Commerce is launching a new wholesale website (wholesale_site) alongside the existing retail storefront. The development team needs a PHP setup script (a Magento data patch or standalone script) that handles the programmatic side of the website launch: making sure products from a specified list of SKUs are visible on the new wholesale website, setting website-level configuration values via the proper Magento API, and reading back configuration values at the correct scope to verify the setup.
The team has been burned before by config changes that appeared to take effect immediately but were actually still serving cached values hours later, so the setup script must handle cache invalidation. There's also a requirement to implement a helper class that other modules can use to safely read configuration at either website or store-view scope — the rest of the codebase should not have to deal with raw scope strings.
Output Specification
Produce a PHP file (or set of files) that implements:
1. A configuration manager class (WebsiteConfigManager.php or similar) that:
- Can write a configuration value at a given scope and scope ID
- Invalidates the config cache after writing
- Can read a configuration value at website scope (given a website code)
- Can read a configuration value at store view scope (given a store code)
2. A product assignment helper class (ProductWebsiteAssigner.php or similar) that:
- Accepts a product ID and website code and makes the product visible on that website
- Includes a method that sets a website-specific price for a product
3. A setup script (setup.php or a Magento DataPatch class) that uses the above helpers to:
- Assign a hardcoded list of 3 product IDs (101, 102, 103) to
wholesale_site - Set the base URL for
wholesale_sitetohttps://wholesale.mystore.com/ - Read back and print the base URL at website scope to verify it was saved
Include inline comments in the code explaining the approach.
{
"name": "finsi/magento-multi-store",
"version": "0.1.0",
"summary": "Multi-website, multi-store setup with shared catalogs and scoped config",
"skills": {
"magento-multi-store": {
"path": "SKILL.md"
}
}
}