Configuration Scopes

Default, website and store view: how a config value is inherited, how an override is stored, and how to read it for the right store.

Syntax$this->scopeConfig->getValue('section/group/field', ScopeInterface::SCOPE_STORE, $storeCode)

One Magento installation can run several storefronts. Every setting therefore has three possible levels, called scopes: default for the whole installation, website, and store view. The scope switcher at the top left of Stores > Configuration chooses which level you are editing.

Inheritance

Resolving a value walks upwards: store view, then that store's website, then default, then the merged config.xml. Overrides live in core_config_data, one row each, with the scope column set to default, websites or stores and scope_id holding the numeric id. A field only offers a level if system.xml sets the matching showInDefault, showInWebsite or showInStore attribute.

Reading for a store

getValue($path) is shorthand for getValue($path, ScopeConfigInterface::SCOPE_TYPE_DEFAULT, null), and it deliberately ignores which storefront the visitor is on. For a storefront value, pass a store scope. Both spellings work: ScopeInterface::SCOPE_STORE is the string store and the framework normalises it to ScopeInterface::SCOPE_STORES, the string stores, before looking anything up. With a store scope and a null scope code, Magento resolves the current store.

What trips people up

Reading at default scope from storefront code is the usual bug: the value is right on one store view and wrong on the next. If a value changed outside the admin does not show up, run bin/magento cache:flush config.

Example

Example · php
<?php
// app/code/Acme/Loyalty/Controller/Points/Scope.php
declare(strict_types=1);

namespace Acme\Loyalty\Controller\Points;

use Magento\Framework\App\Action\HttpGetActionInterface;
use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Framework\Controller\Result\Json;
use Magento\Framework\Controller\Result\JsonFactory;
use Magento\Store\Model\ScopeInterface;

class Scope implements HttpGetActionInterface
{
    private const PATH_RATE = 'loyalty/general/points_per_currency';

    public function __construct(
        private readonly ScopeConfigInterface $scopeConfig,
        private readonly JsonFactory $resultJsonFactory
    ) {
    }

    public function execute(): Json
    {
        // Installation-wide. Ignores the visitor's storefront.
        $installationWide = $this->scopeConfig->getValue(self::PATH_RATE);

        // The current store view: a null scope code resolves the request's store.
        $forThisStore = $this->scopeConfig->getValue(
            self::PATH_RATE,
            ScopeInterface::SCOPE_STORE
        );

        // A named store view, whichever store the request arrived on.
        $forGerman = $this->scopeConfig->getValue(
            self::PATH_RATE,
            ScopeInterface::SCOPE_STORE,
            'de'
        );

        return $this->resultJsonFactory->create()->setData([
            'default' => (int) $installationWide,
            'current' => (int) $forThisStore,
            'de' => (int) $forGerman,
        ]);
    }
}

When to use it

  • Give a German store view a different points rate from the rest of the installation.
  • Fix a setting that reads correctly on the main store and wrongly on a second one.
  • Restrict a setting to website level so store views cannot diverge.
  • Check from the CLI which scope a surprising value was actually saved in.

More examples

Finding which scope a value came from

Run the same path at each scope and the override chain becomes visible: 10 installation-wide, 20 for the base website, 35 for the German store view. --scope takes default, websites or stores; --scope-code takes the website or store view code, not its numeric id. This is the quickest answer to "why does this storefront behave differently" and it needs no database client.

Example · bash
$ bin/magento config:show loyalty/general/points_per_currency
10

$ bin/magento config:show --scope=websites --scope-code=base \
    loyalty/general/points_per_currency
20

$ bin/magento config:show --scope=stores --scope-code=de \
    loyalty/general/points_per_currency
35

$ bin/magento config:set --scope=stores --scope-code=de \
    loyalty/general/points_per_currency 40
Value was saved.

Restricting a field to website level

showInStore="0" means the field is simply absent from the form when the scope switcher is on a store view, so no stores row can ever be created for it through the admin. Use this when a value must be consistent across the languages of one website, such as an external programme or account identifier. The attributes govern the form only: code that passes a store scope still gets an answer, resolved by inheritance from the website row. Apply with bin/magento cache:flush.

Example · xml
<!-- app/code/Acme/Loyalty/etc/adminhtml/system.xml (field only) -->
<field id="programme_code" translate="label comment" type="text" sortOrder="50"
       showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1">
    <label>Loyalty Programme Code</label>
    <comment>One programme per website. Store views share it.</comment>
</field>

Inspecting the override rows directly

Three rows, three scopes, and a unique key across scope, scope_id and path, so there can never be two rows for the same field at the same level. The scope column holds the plural forms, websites and stores, while the API constants you pass in PHP are the singular store and website; the framework converts between them. Query the table to look, never to write: writing here bypasses the backend models and leaves the config cache stale.

Example · sql
-- Read-only inspection of the override table.
SELECT scope, scope_id, path, value
FROM core_config_data
WHERE path = 'loyalty/general/points_per_currency'
ORDER BY scope, scope_id;

-- +----------+----------+-------------------------------------+-------+
-- | scope    | scope_id | path                                | value |
-- +----------+----------+-------------------------------------+-------+
-- | default  |        0 | loyalty/general/points_per_currency | 10    |
-- | stores   |        2 | loyalty/general/points_per_currency | 35    |
-- | websites |        1 | loyalty/general/points_per_currency | 20    |
-- +----------+----------+-------------------------------------+-------+

Discussion

  • Be the first to comment on this lesson.