Application Configuration
Craft’s entire application configuration (opens new window) can be customized via config/app.php
. Any items returned by an app.php
config file will get merged into the main application configuration array.
You can further customize the application configuration for only web requests or console requests from config/app.web.php
and config/app.console.php
.
New Craft projects (opens new window) include a stub of app.php
to set an app ID and bootstrap an example Module. Most applications will not require significant application config.
# Approach
What follows differs in nature from other configuration you’ve likely encountered, so far: general and database config is handled via curated, publicly-documented settings that Craft pulls from to adjust its bootstrapping or runtime behavior. Application config, on the other hand, directly modifies components (opens new window) of the Craft application—either by customizing them in-place, or swapping them out with new ones.
Of course, this power comes with a greater risk of misconfiguration. Some components may also require a bit of independent research to discover available options; configuring a component (opens new window) is in most cases simply defining properties the class initializes with, so some degree of comfort with source-diving and class reference will go a long way.
Keep in mind that even when Craft doesn’t provide “defaults” explicitly in its own app.php
config files (or config helper methods), the underlying component classes may have default property values—or even compute defaults based on other values.
# Static
Most components’ config can be provided as a plain array, which gets merged with the default config map prior to initialization. This style is mostly used for customizing individual properties on a component:
<?php
return [
// ...
'components' => [
// Customize one property on an existing component:
'deprecator' => [
'throwExceptions' => App::env('HARD_MODE') ?: false,
],
],
];
# Closures
Craft uses functions to dynamically define some components—see app.php
(opens new window) and its app.web.php
and app.console.php
counterparts for a complete list in each context.
Components defined in this way should only be overridden with another function. The process will almost always look something like this:
- Call the same helper method that Craft uses internally to build the base config object
- Assign or reassign properties on it
- Pass it to
Craft::createObject()
to initialize the component
If your intent is to completely replace a component, you can opt out of this pattern and define a config map from scratch. This may be necessary when properties coming from the helper method are incompatible with the new component—such is the case with alternate cache drivers:
<?php
return [
// ...
'components' => [
// Adjust parts of a component based on the environment:
'db' => function() {
$config = craft\helpers\App::dbConfig();
if (App::env('HIGH_AVAILABILITY')) {
$config['replicaConfig'] = [
// ... Base replica DB configuration
];
$config['replicas'] = [
// ... unique config for each replica, like hosts or DSNs.
];
}
return Craft::createObject($config);
},
// Completely re-define a component without letting defaults leak in:
'cache' => function() {
$config = [
'class' => yii\redis\Cache::class,
'redis' => [
// ... Redis connection configuration
],
];
return Craft::createObject($config);
},
],
];
# Common Components
We’ll only cover a few commonly-customized components here. Refer to Craft’s own src/config/app.php (opens new window), app.web.php (opens new window) and app.console.php (opens new window) when determining what components are initialized for each type of request—for example, Craft uses two different request
component classes (craft\web\Request (opens new window) and craft\console\Request (opens new window)) to help smooth over some differences in Yii’s HTTP and CLI APIs.
# Cache
By default, Craft will store data caches on disk in the storage/runtime/cache/
folder. You can configure Craft to use alternative cache storage (opens new window) layer by overriding the cache
application component from config/app.php
.
To help avoid key collisions when sharing non-standard cache drivers between multiple applications, set a unique application id
. See the Craft starter project (opens new window) for an example of how this is configured, then run the following command to generate and append a CRAFT_APP_ID
value to your .env
file:
php craft setup/app-id
# Database Example
To store data caches in the database, create a cache
table as specified by yii\caching\DbCache::$cacheTable (opens new window). Craft provides a console command for convenience, which will honor your tablePrefix
setting:
php craft setup/db-cache-table
Once that’s done, you can set your cache
application component to use craft\cache\DbCache (opens new window).
<?php
return [
'components' => [
'cache' => function() {
$config = [
'class' => craft\cache\DbCache::class,
'defaultDuration' => Craft::$app->config->general->cacheDuration,
];
return Craft::createObject($config);
},
],
];
If you’ve already configured Craft to use yii\caching\DbCache (opens new window) rather than craft\cache\DbCache (opens new window), you can safely switch to the latter if you remove your cache
table’s dateCreated
, dateUpdated
, and uid
columns.
# APC Example
<?php
use craft\helpers\App;
return [
'components' => [
'cache' => function() {
$config = [
'class' => yii\caching\ApcCache::class,
'useApcu' => true,
'keyPrefix' => Craft::$app->id,
'defaultDuration' => Craft::$app->config->general->cacheDuration,
];
return Craft::createObject($config);
}
],
];
# Memcached Example
<?php
use craft\helpers\App;
return [
'components' => [
'cache' => function() {
$config = [
'class' => yii\caching\MemCache::class,
'useMemcached' => true,
'username' => App::env('MEMCACHED_USERNAME'),
'password' => App::env('MEMCACHED_PASSWORD'),
'defaultDuration' => 86400,
'servers' => [
[
'host' => App::env('MEMCACHED_HOST') ?: 'localhost',
'persistent' => true,
'port' => 11211,
'retryInterval' => 15,
'status' => true,
'timeout' => 15,
'weight' => 1,
],
],
'keyPrefix' => Craft::$app->id,
'defaultDuration' => Craft::$app->config->general->cacheDuration,
];
return Craft::createObject($config);
},
],
];
# Redis Example
To use Redis cache storage, install yii2-redis (opens new window) and replace your cache
component with yii\redis\Cache::class
, providing connection details to its redis
property:
<?php
use craft\helpers\App;
return [
'components' => [
'cache' => function() {
$config = [
'class' => yii\redis\Cache::class,
'keyPrefix' => Craft::$app->id,
'defaultDuration' => Craft::$app->config->general->cacheDuration,
// Full Redis connection details:
'redis' => [
'hostname' => App::env('REDIS_HOSTNAME') ?: 'localhost',
'port' => 6379,
'password' => App::env('REDIS_PASSWORD') ?: null,
],
];
return Craft::createObject($config);
},
],
];
If you are also using Redis for session storage or the queue, you should set a unique database
(opens new window) for each component’s connection to prevent inadvertently flushing important data!
# Database
If you need to configure the database connection beyond what’s possible with Craft’s database config settings, you can do that by overriding the db
component.
This example sets up read/write splitting by defining read replicas. The writer will be whatever is configured in config/db.php
.
<?php
use craft\helpers\App;
return [
'components' => [
'db' => function() {
// Get the default component config (using values in `db.php`):
$config = App::dbConfig();
// Define the default config for replica connections:
$config['replicaConfig'] = [
'username' => App::env('DB_REPLICA_USER'),
'password' => App::env('DB_REPLICA_PASSWORD'),
'tablePrefix' => App::env('CRAFT_DB_TABLE_PREFIX'),
'attributes' => [
// Use a smaller connection timeout
PDO::ATTR_TIMEOUT => 10,
],
'charset' => 'utf8',
];
// Define the replica connections, with unique DSNs:
$config['replicas'] = [
['dsn' => App::env('DB_REPLICA_DSN_1')],
['dsn' => App::env('DB_REPLICA_DSN_2')],
['dsn' => App::env('DB_REPLICA_DSN_3')],
['dsn' => App::env('DB_REPLICA_DSN_4')],
];
// Instantiate and return the configuration object:
return Craft::createObject($config);
},
],
];
The db
component is just Craft’s default database connection—but your application can connect to multiple databases by creating additional components.
For example, if you need to read some data from a legacy database as part of a migration, you could define a legacydb
component with its own connection settings. Then, any craft\db\Query (opens new window) execution methods can be called using the alternate component:
use craft\helpers\App;
return [
'components' => [
'legacydb' => [
'server' => App::env('LEGACY_DB_SERVER'),
// ...
],
],
];
Note that if you need to supply custom PDO attributes to your primary database connection (for example, setting up an SSL connection to your database), you should do so via the attributes
key in your config/db.php
file, not from config/app.php
while overriding the db
component.
# Log
Check out the guide on Logging for some detailed examples.
# Session
In a load-balanced environment, you may want to override the default session
component to store PHP session data in a centralized location.
The session
component must have the craft\behaviors\SessionBehavior (opens new window) behavior attached to provide methods that the system relies on. When configuring the component from scratch, you must explicitly include it by setting an as session
key to craft\behaviors\SessionBehavior::class
, where as session
is a Yii shorthand (opens new window) for attaching behaviors via a configuration object.
The session
component should only be overridden from app.web.php
so it gets defined for web requests, but not console requests.
# Redis Example
// config/app.web.php
<?php
use craft\helpers\App;
return [
'components' => [
'session' => function() {
// Get the default component config:
$config = craft\helpers\App::sessionConfig();
// Replace component class:
$config['class'] = yii\redis\Session::class;
// Define additional properties:
$config['redis'] = [
'hostname' => App::env('REDIS_HOSTNAME') ?: 'localhost',
'port' => 6379,
'password' => App::env('REDIS_PASSWORD') ?: null,
];
// Return the initialized component:
return Craft::createObject($config);
}
],
];
If you are sharing a Redis server between multiple components, you should set a unique database
(opens new window) for each one.
# Database Example
First, you must create the database table that will store PHP’s sessions. You can do that by running the craft setup/php-session-table
console command from your project’s root folder.
<?php
return [
'components' => [
'session' => function() {
// Get the default component config:
$config = craft\helpers\App::sessionConfig();
// Override the class to use DB session class:
$config['class'] = yii\web\DbSession::class;
// Set the session table name:
$config['sessionTable'] = craft\db\Table::PHPSESSIONS;
// Return the initialized component:
return Craft::createObject($config);
},
],
];
# Mailer
To override the mailer
component config (which is responsible for sending emails), do this in config/app.php
:
<?php
use craft\helpers\App;
return [
'components' => [
'mailer' => function() {
// Get the default component config:
$config = App::mailerConfig();
// Use Mailpit in dev mode:
if (Craft::$app->getConfig()->getGeneral()->devMode) {
$adapter = craft\helpers\MailerHelper::createTransportAdapter(
craft\mail\transportadapters\Smtp::class,
[
'host' => App::env('MAILPIT_SMTP_HOSTNAME'),
'port' => App::env('MAILPIT_SMTP_PORT'),
]
);
// Override the transport:
$config['transport'] = $adapter->defineTransport();
}
// Return the initialized component:
return Craft::createObject($config);
},
],
];
Any changes you make to the Mailer component from config/app.php
will not be reflected when testing email settings from Settings → Email.
# Queue
Craft’s job queue is powered by the Yii2 Queue Extension (opens new window). By default, Craft will use a custom queue driver (opens new window) based on the extension’s DB driver (opens new window).
Switching to a different driver by overriding Craft’s queue
component from config/app.php
will result in a loss of visibility into the queue’s state from the control panel. Instead of replacing the entire component, set your custom queue driver config on craft\queue\Queue::$proxyQueue (opens new window):
<?php
return [
'components' => [
'queue' => [
'proxyQueue' => [
'class' => yii\queue\redis\Queue::class,
'redis' => [
'hostname' => App::env('REDIS_HOSTNAME') ?: 'localhost',
'port' => 6379,
'password' => App::env('REDIS_PASSWORD') ?: null,
],
],
'channel' => 'queue', // Queue channel key
],
],
];
Available drivers are listed in the Yii2 Queue Extension documentation (opens new window).
Only drivers that implement craft\queue\QueueInterface (opens new window) will be visible within the control panel.
If your queue driver supplies its own worker, set the runQueueAutomatically config setting to false
in config/general.php
.
# Mutex
Craft uses the database for mutex (or “mutually exclusive”) locks 4.6.0+, which means it will work natively in load-balanced environments (opens new window).
Prior to 4.6, enabling devMode
would automatically switch from the default FileMutex
driver to a special NullMutex
driver to help avoid some virtualization bugs. Now, NullMutex
is only used when a database connection is not available (i.e. prior to installation).
You can configure a custom mutex driver by overriding the mutex
component’s nested $mutex
property:
return [
'components' => [
'mutex' => function() {
$generalConfig = Craft::$app->getConfig()->getGeneral();
$config = [
'class' => craft\mutex\File::class,
// Alter just this nested property of the main mutex component:
'mutex' => [
'class' => yii\mutex\FileMutex::class,
'fileMode' => $generalConfig->defaultFileMode,
'dirMode' => $generalConfig->defaultDirMode,
],
];
// Return the initialized component:
return Craft::createObject($config);
},
],
// ...
];
The specific properties that you can (or must) use in the configuration object will differ based on the specified mutex class—check the driver’s documentation for instructions.
The primary mutex component should always be an instance of craft\mutex\Mutex (opens new window). We’re only modifying the existing mutex
component’s nested driver property and leaving the rest of its config as-is!
# Modules
You can register and bootstrap custom Yii modules into the application from config/app.php
as well. See How to Build a Module for more info.
An empty Module is included in every starter project, and can be activated by adding its ID to the bootstrap
key of app.php
:
return [
// ...
'modules' => [
'my-module' => \modules\Module::class,
],
'bootstrap' => ['my-module'],
];
# Requests + Responses 4.11.0+
To set arbitrary headers on every site response, attach craft\filters\Headers (opens new window) to the root web application, in config/app.web.php
:
return [
// Attach the headers filter to the application:
'as headersFilter' => [
'class' => \craft\filters\Headers::class,
'site' => ['siteA', 'siteB'],
'headers' => [
// Define pairs of headers:
'Permissions-Policy' => 'interest-cohort=()',
'X-Foo' => 'Bar',
],
],
];
We also provide a CORS (opens new window)-specific filter (craft\filters\Cors (opens new window)) to manage server-side policies on a per-action basis:
return [
// Attach the CORS filter to the application:
'as corsFilter' => [
'class' => \craft\filters\Cors::class,
// Scope to specific sites (optional):
'site' => ['siteA', 'siteB'],
// CORS defaults for all non-CP requests:
'cors' => [
'Origin' => [
'https://my-project.ddev.site',
'https://es.my-project.ddev.site',
],
'Access-Control-Request-Method' => ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS'],
'Access-Control-Request-Headers' => ['*'],
'Access-Control-Allow-Credentials' => true,
'Access-Control-Max-Age' => 86400,
'Access-Control-Expose-Headers' => [],
],
// Controller/action-specific overrides (optional):
'actions' => [
'graphql/api' => [
'Origin' => ['*'],
'Access-Control-Allow-Credentials' => false,
],
],
],
];
With Dev Mode (opens new window) on, some potentially dangerous CORS misconfigurations will trigger exceptions.
Headers in action-specific overrides are not merged with global headers—they are only applied if the header was already set, globally!