
How Laravel loads config/ files
Behind the magic
My previous post delved into how Laravel loads your .env variables in your config/ files.
This turn, we should see how it loads the config/ files in your application! (TL;DR at the end!)
Everything is located in the following snippet of code:
Illuminate\Foundation\Bootstrap\LoadConfiguration
public function bootstrap(Application $app)
{
$items = [];
// First we will see if we have a cache configuration file. If we do, we'll load
// the configuration items from that file so that it is very quick. Otherwise
// we will need to spin through every configuration file and load them all.
if (file_exists($cached = $app->getCachedConfigPath())) {
$items = require $cached;
$loadedFromCache = true;
}
// Next we will spin through all of the configuration files in the configuration
// directory and load each one into the repository. This will make all of the
// options available to the developer for use in various parts of this app.
$app->instance('config', $config = new Repository($items));
if (! isset($loadedFromCache)) {
$this->loadConfigurationFiles($app, $config);
}
// Finally, we will set the application's environment based on the configuration
// values that were loaded. We will pass a callback which will be used to get
// the environment in a web context where an "--env" switch is not present.
$app->detectEnvironment(fn () => $config->get('app.env', 'production'));
date_default_timezone_set($config->get('app.timezone', 'UTC'));
mb_internal_encoding('UTF-8');
}
Before we start, here is an overview of the process:

There's not much left to say, let’s get started!
The cache
Like in the previous boot, Laravel always checks for the cache/ to see if there is anything there. We’re assuming we have nothing cached and we’re going for the standard flow.
The first thing that happens is that we call instance on the $app object. Which does what?
The implementation of the instance() method is not in the Illuminate\Foundation\Application but in the Illuminate\Container\Container class.
public function instance($abstract, $instance)
{
$this->removeAbstractAlias($abstract);
$isBound = $this->bound($abstract);
unset($this->aliases[$abstract]);
// We'll check to determine if this type has been bound before, and if it has
// we will fire the rebound callbacks registered with the container and it
// can be updated with consuming classes that have gotten resolved here.
$this->instances[$abstract] = $instance;
if ($isBound) {
$this->rebound($abstract);
}
return $instance;
}
All it seems to do is check if an object with the same alias, ‘config’, is loaded in the container. If it is, then it will replace it. Right now it adds the Repository class with a list of the configs ($items) in the container.
Load the configs!
The next step is simply to load each and every file received in the construction of the Repository.
if (! isset($loadedFromCache)) {
$this->loadConfigurationFiles($app, $config);
}
/**
* Load the configuration items from all of the files.
*
* @param \Illuminate\Contracts\Foundation\Application $app
* @param \Illuminate\Contracts\Config\Repository $repository
* @return void
*
* @throws \Exception
*/
protected function loadConfigurationFiles(Application $app, RepositoryContract $repository)
{
$files = $this->getConfigurationFiles($app);
if (! isset($files['app'])) {
throw new Exception('Unable to load the "app" configuration file.');
}
foreach ($files as $key => $path) {
$repository->set($key, require $path);
}
}
Diving in the getConfigurationFiles unravels something rather nice.
protected function getConfigurationFiles(Application $app)
{
$files = [];
$configPath = realpath($app->configPath());
foreach (Finder::create()->files()->name('*.php')->in($configPath) as $file) {
$directory = $this->getNestedDirectory($file, $configPath);
$files[$directory.basename($file->getRealPath(), '.php')] = $file->getRealPath();
}
ksort($files, SORT_NATURAL);
return $files;
}
It does the following:
- Created a path to the config/ files
- Restricts the finder to retrieve only files ending in .PHP
- Retrieves files recursively from nested directories (this is the fun part)
- Sort the configs
Now, we know it retrieves configs from nested directories! So we can make something like this:

And Laravel will still load them! You can access them by using their path, like this:
$kibanaAuth = config("elk.kibana.auth");
$elkAuth = config("elk.auth");
Now, after all of this, we got our config files ready to run!
[
"app" => "/home/user/example-app/config/app.php"
"auth" => "/home/user/example-app/config/auth.php"
"broadcasting" => "/home/user/example-app/config/broadcasting.php"
"cache" => "/home/user/example-app/config/cache.php"
"cors" => "/home/user/example-app/config/cors.php"
"database" => "/home/user/example-app/config/database.php"
"elk.auth" => "/home/user/example-app/config/elk/auth.php"
"elk.kibana.auth" => "/home/user/example-app/config/elk/kibana/auth.php"
"filesystems" => "/home/user/example-app/config/filesystems.php"
"hashing" => "/home/user/example-app/config/hashing.php"
"logging" => "/home/user/example-app/config/logging.php"
"mail" => "/home/user/example-app/config/mail.php"
"queue" => "/home/user/example-app/config/queue.php"
"sanctum" => "/home/user/example-app/config/sanctum.php"
"services" => "/home/user/example-app/config/services.php"
"session" => "/home/user/example-app/config/session.php"
"view" => "/home/user/example-app/config/view.php"
]
The next part of the code will stop execution if our config/app.php file is missing:
if (! isset($files['app'])) {
throw new Exception('Unable to load the "app" configuration file.');
}
If we have it, we just go through every value of every file and set it in an array inside the Repository:
/**
* All of the configuration items.
*
* @var array
*/
protected $items = [];
public function set($key, $value = null)
{
$keys = is_array($key) ? $key : [$key => $value];
foreach ($keys as $key => $value) {
Arr::set($this->items, $key, $value);
}
}
Our next step:
$app->detectEnvironment(fn () => $config->get('app.env', 'production'));
It passes a closure to the detectEnvironment method. The closure returns the config/app.php environment variable or ‘production’ if it’s not configured.
The detectEnvironment method is easy to understand:
public function detectEnvironment(Closure $callback)
{
$args = $_SERVER['argv'] ?? null;
return $this['env'] = (new EnvironmentDetector)->detect($callback, $args);
}
$args = $_SERVER['argv'] ?? null;
: Retrieves command line arguments from$_SERVER['argv']
if available, otherwise sets$args
tonull
.return $this['env'] = (new EnvironmentDetector)->detect($callback, $args);
: Calls thedetect()
method of anEnvironmentDetector
instance, passing the callback and command line arguments as parameters. The detected environment is then stored in the container ($this['env']
) and returned.
Lastly, we set the timezone of the application and its encoding:
date_default_timezone_set($config->get('app.timezone', 'UTC'));
mb_internal_encoding('UTF-8');
To sum it up…
- Laravel loads configuration files during bootstrapping using the
Illuminate\Foundation\Bootstrap\LoadConfiguration
class. - It checks for cached configuration files first, loading them if available for efficiency.
- If no cached files are found, it loads configuration files from the
config/
directory. - Configuration files are loaded recursively, even from nested directories within
config/
. - The
config()
helper function can be used to access configuration values, using dot notation for nested files. - The environment is detected using the
detectEnvironment()
method, which utilizes command line arguments if available. - The detected environment is stored and used to set the application’s timezone and encoding.
- Laravel’s configuration loading process is transparent and allows for flexible configuration management.