All about the Magento 2 configuration settings fallback process
Everyone knows about creating & assigning configuration settings in Magento 2. But do you know about the fallback process, and how values can be overridden per environment?
The configuration workflow in Magento 2 is pretty amazing, because it can be controlled globally, per domain, per site, or even down to a specific environment. This fallback process is generally unknown though, and becoming familiar with it can help you out when crafting our solutions for your clients, or architecting that custom Magento module for distribution.
Basic global configuration
Let's start by taking a look at an example of a typical basic global configuration. Assume we are trying to implement a "dark mode" feature on the frontend of our website, and we'd like a configuration setting to control this feature globally on our site.
We'd start by creating a module, if we don't already have one. Then we'd create a simple configuration definition within an
<?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> <!-- We target the "web" section, so we don't need to create a new section. --> <section id="web"> <!-- Note that we need set the showInDefault="1" attribute for this configuration option to show in the Default configuration, and setting "sortOrder" to a high number is recommended so we don't clash with core configuration settings. --> <group id="darkmode" translate="label" type="text" showInDefault="1" sortOrder="1000"> <label>Dark Mode</label> <!-- The showInDefault="1" attribute also needs to be set here... --> <field id="enable" translate="label" type="select" showInDefault="1"> <label>Enable?</label> <!-- The Yesno source model gives us that fancy Yes/No dropdown. --> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> </field> </group> </section> </system> </config>
This will create a new "Dark Mode" group at Stores > Configuration > General > Web, with an "Enable?" Yes/No dropdown:
That is all that is needed to create a new "system config" key and value!
It's important to note where exactly in the XML tree we are placing this configuration option. A great practice to follow is avoiding creating additional admin groups & sections if at all possible, so you can retain a clean admin interface. Since the General > Web section already exists, this is a great place to collate our custom configuration option.
We can convert the nested nodes into a path to reference our configuration option. Naturally, the path for this Dark Mode configuration can be referenced with:
You can break out of this default location by defining a
config_pathnode containing the full path location of where you wish to store this config.
For example, if we wish to store this value at
web/darkmode/enable, we can add a node as a child to the
fieldnode with the contents:
We have not yet set a default value for this configuration. We can do this by creating a new
config.xml file within our module, and reference the path of our new configuration property:
<?xml version="1.0"?> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Store:etc/config.xsd"> <!-- This is the "config type" and is "default", "stores" or "websites" --> <default> <!-- web/darkmode/enable is the path for our configuration option. --> <web> <darkmode> <enable>0</enable> <!-- This tells web/darkmode/enable to default to 0 (No). --> </darkmode> </web> </default> </config>
Since a configuration property has been defined and a default value set, we can now reference this configuration programmatically.
A quick way to check this would be using n98magerun2's
=> [ "enable" => "0", ]
If you are using docker-magento for your development environment, you can also use
bin/devconsoleas a shortcut for the
Configuration scope for websites and stores
The setup we created above defines a "global" configuration property. We can also have configurations related to a specific website or store.
<?xml version="1.0"?> <config ...> <system ...> <section ...> <!-- We can also add attributes for showInWebsite="1" or showInStore="1" (or both) to have this property configurable within a website or store scope, respectively. --> <group id="darkmode" ... showInWebsite="1" showInStore="1"> ... <field id="enable" ... showInWebsite="1" showInStore="1"> ...
For now, Dark Mode is set to a global "No" value. However, the additions above now allow Dark Mode to be configured from the admin after selecting a scope other than "Default":
If we wished to change the default configuration value for a specific scope, we can also add a secondary value to our module's
<?xml version="1.0"?> <config ...> <default> ... </default> <!-- We can also use "websites" or "stores" here to target a specific scope. --> <websites> <base> <!-- The config here will only apply to the "base" website --> <web> <darkmode> <enable>1</enable> <!-- This tells web/darkmode/enable to default to 1 (Yes), but just for this specific website. Since default values are not defined for other websites, the web/darkmode/enable value will fall back to the "default" scope for these sites, which is currently set to 0 (No). --> </darkmode> </web> </base> </websites> </config>
The code above targets the "base" website. You can find the code to use for this value by going to Admin > Stores > Settings > All Stores, and looking at the values from the "Web Site" column for the "code" to target.
Similarly, you can target a specific store by using the
stores node in tandem with the related "code" from the "Store" column data.
All of the
config.xmlfiles in Magento are merged into one giant XML tree. If you wish to override third-party
config.xmlfiles, you can use the
<sequence>node in your module's
module.xmlfile to control the load ordering of modules. This controls the precedence of how XML nodes are merged together.
The last value in an XML tree always has precedence, so make sure your module loads "after" others to take control of the value you are targeting.
Database configurations take precedence
As of right now, all configuration properties are using the default values. But if we change & save a configuration value from the admin, it will save the value to a new row in the
core_config_data database table.
The last record in the
core_config_data database table stores the path of
web/darkmode/enable with the value of
1 set for the
Note that for
storesscopes, the database uses the
scope_idwith the numeric representation of the related scope.
You can find this numeric id in the
Once the data is saved in the database, it can additionally be retrieved using the built-in
bin/magento command with:
bin/magento config:show web/darkmode/enable
This command will return
1, which is the value retrieved from the
core_config_data database table for this property. It will not a return a value if there are no related records that exist in this database table.
We can also set a value for this configuration property by using the
bin/magento config:set web/darkmode/enable 0
Upon refreshing the contents of the
core_config_data records, we'll see that the record for this property did indeed update:
We can use the
--scope-code command parameters to define a scope other than "default".
For example, we can set a value for the "base" website with:
bin/magento config:set --scope=websites --scope-code=base web/darkmode/enable 1
As expected, this creates yet another database record in the
core_config_data table for the scope that was passed in. Note that the
scope_code is translated into the numeric representation for that code at the time of insertion:
Values in the
core_config_data database table always override values defined in the PHP or XML layer. This provides a fallback mechanism, but there are additional fallback processes in place for environment management.
config.php overrules database configurations
Magento defines "shared-configuration" settings within the file at
This file is committed to version control, and is shared among all environments. The configurations are stored in a multi-dimensional array under the
system index in the format:
return [ ... 'system' => [ 'websites' => [ 'base' => [ 'web' => [ 'darkmode' => [ 'enable' => '0', ], ], ], ], ], ];
This example uses the
websites scope, however you can also target
stores scopes as well. Be sure to write all configuration values as either
null or a string, even integer values such as the one above.
After adding or changing a value within this file, you'll notice an error on the frontend of your Magento site:
1 exception(s): Exception #0 (Magento\Framework\Exception\LocalizedException): The configuration file has changed. Run the "app:config:import" or the "setup:upgrade" command to synchronize the configuration.
Magento detects mismatches between changes of value in your database compared to this file. The fix is easy though, just run:
After importing these values, any code looking up the value for this configuration property will return the value as expected. However, it's important to know that the
core_config_data table will not be updated with values from the import.
Again testing with
$di->get('Magento\Framework\App\Config\ScopeConfigInterface')->getValue('web/darkmode', 'websites', 'base')
=> [ "enable" => "0", ]
There appears to be a bug with the
bin/magentoscript when retrieving the value with a specific scope, as
bin/magento config:show --scope=websites --scope-code=base web/darkmode/enablereturns
1. This was found during the writing of this article, and a bug will be filed with the Magento 2 GitHub repo shortly.
Defining a value within the
config.php file will lock the value from being edited in the admin:
This is useful if you wish to hard-code a value for a specific configuration property and scope, and not allow it to be controlled by an admin user.
There's yet another level of precedence though over
env.php overrules config.php
app/etc/env.php file acts in an extremely similar manner to the
app/etc/config.php file, using the same format:
return [ ... 'system' => [ 'websites' => [ 'base' => [ 'web' => [ 'darkmode' => [ 'enable' => '1', ], ], ], ], ], ];
There is a big difference between
env.php though, and that is the env.php file is not committed to version control, and this file is not shared among environments.
This allows you to define environment-specific changes to a configuration property value, so different environments can have different values. Similarly to
config.php, values defined in
env.php files are locked and are not editable from the admin.
Environment variables have the final word
Just when you thought this article was over, there is yet one more precedence to the fallback of a Magento configuration, and that is the use of an environment variable. This functions just like the
env.php file, but doesn't need to be set within this file. This can be useful with ephemeral, read-only servers, as well as some other specific cases.
Like everything else in Magento, setting a configuration value with an environment variable follows a very specific naming convention.
For default-scoped configurations:
For our Dark Mode configuration property, this would be set with:
You can read in the DevDocs about extended usage of environment variables to override configuration settings.
I hope you learned at least one new thing about the Magento configuration by reading this article.
If you liked it, please share it with your fellow Magento developers! You may also sign up below to get notified about new Magento-related blog posts.
Intrigued to learn more about Magento? Experience these 3 helpful things that can help you:
Create custom Magento checkout layout processors to modify jsLayout
Still using plugins to modify properties of jsLayout? Stop right now, and learn a more elegant way to customize the Magento checkout with your own custom layout processor.
Managing application state is always difficult, and knowing when to use local or external state can help guide a proper architectural decision for your Magento store.