Magento 2 Store View Configuration: Best Practices for Scopes
AI generated
M2
di.xml
Magento 2 · Multi Store · Configuration
Magento 2 Store View Configuration
Best practices for clean scope hierarchies

A misconfigured Magento 2 store view configuration often goes unnoticed for weeks, until a customer on the wrong domain sees the wrong price displayed. The scope hierarchy of default, website and store view decides which value actually wins, and anyone who has not internalized this order produces configuration errors that only show up in production.

18 min read Scope hierarchy · core_config_data · CLI Magento 2.4.x

1. Why store view configuration deserves its own discipline

Anyone running Magento 2 with a single website and a single store view barely notices the complexity behind Magento 2 store view configuration. As soon as a second market, a second language or a second brand is added, the simple backend settings page turns into a multidimensional system with three layered levels. Each of these levels can hold a different value for the same configuration path, and Magento resolves which value actually gets delivered according to a fixed rule.

The real risk does not lie in the technology itself, but in the fact that a broken store view configuration rarely surfaces immediately. A value set incorrectly at website level may silently override a deliberately configured store view setting, without any warning appearing in the admin grid. Only when a customer in a specific store sees the wrong shipping option, or receives a transactional email with the wrong sender name, does the root cause become visible. The following sections show how the scope hierarchy actually works and which practices prevent these errors from the start.

2. The scope hierarchy: default, website, store, store view

Magento 2 has four configuration levels, even though the admin backend usually mentions only three visible scopes. At the top sits default, the global fallback level that applies to every website unless a more specific setting exists. Below it comes the website level, which groups several store groups into one economic unit, for example because they share the same payment processing or the same product catalog. Within a website sits the store level, also called a store group in Magento, which defines the product catalog and category tree structure. At the bottom, and most important for store view configuration, sits the store view level: the language and presentation variant actually experienced by the customer in the storefront.

The rule Magento uses to resolve a value is simple, yet frequently misunderstood: Magento first looks for an entry at the most specific level, then at the website level, and finally at default. If no entry exists for a store view in core_config_data, Magento automatically falls back to the website value, and if that does not exist either, the default value applies. In practice this fallback chain means a value once set at store view level remains in place even after the default value is later changed, which is regularly overlooked during migrations and feature rollouts.


<?php

declare(strict_types=1);

namespace Mironsoft\StoreConfig\ViewModel;

use Magento\Framework\View\Element\Block\ArgumentInterface;
use Magento\Store\Model\ScopeInterface;
use Magento\Framework\App\Config\ScopeConfigInterface;

/**
 * ViewModel resolving a config value across the Magento scope hierarchy.
 */
final class ScopeResolver implements ArgumentInterface
{
    /**
     * @param ScopeConfigInterface $scopeConfig Magento scope config reader
     */
    public function __construct(
        private readonly ScopeConfigInterface $scopeConfig
    ) {
    }

    /**
     * Resolve a config path for the current store view, falling back
     * to website and default scope automatically.
     *
     * @param string $path Config path, e.g. "general/store_information/name"
     * @param int|null $storeId Store view id, null uses current store
     * @return string|null Resolved value or null if not configured anywhere
     */
    public function resolve(string $path, ?int $storeId = null): ?string
    {
        return $this->scopeConfig->getValue(
            $path,
            ScopeInterface::SCOPE_STORE,
            $storeId
        );
    }
}

3. Understanding core_config_data: path, scope, value

Every configuration value technically lands in exactly one table: core_config_data. The structure is deliberately simple: a path column for the config path such as general/locale/code, a scope column holding default, websites or stores, a scope_id column with the numeric id of the respective website or store view, and finally value with the actual content. Anyone who queries this table directly via SQL immediately sees at which level a value was actually set, instead of clicking through several tabs in the backend.

A common mistake in store view configuration happens when developers insert rows directly into core_config_data via a SQL script without invalidating the config cache afterwards. Magento caches configuration values aggressively in the config cache area, and a value inserted via SQL often only becomes visible after the next cron run, unless bin/magento cache:flush config is run explicitly. For production systems the CLI command config:set should therefore always be preferred over direct SQL manipulation, since it triggers cache invalidation automatically.


-- Inspect which scope actually holds a value for a given path
SELECT config_id, scope, scope_id, path, value
FROM core_config_data
WHERE path = 'general/locale/code'
ORDER BY
  CASE scope
    WHEN 'default'  THEN 0
    WHEN 'websites' THEN 1
    WHEN 'stores'   THEN 2
  END;

-- Find all store-view-level overrides for a specific website
SELECT ccd.scope_id, ccd.path, ccd.value, s.name AS store_view_name
FROM core_config_data ccd
INNER JOIN store s ON s.store_id = ccd.scope_id
WHERE ccd.scope = 'stores'
  AND ccd.path LIKE 'general/store_information/%';

4. Adding custom configuration values with the correct scope

Anyone developing a custom module that offers backend settings must explicitly declare in system.xml, for each field, at which level a value may be overridden. The attributes showInDefault, showInWebsite and showInStore independently control whether a field is visible and editable at the respective level. Careful store view configuration here means consciously deciding: should a value really differ per store view, such as a sender address for transactional emails, or should it stay global, such as an API key for a payment provider that is identical across a whole website.

A scope granted too generously at store view level, in practice, leads editors to accidentally maintain different values per store view where a single unified value was actually intended. Conversely, an overly restrictive scope prevents international teams from configuring their respective store views independently. The rule of thumb: values that relate to language, currency or the legal requirements of a given market belong at store view level, while technical integration values usually belong at website or default level.


<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Config:etc/system_file.xsd">
    <system>
        <section id="mironsoft_storeconfig" translate="label" sortOrder="200"
                 showInDefault="1" showInWebsite="1" showInStore="1">
            <label>Store Config Extensions</label>
            <tab>general</tab>
            <resource>Mironsoft_StoreConfig::config</resource>
            <group id="general" translate="label" sortOrder="10"
                   showInDefault="1" showInWebsite="1" showInStore="1">
                <label>General Settings</label>
                <!-- Store view scope: legitimately different per market/language -->
                <field id="support_email" translate="label" type="text"
                       sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="1">
                    <label>Support Email per Store View</label>
                </field>
                <!-- Website scope only: shared payment gateway credential -->
                <field id="gateway_api_key" translate="label" type="obscure"
                       sortOrder="20" showInDefault="1" showInWebsite="1" showInStore="0">
                    <label>Gateway API Key</label>
                    <backend_model>Magento\Config\Model\Config\Backend\Encrypted</backend_model>
                </field>
            </group>
        </section>
    </system>
</config>

5. CLI workflows: config:set, config:show, config:sensitive

The CLI command bin/magento config:set optionally accepts the parameters --scope and --scope-code and allows targeted changes per website or store view without opening the backend at all. For deployment pipelines this is essential, because it lets the entire store view configuration be represented reproducibly in scripts instead of relying on manual clicks in the admin panel. The complementary command config:show returns the effectively resolved value for a given scope and is therefore the most important diagnostic tool whenever a value does not arrive as expected in the storefront.

For sensitive values such as API keys or SMTP passwords there is additionally config:set --lock-env, which stores the value directly in env.php instead of core_config_data, protecting it from accidental changes through the backend. This combination, database-based store view configuration for content values and file-based configuration for security-relevant values, is the recommended approach for production multi-store systems.


#!/usr/bin/env bash
set -euo pipefail

# Set a value only for the German store view (assume store code "de")
bin/magento config:set --scope=stores --scope-code=de \
  general/locale/code de_DE

# Set a value for the entire website "main"
bin/magento config:set --scope=websites --scope-code=main \
  carriers/flatrate/active 1

# Inspect the effectively resolved value for one store view
bin/magento config:show general/locale/code --scope=stores --scope-code=at

# Lock a sensitive value into env.php instead of core_config_data
bin/magento config:set --lock-env \
  payment/mironsoft_gateway/api_key "sk_live_xxx"

# Always flush config cache after direct database changes
bin/magento cache:flush config

6. env.php versus database configuration

A common misunderstanding in store view configuration concerns the boundary between values in app/etc/env.php and values in core_config_data. As a rule: env.php is meant for environment-specific, security-critical or deployment-relevant values, such as database credentials, cache backend configuration or encryption keys. These values differ per environment (development, staging, production) but are identical across all stores within one environment, which is exactly why they do not belong at the store view level of the database.

Values in core_config_data, on the other hand, are meant for content-related, editorially maintainable settings that may differ between stores and should be changeable by business users without a deployment. Anyone who accidentally stores store-specific values in env.php loses the ability to maintain them granularly per store view in the backend, and has to go through a full deployment cycle for every change. Clearly separating these two configuration levels is one of the most important architectural decisions for a maintainable multi-store setup.

7. Cache invalidation after scope changes

Changes to store view configuration are not always immediately visible in the storefront, because Magento operates several cache layers between the database and the delivered page. The config cache itself is usually invalidated automatically for changes made through the backend, but not always reliably for direct database changes or batch CLI commands. In addition, the full page cache stores entire HTML fragments that, at the time of generation, still contained the old configuration value, such as a phone number displayed in the footer.

For production deployments a fixed sequence is recommended: first use config:set or the backend, then run cache:flush config full_page block_html layout, and when Varnish is in use, additionally trigger an explicit Varnish purge for the affected store view. Anyone who automates this sequence into deployment scripts avoids the common situation where a support ticket about an apparently wrong setting is in fact just a caching problem.

8. Common pitfalls in production

The most common pitfall in store view configuration is assuming that a value changed at the default level automatically applies everywhere. If a more specific value already exists at website or store view level, it remains in place and silently overrides the default change. This regularly leads to situations where a global update, such as a new legally required notice, simply does not arrive in a single store view, because someone set an individual value there years ago that nobody remembers anymore.

A second pitfall concerns configuration values fetched programmatically via ScopeConfigInterface::getValue() without an explicit scope parameter. If the second and third parameters are missing, Magento falls back to the current store context, which in the context of a cron job or a CLI command is often the default store rather than the store the code is actually meant to run for. Anyone writing custom modules should therefore always pass the store scope explicitly instead of relying on the implicit context.


<?php

declare(strict_types=1);

namespace Mironsoft\StoreConfig\Cron;

use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Store\Model\ScopeInterface;
use Magento\Store\Api\StoreRepositoryInterface;

/**
 * Cron job iterating explicitly over every store view instead of relying
 * on the implicit default-store context.
 */
final class SyncSupportEmails
{
    /**
     * @param ScopeConfigInterface $scopeConfig Reads config per explicit scope
     * @param StoreRepositoryInterface $storeRepository Lists all store views
     */
    public function __construct(
        private readonly ScopeConfigInterface $scopeConfig,
        private readonly StoreRepositoryInterface $storeRepository
    ) {
    }

    /**
     * Read the support email for every store view explicitly.
     *
     * @return void
     */
    public function execute(): void
    {
        foreach ($this->storeRepository->getList() as $store) {
            // WRONG: $this->scopeConfig->getValue('general/store_information/name')
            // would silently resolve against the default store in a cron context.

            // RIGHT: always pass the store scope and id explicitly.
            $email = $this->scopeConfig->getValue(
                'trans_email/ident_support/email',
                ScopeInterface::SCOPE_STORE,
                $store->getId()
            );

            if ($email !== null) {
                // ... sync $email for this specific store view
            }
        }
    }
}

9. Scope strategies compared

The following overview shows which configuration level is typically appropriate for which kind of setting in store view configuration, and what consequences the respective choice has in operation.

Value type Wrong scope Recommended scope Rationale
Payment provider API key Store view env.php (lock-env) Security-critical, per environment, not per store
Locale code Global default Store view Language must differ per market
Catalog price scope Store view Website Prices usually apply uniformly across a website
Support email address Default only Store view Market-dependent contact information
Cache backend connection core_config_data env.php Infrastructure value, not editorially maintained

This mapping is not a rigid rulebook, but a guideline oriented around two questions: must the value genuinely differ per store view, and should the value be changeable by business users without a deployment. Anyone who consistently answers both questions for every new configuration value avoids most of the scope-related errors typically found in grown multi-store installations.

Mironsoft

Magento 2 multi store and internationalization

Need store view configuration that survives rollouts?

We analyze existing Magento 2 multi-store setups, clean up the scope hierarchy, and automate configuration through CLI workflows so deployments stay reproducible and error-free.

Scope audit

Review existing core_config_data for conflicting scope overrides

CLI automation

Represent configuration values as reproducible deployment scripts

Module consulting

Plan system.xml scopes for custom modules correctly and future-proof

10. Summary

A clean Magento 2 store view configuration rests on understanding the fallback chain from store view over website to default, and on the conscious decision which values should be visible at each level in the first place. The core_config_data table makes this structure transparent, CLI commands like config:set and config:show make it reproducible and diagnosable. Security-critical values consistently belong in env.php, editorial and market-dependent values in the database.

The biggest lever against misconfiguration lies in the discipline of making scope decisions once, deliberately, and then rolling them out consistently through CLI scripts instead of maintaining values manually in the backend. Anyone who additionally clears the relevant caches after every change avoids the most common source of error in production: values that are technically already set correctly, but not yet delivered due to caching.

Magento 2 Store View Configuration — Key Takeaways

Scope hierarchy

Store view overrides website, website overrides default. Fallback only applies when no more specific value exists.

core_config_data

The scope, scope_id and path columns determine at which level a value actually sits. Directly queryable via SQL.

CLI over backend

config:set --scope=stores/websites for reproducible deployments, config:show to diagnose the effective value.

env.php for security

Security-critical, environment-specific values belong in env.php via --lock-env, not in the database.

11. FAQ: Magento 2 Store View Configuration

1Website, store, store view: what is the difference?
Website is the economic unit with its own domain and base currency. Store defines catalog and category tree. Store view is the concrete language and presentation variant in the storefront.
2Which level wins with multiple set values?
Always the most specific: store view before website before default. If a level is missing, Magento falls back to the next higher one.
3Why doesn't a default change apply everywhere?
Because existing more specific values at website or store view level remain in place and override the default change.
4How do I find the actual scope level of a value?
Filter core_config_data via SQL, or use bin/magento config:show with --scope and --scope-code for the effective value.
5API keys: core_config_data or env.php?
In env.php with config:set --lock-env, so they cannot be accidentally changed via the backend and stay out of DB backups.
6How do I control scopes in system.xml?
With showInDefault, showInWebsite and showInStore on the field element, controllable independently.
7Must I always clear the cache after config:set?
Usually automatic, but not for direct SQL changes. Then bin/magento cache:flush config is mandatory.
8Why does getValue() return a wrong value in cron?
Without an explicit scope, Magento uses the current store context, which in cron is often the default store. Always pass scope explicitly.
9Catalog price scope: website or store view?
Usually website level, since prices should typically be uniform within a website.
10Does the full page cache affect scope changes?
Yes, cached HTML fragments contain old values. After scope changes, also clear full_page and block_html.