# Craft CMS Documentation Craft CMS 5.x documentation covering installation, configuration, templating, and extension points. Last updated: 2026-10-08T11:00:56-07:00 (commit 540fe30ce) --- # Code of Conduct Like the technical community as a whole, the Craft team and community is made up of a mixture of professionals and volunteers from all over the world, working on every aspect of the mission - including mentorship, teaching, and connecting people. Diversity is one of our strengths. With that, challenges will arise that can lead to communication issues and unhappiness. To that end, we have a few ground rules that we ask people to adhere to. This code applies equally to founders, mentors and those seeking help and guidance. This isn’t an exhaustive list of things that you can’t do. Rather, take it in the spirit in which it’s intended - a guide to make it easier to enrich all of us and the technical communities in which we participate. This code of conduct applies to all spaces used by the Craft community for communication. This includes Craft Discord, Craft CMS Stack Exchange, GitHub, Twitter, Facebook, meetups, conferences, and any other relevant forums. Violations of this code outside of any spaces managed by the Craft team will still affect a person’s ability to participate within them. If you believe someone is violating the code of conduct, we ask that you report it by contacting us from [craftcms.com/contact](https://craftcms.com/contact). We take all reports seriously, and your identity will remain confidential. - **Be friendly and patient.** - **Be welcoming.** We strive to be a community that welcomes and supports people of all backgrounds and identities. This includes, but is not limited to members of any race, ethnicity, culture, national origin, colour, immigration status, social and economic class, educational level, sex, sexual orientation, gender identity and expression, age, size, family status, political belief, religion, and mental and physical ability. - **Be considerate.** Your work will be used by other people, and you in turn will depend on the work of others. Any decision you take will affect users and colleagues, and you should take those consequences into account when making decisions. Remember that we’re a world-wide community, so you might not be communicating in someone else’s primary language. - **Be respectful.** Not all of us will agree all the time, but disagreement is no excuse for poor behavior and poor manners. We might all experience some frustration now and then, but we cannot allow that frustration to turn into a personal attack. It’s important to remember that a community where people feel uncomfortable or threatened is not a productive one. Members of the Craft community should be respectful when dealing with other members as well as with people outside the Craft community. - **Be careful in the words that you choose.** We are a community of professionals, and we conduct ourselves professionally. Be kind to others. Do not insult or put down other participants. Harassment and other exclusionary behavior aren’t acceptable. This includes, but is not limited to: - Violent threats or language directed against another person. - Discriminatory jokes and language. - Exclusionary gendered language (guys, gentlemen, bros, etc). - Posting sexually explicit or violent material. - Posting (or threatening to post) other people’s personally identifying information ("doxing"). - Personal insults, especially those using racist or sexist terms. - Unwelcome sexual attention. - Advocating for, or encouraging, any of the above behavior. - Repeated harassment of others. In general, if someone asks you to stop, then stop. - **When we disagree, try to understand why.** Disagreements, both social and technical, happen all the time and Craft is no exception. It is important that we resolve disagreements and differing views constructively. Remember that we’re different. The strength of Craft comes from its varied community, people from a wide range of backgrounds. Different people have different perspectives on issues. Being unable to understand why someone holds a viewpoint doesn’t mean that they’re wrong. Don’t forget that it is human to err and blaming each other doesn’t get us anywhere. Instead, focus on helping to resolve issues and learning from mistakes. *Credits for the sources and inspiration of this code of conduct go to the [Django Project](https://www.djangoproject.com/conduct/) and many others.* --- # Configuring Craft Craft can be configured to work in a way that makes sense for you, your team, and your infrastructure. Broadly, configuration refers to how Craft will behave in a particular environment. Most applications will only require [database connection information](#database) to work, but as you get familiar with more of Craft’s features, install plugins, or start using additional services, you may need to provide additional config. We’ll start by looking at how Craft builds its config in a context-aware way, then get into some specific options. Keep in mind that—unlike most aspects of a Craft project—this will require some basic PHP knowledge. [Project config](https://craftcms.com/docs/5.x/system/project-config.html) is a discrete concept, but was designed to integrate with the core config system. We’ll look at some examples in just a moment. ## Where Configuration Happens ### Config Files The most common way to customize your Craft project is by editing files in the [`config/` folder](https://craftcms.com/docs/5.x/system/directory-structure.html). These files act as a canonical map of what customizations you’ve made to a project, and as a bridge between specific settings and [environment variables](#setting-and-resolving-options). | Concern | File(s) | Description | ------- | -------- | ----------- | [General Configuration](#general) | `general.php` | Global options that can affect the front-end, control panel, debugging, etc. | [Database Settings](#database) | `db.php` | Connection settings for your database. | [Custom Options](#custom-settings) | `custom.php` | Arbitrary key-value storage for your own options. | [Routing](#url-rules) | `routes.php` | Custom HTTP routes. | [Redirection](https://craftcms.com/docs/5.x/system/routing.html#redirection) | `redirects.php` | Additional patterns for redirection. | [Application Configuration](#application-configuration) | `app.php`, `app.web.php`, `app.console.php` | Overrides for the root application and any of its [Components](https://www.yiiframework.com/doc/guide/2.0/en/concept-components). | Plugin Settings | `{plugin-handle}.php`, or other custom files | Consult the plugin’s documentation for specifics. | [Advanced](#advanced) | | Specific library options and/or behaviors that may be exposed in a non-standard way, like [Guzzle](#guzzle) or the Twig [sandbox environment](#twig-sandbox). You may find other files in the `config/` folder, like `license.key` or the `project/` folder. Craft (and many plugins) will ask you to place config-adjacent files here, even if they don’t work in a consistent way. Sensitive credentials like your database’s password should be kept out of tracked files—but instead of ignoring config files outright, let’s take a look at some options. ### `.env` New [Craft projects](https://github.com/craftcms/craft) use [vlucas/phpdotenv](https://github.com/vlucas/phpdotenv) to load values into the environment from a `.env` file in the root of your project. A basic `.env` file contains pairs of keys and values: ```env CRAFT_APP_ID=my-project CRAFT_ENVIRONMENT=dev # ...and comments! ``` These values can be referenced in your config files by calling [App::env()](craft5:craft\helpers\App::env()), or using them directly in a [control panel setting](#control-panel-settings). Use of PHP’s `getenv()` directly is discouraged, due to [issues with thread-safety](https://github.com/craftcms/cms/issues/3631). The [`getenv()` Twig function](https://craftcms.com/docs/5.x/reference/twig/functions.html#getenv) is a wrapper around our custom `App::env()` method, and therefore fine to use in templates. Craft doesn’t require your variables to follow any kind of naming convention, but it will automatically discover [some specific environment variables](#environment-overrides) for general and database settings. For most installations, the `.env` file is the only place where secrets should be stored. Avoid checking it in to version control! Some platforms (especially those with ephemeral filesystems, like [Craft Cloud](https://craftcms.com/cloud)) provide a GUI for managing environment variables in lieu of using a `.env` file, and will automatically inject them when the server or process starts. `App::env()` is still the recommended method for retrieving environment variables set in this way. Once Craft has fully initialized and the current [site](https://craftcms.com/docs/5.x/system/sites.html) is known, it adds two additional environment variables: Since 5.9.0: $CRAFT_SITE and $CRAFT_SITE_UPPER variables | Variable Name | Value | | --- | --- | | `CRAFT_SITE` | The current site’s handle. | | `CRAFT_SITE_UPPER` | The current site’s handle, converted to `SCREAMING_SNAKE_CASE`. | #### Nested Variables In your `.env` file, one variable can reference another: ```bash BASE_HOSTNAME="acmelabs.com" PRIMARY_SITE_URL="https://${BASE_HOSTNAME}" GLOBAL_SITE_URL="https://global.${BASE_HOSTNAME}" ``` Depending on your infrastructure, this may also be possible at other points in process of loading environment variables. The example above works [thanks to `vlucas/phpdotenv`](https://github.com/vlucas/phpdotenv?tab=readme-ov-file#nesting-variables), the default `.env` file loader; Docker (and therefore DDEV) share this general syntax, but not all variables are available at each step as the container boots up—so interpolation is best left until this last stage. Earlier layers may allow interpolation to be escaped so that it is only evaluated by a later one: ```bash # Here, we escape the beginning interpolation token with a backslash (\): BASE_API_HOSTNAME="api.\${DDEV_HOSTNAME}" ``` Craft does not know or indicate when substitutions have occurred—it only sees the final, fully-resolved values. Variables with substitutions will only be written back into the environment when using one of the [mutable loaders](https://github.com/vlucas/phpdotenv?tab=readme-ov-file#immutability-and-repository-customization). [Control panel settings](#control-panel-settings) can also use interpolation. ### Entry Script Craft will also respond to a handful of [specific environment variables or PHP constants](#bootstrap-config), as long as they are set prior to bootstrapping the application in your entry script. The [starter project](repo:craftcms/craft) shares a `bootstrap.php` file between `web/index.php` and the `craft` executable to consolidate the definition of constants. ### Secrets You can store sensitive values that won’t leak into the process’s environment in a special “secrets” file. The path to this file is determined by the [`CRAFT_SECRETS_PATH` environment variable or constant](#craft_secrets_path). When defined, Craft will attempt to include a file at that path (presumed to be a PHP script that `return`s an associative array), and checks it prior to resolving any environment variable or constant with craft5:craft\helpers\App::env(). ```php return [ 'SUPER_SECRET_KEY' => '...', ]; ``` Even though secrets aren’t automatically loaded into the environment on every request, they can still be accessed via application helpers, Twig templates (like system messages), other PHP scripts, or anyone with direct filesystem access. This is only recommended in situations where environment variable exfiltration is among the last attack surfaces. If your server supports any form of remote access (say, via SSH), this is _not_ a practical security measure! ## Setting and Resolving Options Each setting accepts specific [types and values](#types-and-values) (like an integer, interval expression string, or boolean), but Craft can resolve them in two ways: - **Statically:** A value is set explicitly in a config file, and is the same in all environments. Example: Customizing file types that can be uploaded. - **Dynamically:** Values are only known at runtime, or are set conditionally based on the current environment. Typically, this will involve a call to [App::env()](craft5:craft\helpers\App::env()) using the name of an environment variable that is expected to exist—or whose absence is significant. Example: Dev mode, database connection details, or a storage bucket URL. ### Priority Craft takes the first discovered value, in this order: 0. **Environment Overrides:** For general and database settings, Craft looks for special [environment variables](#environment-overrides) and [PHP constants](#php-constants). 1. **Config Files:** Craft evaluates and merges [multi-environment](#multi-environment-configs) and [application type](#application-types) PHP config files. 2. **Defaults:** Every option has a default value, even if it’s `null`. You can find these defaults in the documentation for each setting. ### Style At its simplest, a config file returns a key-value map of settings: ```php use craft\helpers\App; return [ 'devMode' => App::env('DEV_MODE') ?? false, 'userSessionDuration' => 'P1D', ]; ``` If you are interested in auto-completion and type hints from your editor, the equivalent “fluent” style declaration would look like this: ```php use craft\config\GeneralConfig; use craft\helpers\App; return GeneralConfig::create() ->devMode(App::env('DEV_MODE') ?? false) ->userSessionDuration('P1D'); ``` Each option becomes a method call, accepting the same [values](#types-and-values) that you would provide in a config map. The configuration object is returned to allow chaining. Fluent config is currently available for _general_ and _database_ settings, but unsupported in plugins, [custom](#custom-settings) config files, and [application config](#application-configuration). When in doubt, use a config map! Config files can also return a closure that accepts a single `$config` argument and returns a config object. Since 5.3.0: Closures in config files ```php // config/general.php use craft\config\GeneralConfig; return function(GeneralConfig $config) { return $config ->aliases([ '@webroot' => dirname(__DIR__) . '/web', ]) // ... ; }; ``` Craft will always pass an instance of craft5:craft\config\GeneralConfig or craft5:craft\config\DbConfig to a closure in the primary general or database config files, respectively—but [application type-specific files](#application-types) should accept whatever the _primary_ file returns: ```php general.php return [ 'aliases' => [ '@webroot' => dirname(__DIR__) . '/web', ], ]; ``` ```php general.console.php return function(array $config) { $config['aliases']['@web'] = App::env('CLI_WEB_URL'); return $config; }; ``` There is no equivalent to `GeneralConfig` and `DbConfig` for [application](#application-configuration). If you return an array from the primary file, there is limited value in using a closure in an application type-specific file. The `GeneralConfig` class has a special `addAlias()` method that allows you to merge additional aliases, when using closures _and_ fluent config. ### Application Types Since 5.3.0: Application type-specific config files Craft has two primary application types—_web_ and _console_. Web requests are typically initiated by an HTTP server via `index.php`; console requests are initiated from the `craft` executable, on the command line. You can provide configuration that targets a specific application type by creating additional general, database, or application configuration files with the appropriate suffix:
Category | File | Application Type --- | --- | --- [General](#general) | `general.php` | All   | `general.web.php` | Web only   | `general.console.php` | Console only [Database](#database) | `db.php` | All   | `db.web.php` | Web only   | `db.console.php` | Console only [Application](#application-configuration) | `app.php` | All   | `app.web.php` | Web only   | `app.console.php` | Console only
The primary config file is always evaluated, but only one of the `web` or `console` files are merged on top of it, when present. See the previous section for some examples of how to combine application type-specific configuration and config styles. ### Types and Values Most config settings expect a [scalar](https://www.php.net/manual/en/function.is-scalar.php) value, and will generate exceptions if they are not (and can not be coerced to) a valid type. Normalization may occur on some values. For instance, any setting that expects a “file size” (like [`maxUploadFileSize`](config5:maxUploadFileSize)) will interpret a numeric value in bytes—but passing a string allows you to use other formats like `256M` or `1G` by virtue of Craft normalizing the value with [ConfigHelper::sizeInBytes()](craft5:craft\helpers\ConfigHelper::sizeInBytes()). A few settings support complex types, like arrays and closures: ```php use craft\config\GeneralConfig; return GeneralConfig::create() // Arrays: ->extraFileKinds([ 'fonts' => [ 'extensions' => ['otf', 'ttf', 'woff', 'woff2'], ], ]) // Functions or "closures": ->postLoginRedirect(function($siteHandle) { // Perform tests on the signed-in User: $user = Craft::$app->getUser()->getIdentity(); // Send to their account, if their profile is incomplete... if (empty($user->someProfileField)) { return 'account/profile'; } // ...or the homepage, by default: return '/'; }); ``` In this example, the function passed to `postLoginRedirect` will be called by [ConfigHelper::localizedValue()](craft5:craft\helpers\ConfigHelper::localizedValue()) with the current site’s handle, only *at the time the value needed*. This distinction is important, because Craft is not fully initialized when configuration files are *evaluated*, but will be by the time the application is ready to redirect a logged-in user. Keep in mind that while scalar values are automatically normalized during configuration, the return value of a function *is not*. Refer to a config property’s documentation for a full list of its supported types and values! ### Multi-Environment Configs Config files using the traditional map style can define configurations for each of your environments all in one place—called a “multi-environment config”. To establish that your config file should be treated as a multi-environment config, it must have a `*` key which defines the base config that should be applied to each of your environments, followed by additional keys which will be matched against the [`CRAFT_ENVIRONMENT`](https://craftcms.com/docs/5.x/reference/config/bootstrap.html#craft-environment) environment variable or PHP constant. When determining the exact configuration that should be used for a request, the base config and environment-specific config arrays will be merged together. If any config settings are defined by both arrays, the environment-specific config will take precedence. For example, given the following multi-environment config: ```php // -- config/general.php -- return [ '*' => [ // Applies to all environments: 'defaultWeekStartDay' => 1, 'omitScriptNameInUrls' => true, 'allowAdminChanges' => false, ], 'dev' => [ // Only applies to development environments, overriding anything in `*`: 'allowAdminChanges' => true, 'devMode' => true, ], 'production' => [ // Only applies to the production environment, overriding anything in `*`: 'cpTrigger' => 'secret-word', ], ]; ``` Environments matching `dev` would end up with the following merged config: ```php return [ 'defaultWeekStartDay' => 1, 'omitScriptNameInUrls' => true, 'allowAdminChanges' => true, 'devMode' => true, ]; ``` Make sure your key(s) are sufficiently unique! Craft reads your array of config settings from top to bottom, applying config settings wherever the `CRAFT_ENVIRONMENT` value *contains* the key. If the environment cannot be determined, your server’s hostname will be used. Do not combine fluent and multi-environment config in the same file. Merging fluent config models causes all previously-set values to be overwritten. ### Aliases Some settings and functions in Craft support [Yii aliases](guide:concept-aliases), which are most often used as placeholders for file system paths and URLs. Out of the box, Craft provides these aliases—but you can override them or provide new ones with the config5:aliases config setting: | Alias | Description | Based On | --- | --- | --- | `@app` | Path to Craft’s source code. | [CRAFT_VENDOR_PATH](https://craftcms.com/docs/5.x/reference/config/bootstrap.html#craft-vendor-path) | `@config` | Path to your `config/` folder. | [CRAFT_BASE_PATH](https://craftcms.com/docs/5.x/reference/config/bootstrap.html#craft-base-path) | `@contentMigrations` | Path to your `migrations/` folder. | [CRAFT_BASE_PATH](https://craftcms.com/docs/5.x/reference/config/bootstrap.html#craft-base-path) | `@craft` | Path to Craft’s source code. | `@app` | `@dotenv` | Path to your [.env](https://craftcms.com/docs/5.x/system/directory-structure.html#env) file. | [CRAFT_DOTENV_PATH](https://craftcms.com/docs/5.x/reference/config/bootstrap.html#craft-dotenv-path) | `@lib` | Path to extra libraries packaged with Craft. | `@app` | `@root` | The root project path. | [CRAFT_BASE_PATH](https://craftcms.com/docs/5.x/reference/config/bootstrap.html#craft-base-path) | `@runtime` | Path to your `storage/runtime/` folder. | `@storage` | `@storage` | Path to your `storage/` folder. | [CRAFT_STORAGE_PATH](https://craftcms.com/docs/5.x/reference/config/bootstrap.html#craft-storage-path) | `@templates` | Path to your `templates/` folder. | [CRAFT_TEMPLATES_PATH](https://craftcms.com/docs/5.x/reference/config/bootstrap.html#craft-templates-path) | `@tests` | Path to your `tests/` folder. | [CRAFT_TESTS_PATH](https://craftcms.com/docs/5.x/reference/config/bootstrap.html#craft-tests-path) | `@translations` | Path to your `translations/` folder. | [CRAFT_TRANSLATIONS_PATH](https://craftcms.com/docs/5.x/reference/config/bootstrap.html#craft-translations-path) | `@vendor` | Path to your `vendor/` folder. | [CRAFT_VENDOR_PATH](https://craftcms.com/docs/5.x/reference/config/bootstrap.html#craft-vendor-path) | `@web` | URL to the folder that contains the `index.php` file that was loaded for the request. | [CRAFT_WEB_URL](https://craftcms.com/docs/5.x/reference/config/bootstrap.html#craft-web-url) | `@webroot` | Path to the folder that contains the `index.php` file that was loaded for the request. | [CRAFT_WEB_ROOT](https://craftcms.com/docs/5.x/reference/config/bootstrap.html#craft-web-root) The `@web` alias is [determined automatically](kb:how-craft-determines-the-site-url), and generally should _not_ be overridden. Additionally, using `@web` in sites’ or asset volumes’ **Base URL** settings can lead to unpredictable behavior. Aliases can be set to plain strings, or to the content of an environment variable. Keep in mind that **aliases are resolved recursively**, so you can define one based on another (including those whose values came from the environment): ```php use craft\helpers\App; return [ 'aliases' => [ '@primaryUrl' => App::env('PRIMARY_SITE_URL'), '@shared' => App::env('SHARED_PATH'), '@uploads' => '@shared/web/uploads', '@assets' => '@primaryUrl/uploads', ], ]; ``` Assuming `PRIMARY_SITE_URL` is defined as `https://mydomain.com` and `SHARED_PATH` is `/var/www/releases/123/shared`, these aliases would evaluate to: - `@primaryUrl`: `https://mydomain.com` - `@shared`: `/var/www/releases/123/shared` - `@uploads`: `/var/www/releases/123/shared/web/uploads` - `@assets`: `https://mydomain.com/uploads` Recursive aliases are preferred to basic string interpolation, because they are evaluated at the time of _use_ rather than _definition_. Aliases are only resolved at the beginning of a string. ### Environment Overrides Craft allows some settings to be defined directly from environment variables, PHP constants, or secrets using the special `CRAFT_` prefix. Add the prefix to any [general config](https://craftcms.com/docs/5.x/reference/config/general.html) (`CRAFT_`) or [database connection settings](https://craftcms.com/docs/5.x/reference/config/db.html) (`CRAFT_DB_`) in [screaming snake case](https://dev.to/fission/screaming-snake-case-43kj). For example: - General: config5:allowUpdates → `CRAFT_ALLOW_UPDATES` - Database: [`port`](https://craftcms.com/docs/5.x/db.html#port) → `CRAFT_DB_PORT` - General: config5:testToEmailAddress → `CRAFT_TEST_TO_EMAIL_ADDRESS` Any environment variable or constant names that match a known config setting (using the formula above) will have the [highest priority](#priority)! The `CRAFT_` prefix is intended to reduce the likelihood of collisions with other environment variables that a server or platform might inject—it’s actually _inadvisable_ to use this prefix when defining custom variables that aren’t intended to be used as overrides. Enforcing a structure for database connection details (even with a multi-environment config file) can cause problems when working with others, or across environments. Overrides make it possible to use whatever connection parameters are available in the current environment, without having to maintain a config file just to map variables to config settings. If you are working with others and your `.env` isn’t checked in to version control (it probably shouldn’t be!), make sure you have a way of communicating what options are required to get your application running! A `.env.example` file is a great place to store keys and comments, without exposing secrets—and it lets you simply copy the file when spinning up a new environment. ## Using Configured Values Most configuration is applied automatically on every request, and requires no additional action. However, Craft provides a powerful way to use dynamically-resolved config values in other parts of the system. ### Control Panel Settings Most values in the **Settings** area of Craft’s control panel are recorded in [Project config](https://craftcms.com/docs/5.x/system/project-config.html). While this makes schema changes much easier to move between environments, it presents a challenge when something like a URL needs to change per-environment, or an API key leaks into YAML files. For this reason, Craft provides a way to bind system settings to dynamic aliases and environment variables. ![Craft’s autosuggest field, displaying a suitable match](./images/site-base-url-setting.png) Whenever you see this UI, you can provide a valid alias or environment variable name, in addition to plain values. Craft will always store and display the raw, unparsed value, but uses [App::parseEnv()](craft5:craft\helpers\App::parseEnv()) when the value is consumed. Here are some examples of settings for which dynamic values are useful: - **General Settings:** System Name, Status, and Time Zone; - **Sites:** Base URLs; - **Sections:** Preview Target URIs; - **Asset Filesystems:** Base URL, File System Path (Local only); - **Email:** System Email Address, Sender Name, Email Template path, SMTP credentials; Focusing one of these fields will immediately suggest some values. Type `$` followed by an environment variable’s name, or `@` followed by an alias to narrow the suggestions and find your placeholder. You may combine aliases and environment variables with additional path segments, so `@primaryUrl/uploads` and `$PRIMARY_SITE_URL/de` are both perfectly valid settings. Since 5.5.0: Combining environment variables with other path segments in project config values If a combination of alias and path is used frequently, though, it might make sense to [define a specific alias](#aliases) (like `@uploads`) and use that in the control panel, instead. Prior to Craft 5.5.0, this was only a feature of aliases; environment variables _could not_ be prepended to other values. Plugins can add support for environment variables and aliases in their settings as well. See [Environmental Settings](https://craftcms.com/docs/5.x/extend/environmental-settings.html) to learn how. #### Interpolation Since 5.9.0: Nested interpolation of environment variables Environment variables can be used _anywhere_ in a string that is ultimately passed to craft5:craft\helpers\App::env() or craft5:craft\helpers\App::parseEnv(). For example, you could parameterize the subdomain portion of a group of [sites](https://craftcms.com/docs/5.x/system/sites.html) **Base URL** as `https://$SUBDOMAIN_CORPORATE.$DOMAIN_BASE/en` or `https://$SUBDOMAIN_B2B.$DOMAIN_BASE/en`. An additional syntax (similar to platform-dependent [nested variables](#nested-variables)) brings dynamic resolution to settings stored in project config—but instead of concatenating values, Craft evaluates references inside `${...}` as part of building the variable name itself. Combined with the special [`CRAFT_SITE_UPPER` variables](#env), you can make settings like **System Email Address** more reactive: `$MAILER_FROM_${CRAFT_SITE_UPPER}` would first flatten `${CRAFT_SITE_UPPER}` to something like `ACME_CORPORATE`, then look for the environment variable `MAILER_FROM_ACME_CORPORATE`. A request to a different site might end up trying to resolve the variable `MAILER_FROM_ACME_LABS` or `MAILER_FROM_ACME_B2B`. ### Templates and Modules #### Accessing Config Values You can check the final resolved value of a setting in your templates or a module via the `Config` service. For example, if you wanted to switch some debugging information on or off, you could do the following: ```twig {# Output entry ID for debugging: #} {% if craft.app.config.general.devMode %} ID: {{ entry.id }} {% endif %} ``` ```php // Add `DevMode` header when active: if (Craft::$app->config->general->devMode) { Craft::$app->request->headers->set('DevMode', 'On'); } ``` #### Aliases and Environment Variables In the event you need to evaluate an alias or environment variable directly in Twig or PHP, Craft provides some helpers: ```twig {# Resolve an alias, recursively: #} {{ alias('@uploadsBaseUrl') }} {# Access a specific environment variable: #} {{ getenv('SOME_ENV_VAR') }} {# Fully resolve a value, as though it came from a control panel setting: #} {{ parseEnv(craft.app.config.custom.myDynamicValue) }} ``` ```php // Resolve an alias, recursively: Craft::getAlias('@uploadsBaseUrl'); // Access a specific environment variable: craft\helpers\App::env('SOME_ENV_VAR'); // Fully resolve a value, as though it came from a control panel setting: craft\helpers\App::parseEnv(Craft::$app->config->custom->myDynamicValue); ``` ## Config Categories To make config settings easier to find, they’re organized into a few groups. ### General [General config settings](https://craftcms.com/docs/5.x/reference/config/general.html) are set via the `config/general.php` file, or using special environment variables. ### Database Your [database connection settings](https://craftcms.com/docs/5.x/reference/config/db.html) are set via the `config/db.php` file, or using special environment variables. ### URL Rules You can define custom [URL rules](guide:runtime-routing#url-rules) in `config/routes.php`, and [redirection rules](https://craftcms.com/docs/5.x/system/routing.html#redirection) in `config/redirects.php`. Since 5.6.0: Redirection rules See [Routing](https://craftcms.com/docs/5.x/system/routing.html) for more details about Craft’s request handling behavior. ### Application Configuration Some projects may require customization of Craft [application components](https://craftcms.com/docs/5.x/reference/config/app.html) beyond what is exposed through specific config files. This is done directly via the `config/app.php` file. ### Advanced In rare cases, you may need to customize parts of Craft that don’t follow the normal configuration scheme. #### Guzzle Craft uses [Guzzle](http://docs.guzzlephp.org/en/latest/) to make HTTP requests, when… - …checking for Craft updates; - …sending a support request from the Craft Support widget; - …loading RSS feeds from the Feeds widget; - …working with assets on remote volumes, like Amazon S3; You can customize the settings passed to Guzzle when initializing these requests by creating a `guzzle.php` file in your `config/` folder. The file does not support Craft’s [multi-environment configuration](#multi-environment-configs) and should only ever return an array with your config overrides at the top level. ```php return [ 'headers' => ['Foo' => 'Bar'], 'query' => ['testing' => '123'], 'auth' => ['username', 'password'], 'proxy' => 'https://myproxy:1234', ]; ``` The options defined here will be passed into new `GuzzleHttp\Client` instances. See [Guzzle’s documentation](http://docs.guzzlephp.org/en/latest/) for a list of available options. To use a proxy for _all_ requests, set an [httpProxy](config5:httpProxy) in general config. This will get merged with the Guzzle configuration, and passed to the front-end for use by JavaScript, in the [control panel](https://craftcms.com/docs/5.x/system/control-panel.html). Setting a proxy only in Guzzle’s config will not affect Ajax requests! #### Twig Sandbox Since 5.9.0: Twig sandbox rendering When config5:enableTwigSandbox is _on_, some templates (like [system messages](https://craftcms.com/docs/5.x/system/mail.html#system-messages)) are rendered in a Twig environment governed by a strict security policy. You can customize this policy via a `twig-sandbox.php` config file, which is merged on top of [Craft’s defaults](repo:craftcms/cms/blob/5.x/src/config/twig-sandbox.php). ```php [ // Additional Twig tags to allow ... ], 'allowedFilters' => [ // Additional Twig filters to allow ... ], 'allowedFunctions' => [ // Additional Twig functions to allow ... ], 'allowedMethods' => [ // Keys are class names; // Values are arrays of methods to allow; craft\elements\Asset::class => [ 'getUploader', ], ], 'allowedProperties' => [ // Same format as the above. ], 'allowedClasses' => [ // Each item must be a fully-qualified class name. // Beware: *all* methods and properties are implicitly allowed! craft\web\twig\variables\CraftVariable::class, ], ]; ``` Internally, Craft uses PHP [attributes](https://www.php.net/manual/en/language.attributes.overview.php) to flag classes, methods, and properties as [allowed](craft5:craft\web\twig\AllowedInSandbox), so they do not appear in the default config array. If you find that sandboxed rendering is too restrictive, try adding features to the security policy one-by-one, _before_ completely disabling it. #### HTML Purifier JSON files containing valid [HTML Purifier configuration](https://htmlpurifier.org/live/configdoc/plain.html) can be added to `config/htmlpurifier/`. When creating a [Redactor](plugin:redactor) or [CKEditor](plugin:ckeditor) field, you can select one of your predefined purifier configs—or provide a one-off config object. The [`purify`](https://craftcms.com/docs/5.x/reference/twig/filters.html#purify) filter also accepts a reference to an existing config file or a complete config object. A simple config that scrubs everything but paragraph and anchor tags would look like this: ```json { "HTML.AllowedElements": "p, a", } ``` For security, any keys _not_ set will use their [defaults](https://github.com/ezyang/htmlpurifier/blob/master/plugins/phorum/config.default.php). Note that HTML Purifier expresses many options with dot notation, like `HTML.AllowedElements`. These are the literal keys, not an indication that keys should be nested! ### Custom Settings Settings defined in a `config/custom.php` file don’t map to or affect any built-in Craft features, but can useful to centralize data, flags, or secrets that otherwise don’t have a place to live. ```php return [ 'serviceBaseUrl' => 'https://api.service.com/v1', 'servicePublishableKey' => App::env('SERVICE_PUBLISHABLE_KEY'), 'servicePrivateKey' => App::env('SERVICE_PRIVATE_KEY'), ]; ``` Custom config follows the same multi-environment structure as other files, so you can use the resolved values in a predictable way in any context: ```twig {% set publishableKey = craft.app.config.custom.servicePublishableKey %} {% js "https://cdn.service.com/client.js?key=#{publishableKey}" %} ``` ```php $privateKey = Craft::$app->config->custom->servicePrivateKey; $client = Craft::createGuzzleClient([ 'base_uri' => Craft::$app->config->custom->serviceBaseUrl, ]); $client->post('/donations', [ 'auth' => ['apiuser', $privateKey], 'json' => [ 'amount' => 1000, ], ]); ``` If these settings need to be changed frequently, edited by a control panel user, or don’t depend on the environment, they may be a better fit for a [Global Set](https://craftcms.com/docs/5.x/reference/element-types/globals.html). ## Bootstrap Config Some customization is handled via special variables (set as PHP constants or environment vars) that Craft will take into account as it boots up. Depending on your installation, you may keep these in `web/index.php` and the `craft` CLI entry points, or consolidate common values into a single `required` file, as the [starter project](repo:craftcms/craft) does in its `bootstrap.php` file—they’ll get picked up as long as they’re set prior to calling `$app->run()`. By virtue of accessing these via craft5:craft\helpers\App::env(), Craft also honors values defined by your environment under the same names or keys. The majority of these settings are tied specifically to the structure of your project directory, though, and generally do not need to change between environments. Constants you set directly in `web/index.php` will only be defined for _web_ requests, while any you set in the `craft` executable will only be defined for _console_ requests. Use `bootstrap.php` to define constants for _all_ requests. Bootstrap Variable Reference - View a list of bootstrap variables that Craft uses. - reference/config/bootstrap --- # Hosting & Deployment In more cases than not, our recommendations for hosting and deploying a Craft website also describe effective processes for collaborating with teammates. Let’s start by reviewing some general advice on managing a Craft project. Cloud ## Workflow Our goal here is to define processes that reduce the likelihood of mistakes making it onto a live website, and to adopt systems that help recover [if/when they do](#common-pitfalls). ### Source Control No matter how you work—or who you work with—the single most important thing to do when dealing with code is to frequently check it in to [version control](https://www.atlassian.com/git/tutorials/what-is-version-control). Having regular checkpoints and backups can mitigate all kinds of unexpected losses and regressions. Pushing your work to a centralized repository (GitHub, GitLab, Bitbucket, or elsewhere) also means that getting new code onto a server during a deployment is dramatically simplified. ### System Updates + Project Config Craft uses Composer under the hood to install and [update](https://craftcms.com/docs/5.x/update.html) plugins and packages it depends on. Your [`composer.lock`](https://craftcms.com/docs/5.x/system/directory-structure.html#composerlock) file defines the _exact_ set of packages that your project uses, so tracking it in version control is essential. The lockfile is updated any time you run system updates or install a new plugin, making it possible for Composer to reconstruct your entire [`vendor/`](https://craftcms.com/docs/5.x/system/directory-structure.html#vendor) directory. In a similar manner, changes you make to your site’s settings and content model are tracked by [project config](https://craftcms.com/docs/5.x/system/project-config.html) and written to YAML files in the `config/project/` directory. These files should also be committed to version control. To stay on top of changes from your team, run these two commands _every time you pull new code_: 1. `composer install`: Ensures the `vendor/` directory contains the expected packages; 1. `php craft up`: Applies incoming project config changes; We’ll look at how these systems relate to a [typical deployment](#typical-process) in a moment. ### Content In most cases, your live site’s content should be treated as authoritative: code and configuration flow up (deployment), and content flows back down (database backups). Craft has a built-in backup tool, accessible from the **Utilities** screen of the control panel (for users with the correct permissions) or as a [console command](https://craftcms.com/docs/5.x/reference/cli.html#db-backup). There’s also a command for [restoring](https://craftcms.com/docs/5.x/reference/cli.html#db-restore) backups! It is not advisable to attempt merging database tables or otherwise combining local and live content—including completely squashing a live database with a development one. [Project Config](#system-updates-project-config) is Craft’s solution to diverging schema and content. If you ever need to prepare content ahead of a deployment (say, if you’ve created a new [single](https://craftcms.com/docs/5.x/reference/element-types/entries.html#singles) and can’t risk users seeing a partially blank page), consider using a [content migration](https://craftcms.com/docs/5.x/extend/migrations.html). ## Selecting a Host Craft will work on virtually any hosting platform that meets its [requirements](https://craftcms.com/docs/5.x/requirements.html), but choosing one that matches your expectations for performance and reliability—and your appetite for devops—is key. Check out our [introduction to hosting](kb:hosting-craft-101) Knowledge Base article if this is your first time launching a Craft project, or spin up a free seven-day trial of [Craft Cloud](https://craftcms.com/cloud)! Here are a few things to look out for when reviewing traditional hosting services: ### Recommendations - Choose a host that offers dedicated resources. This is sometimes referred to as a “virtual private server,” and will often include specific system information when selecting a service plan, like the number of CPU cores, RAM, and disk space allocated to your project. - For un-managed hosting, consider using a provisioning tool like [Laravel Forge](https://forge.laravel.com/), [ServerPilot](https://serverpilot.io/), or [Ploi](https://ploi.io/) to simplify setup and maintenance. ### Red Flags - “Shared” hosting may be the most affordable option, but often pits you against other tenants for resources on the same machine. - Lack of SSH access (i.e. FTP-only hosts) means you will be unable to run [console commands](https://craftcms.com/docs/5.x/reference/cli.html), and significantly limits your [deployment](#deployment) options. - While convenient, the presence of cPanel is often a sign that the host is re-selling resources, without control over the actual hardware. ### Advanced Options Craft plays well with non-traditional infrastructure, as well. Let’s look at some examples of how you might take advantage of a provider’s other services. #### Remote Databases Your database does not need to be on the same machine that serves web requests. If your provider offers hosted or “managed” databases, consider how your [database settings](https://craftcms.com/docs/5.x/reference/config/db.html) may differ—in most cases, the only difference is what hostname (or [`server`](https://craftcms.com/docs/5.x/reference/config/db.html#server)) you tell Craft to connect to. Keep in mind that introducing a network roundtrip for each database call can add up quickly. Profile requests with Yii’s debug toolbar to see how this impacts your load times! A remote database is one prerequisite for [scaling](#scalability) your server horizontally. #### Scalability Craft benefits from both vertical and horizontal scaling. Giving your server more compute power and RAM, tuning the HTTP server, and [offloading the database](#remote-databases) and other services (vertical scaling) can bring immediate performance improvements. [Distributing load](kb:configuring-load-balanced-environments) across multiple web servers (horizontal scaling) is also viable, thanks to Craft’s support for centralized [cache](https://craftcms.com/docs/5.x/reference/config/app.html#cache) and [session](https://craftcms.com/docs/5.x/reference/config/app.html#session) storage. If your site demands global distribution (and can’t be cached at the edge), you may be able to take advantage of read/write splitting, replication, and other [advanced database configuration](https://craftcms.com/docs/5.x/reference/config/app.html#database) options to improve performance across regions. Choosing the right asset storage medium or [filesystem](https://craftcms.com/docs/5.x/reference/element-types/assets.html#filesystems) is another critical component of hosting—especially when it comes to durability and availability. #### “Platform as a Service” Proprietary and open source cloud computing solutions are both options for hosting a Craft application. Services like [Heroku](https://heroku.com/) and the Digital Ocean [App Platform](https://www.digitalocean.com/products/app-platform) are designed to reduce your devops burden by provisioning and scaling resources, as well as integrating build pipelines, workers, release hooks, logs, or alerts. ## Deployment Broadly, we’re defining _deployment_ as the process of publishing code changes to a live website. For the following examples, we’ll assume your project uses the standard [directory structure](https://craftcms.com/docs/5.x/system/directory-structure.html). Be sure and read our [Deployment Best Practices](kb:deployment-best-practices) article for some high-level recommendations. What follows is intended for technical users who are tasked with extending their workflow to a web server. ### Typical Process Let’s look at how to apply a Craft-centric [workflow](#workflow) to deployments. To provide concrete examples, we’ll split the process into three phases: 1. **[Build](#build):** Compile all the code that makes the project run. 1. **[Release](#release):** Expose your new build to traffic. 1. **[Migrate](#migrate):** Bring the database into agreement with the new code. Once these definitions are clear, check out some [examples](#examples). These phases aren’t _necessarily_ discrete or sequential, but [isolating each step](#atomic-deployment) can make recovery from a failed deployment much quicker and easier. #### One-time Setup Before you deploy a project for the first time, there is apt to be some setup. This might mean uploading an initial batch of assets, creating some config files, setting up SSH keys, importing a database backup, or connecting a service to GitHub. These requirements will emerge as you decide on the specifics of your deploy process—and where each phase of it happens. #### Build The build phase can vary widely between sites, tools, and platforms. Our primary goal in this phase is to create a fresh copy of your site’s code in as near-complete a state as possible. This will often involve: - Cloning the latest code from your repository; - Installing Composer dependencies; - Rendering other artifacts like CSS and JavaScript; Compiling your application may happen any number of places: locally; directly on the live server; or on a separate cloud-based action runner like [Buddy](https://buddy.works) or [GitHub Actions](https://github.com/features/actions). #### Release This phase is only concerned with replacing the running website with the new build. In some cases, the _build_ and _release_ phases may partially or completely overlap; in others, a release could involve rerouting traffic to an entirely new container! This is also a great time to [clear caches](https://craftcms.com/docs/5.x/reference/cli.html#clear-caches)—cached data and templates may be incompatible with the new build. #### Migrate The final step in any successful deployment should be running migrations. Craft can apply database migrations and project config changes in a single command: ```bash php craft up --interactive=0 ``` Before any pending migrations are applied, Craft will capture a database backup. ### Examples With a generic deployment framework in place, we’re ready to get into a few concrete examples. #### Simple Git Let’s assume you’ve cloned your project onto a host, and configured it to serve requests directly out of the `web/` directory. Within the project’s root directory, a simple Git-based deployment might look like this: ```bash # Fetch new code: git pull # Install dependencies: composer install --no-interaction # Run migrations and apply project config: php craft up --interactive=0 # Build front-end artifacts (less urgent / lower priority): npm install && npm run build ``` Here, the build and release steps are basically combined: updates are integrated directly into the live codebase. For many sites, this is a totally viable process! If you ever need to roll back after a broken deploy, just check out an earlier commit, run `composer install` (and the `npm` commands, if necessary), and—if Craft had run migrations—restore the automatic database backup from `storage/backups/`. Take care to not expose your `.git` directory (or other private files) to the web! #### Atomic Deployment Let’s look at some ways we might improve upon this simple deployment scheme. Our main goal will be to separate each phase of the deployment, so that the live site spends as little time as possible in an intermediate or partially-updated state. The combination of `git pull` and `composer install` are the most problematic—but we should also make an effort to pre-build front-end resources. Instead of doing all this work directly in a live directory, let’s move it off to the side. Suppose your project lives at `/var/www/craft-project/`, and Apache or nginx is configured to use `/var/www/craft-project/web/` as your web root. In `/var/www/`, create two new folders: ```treeview{3,4} /var/www/ ├── craft-project/ ├── releases/ └── shared/ ``` Within `/var/www/`, a deployment might follow these steps: ```bash # Get a timestamp to identify this release: DATE="$(date +'%Y-%m-%d--%H%M%S')" # -> 2023-01-01--123456 # Define paths we’ll be working with: WORKING="/var/www" # Root deploy directory RELEASE="$WORKING/releases/$DATE" # New release directory CURRENT="$WORKING/current" # Live release “link” # Shallow-clone a fresh copy of the latest project files into our release directory: git clone --depth=1 git@github.com/organization/repo $RELEASE # Move into the release directory: pushd $RELEASE # Install Composer packages: composer install --no-interaction # Build other artifacts: npm install npm run build # Move back out to the main directory: popd # Create a symbolic link to the new release: ln -sf $RELEASE $CURRENT ``` After a deployment, the working directory will look like this: ```treeview{3,5} /var/www/ ├── craft-project/ ├── current -> /var/www/releases/2023-01-01--123456/ ├── releases/ │ ├── 2023-01-01--123456/ │ └── ... └── shared/ ``` Don’t want to bother with Git, Node, or Composer on your server? The entire “build” step of an atomic deployment can be done elsewhere, then uploaded to the `releases/` directory all at once. There are a few more pieces to this puzzle, though: - How do we actually serve our new deployment? - How we can we persist files between releases? - What about migrations? The first is straightforward: your HTTP server’s web root must be updated to`/var/www/current/web/`, taking advantage of the stable [symbolic link](https://en.wikipedia.org/wiki/Symbolic_link) created by the last step in the deployment script. This is a _one-time change_ to the server’s configuration—subsequent deploys will just replace the `current` symlink! We can also address persistent files with symbolic links. For each file or directory you want to keep between deployments, add a line just before the final `current` link is created. For the sake of clarity, we’ll also define a new variable pointing to the `shared/` directory we created earlier: ```bash # ... SHARED="$WORKING/shared" # Create links to persistent files: ln -s "$RELEASE/.env" "$SHARED/.env" ln -s "$RELEASE/web/uploads" "$SHARED/web/uploads" # Create a symbolic link to the new release: ln -sf $RELEASE $CURRENT ``` Before this will work, you’ll have to move the “shared” files out of `craft-project/` and into the `shared/` directory. The sub-paths within `shared/` don’t have to be identical, but they should make sense to you and must agree with the script: ```treeview{6,8,13,15} /var/www/ ├── craft-project/ ├── current → /var/www/releases/2023-01-01--123456/ ├── releases/ │ ├── 2023-01-01--123456/ │ │ ├── .env → /var/www/shared/.env │ │ ├── web/ │ │ │ ├── uploads/ → /var/www/shared/web/uploads/ │ │ │ └── ... │ │ └── ... │ └── ... └── shared/ ├── .env └── web/ └── uploads/ └── ... ``` Lastly, migrations and project config may still take a moment to apply—it’s up to you to determine how much downtime your project can tolerate! The only thing different from the simple deployment is the `craft` executable path: ```bash # ... # Run migrations and apply project config: php $RELEASE/craft up --interactive=0 ``` With an atomic deployment, rollbacks are trivial—the symlink can just be replaced with a previous release, because they’re left exactly as-is. This is just a blueprint for an “atomic” deployment—there are countless ways to improve upon it for a given site or server. Tools like [Deployer](https://deployer.org/) (a PHP package, itself) provide an abstraction for deployment that is more similar to configuration than scripting, and services like [Laravel Envoyer](https://envoyer.io) roll everything into a neat, hosted solution. #### Fully-Integrated Platform On PaaS products like Heroku, you are often only responsible for connecting a repository—the platform figures out what steps are required to build the application and the underlying server “image.” In these cases, the presence of `composer.json` is often enough to indicate that the image needs a PHP runtime and that `composer install` must be run to gather dependencies. This paradigm also turns the “release” phase on its head: instead of deploying a bundle of _code_, the _entire server image_ (or images, if scaled horizontally) is cycled into the pool of resources and new requests are routed to it—effectively eliminating downtime. Heroku is just an example of this model, pioneered by Kubernetes and other infrastructure orchestration technologies. Adopting all or parts of the [twelve-factor app](https://12factor.net/) paradigm can be a little disorienting. In particular, an [ephemeral filesystem](https://craftcms.com/docs/5.x/reference/config/bootstrap.html#craft_ephemeral) means you will need to adjust the way environment variables are set (in lieu of `.env`), how the cache works, and how sessions are stored. Some of these options are discussed in [advanced application configuration](https://craftcms.com/docs/5.x/reference/config/app.html). ## Common Pitfalls Sometimes, bad things do happen—despite all our preparation. Here are some common issues you might encounter when working with a hosted Craft site. ### Backups Draft a recovery plan and test it regularly. Backups that exist only on the machine that is being backed up aren’t really backups! Make sure backups that are part of your recovery plan are copied to at least one other off-site location. ### Admin Changes config5:allowAdminChanges should be disabled in all but development environments. If administrators are allowed to make project config changes directly on a live website without oversight, their changes may be reverted the next time you deploy. ### Dev Mode config5:devMode is an invaluable debugging tool for local development—but it’s a performance and security liability on a public-facing website. Keep it off by default, and enable it only via you local `.env` file, by adding `CRAFT_DEV_MODE=true`. Even when `devMode` is off, you can still get profiling data from the Yii debug toolbar by enabling it in your user’s preferences. ### Be Aware of Artifacts Not everything that comprises a Craft site is (or should be) tracked in version control: - Assets uploaded to a local [filesystem](https://craftcms.com/docs/5.x/reference/element-types/assets.html#filesystems) via the [control panel](https://craftcms.com/docs/5.x/system/control-panel.html); - Database backups (stored in [`storage/backups/`](https://craftcms.com/docs/5.x/system/directory-structure.html#storage)); - Secrets (usually [relegated to `.env`](https://craftcms.com/docs/5.x/configure.html#env)); - Caches and other temporary files; - Compiled CSS or JavaScript; See the official [starter project](repo:craftcms/craft)’s `.gitignore` file for a more complete list of files we recommend excluding from version control. When introducing new code onto your server, consider which of the above files should be kept in place and which can be discarded. Fully replacing your web root or project folder is rarely the right move: you shouldn’t need to rebuild configuration or copy files back in after deploying new code, and your client’s uploads should never be deleted or overwritten. Similarly, if you are using Git directly on a server to pull new code in, how do you reconcile unforeseen changes that happen on-disk? ### Cloud Storage There are a ton of benefits to using a remote filesystem for your assets, but care must be taken when managing files in a development environment: deleting or moving assets via the control panel can cause problems the live environment to lose track of files in the storage bucket and may lead to broken links. ### Precision + Judgement Deployments requiring careful timing, quick decision-making, manual intervention, or other non-automated steps should be avoided. Even if the process is well-defined, understood, and practiced, the door remains open to human error. ### The Queue If you use a [daemonized queue runner](https://craftcms.com/docs/5.x/system/queue.html#daemon) (based on `systemd` or `service`, for instance), it will need to be restarted to pick up the latest application code. [CRON-driven queues](https://craftcms.com/docs/5.x/system/queue.html#cron) load the application at each interval and do not need any special treatment. If the path to your application’s `craft` executable changes on each deployment, consider setting up a reliable symlink, or make the appropriate changes to service configuration files or your `crontab`! --- # Licensing & Editions Every Craft project starts with a free **Solo** license, which is valid for public and private use, indefinitely. Additional features like user management are available by [upgrading](#upgrading) to a paid **Team** or **Pro** license. The current Craft edition and version are displayed at the bottom of every control panel screen. Throughout the documentation, edition-specific features are called out with Team and Pro badges. If you don’t see a badge on a page or section, it describes features available to all Craft editions. Features & Pricing - Read more about pricing for Craft editions and services. - https://craftcms.com/pricing ## Upgrading To try out a paid Craft edition, visit the **Plugin Store** in the [control panel](https://craftcms.com/docs/5.x/system/control-panel.html#plugin-store), and click **Upgrade Craft CMS** in the sidebar: Screenshot of Craft’s in-app plugin store opened to the editions screen Selecting an edition will update [project config](https://craftcms.com/docs/5.x/system/project-config.html) and replace the **Try** button with a green **Installed as a trial** message. ## Plugins Some plugins have editions, as well! The [Plugin Store](https://plugins.craftcms.com/) displays editions in the sidebar, each having a **Try** button. Check the **Editions** tab for more information from the developer. ## Trials You are allowed to test paid Craft and plugin editions on private domains for as long as you need. However, deploying an unlicensed edition of Craft to a [public domain](kb:how-craft-license-enforcement-works#public-domains) will cause the control panel to display licensing issues to all [administrators](https://craftcms.com/docs/5.x/system/user-management.html#admin-accounts). Craft CMS & Plugin Trials - Learn how to test and license paid Craft and plugin editions. - https://craftcms.com/knowledge-base/how-to-trial-craft-cms-and-plugin-editions --- # Installation The prevalence of modern, mature PHP development tools and infrastructure makes Craft easy to install, run, [upgrade](https://craftcms.com/docs/5.x/upgrade.html), and [deploy](https://craftcms.com/docs/5.x/deploy.html). This [quick-start](#quick-start) guide focuses solely on setting up a local Craft development environment. If you’re ready to launch, jump to the [hosting](#hosting) or [deployment](#deployment) section. If at any point you feel stuck, the [Tutorial](https://craftcms.com/docs/5.x/../getting-started-tutorial/index.html) is a comprehensive guide for _anyone_ who wants to get set up with a fast, reliable development environment. Downloading or installing Craft by any means binds you to its [license](https://craftcms.com/license). ## Quick-Start Your journey with Craft begins on your local machine, using [DDEV](https://ddev.readthedocs.io/en/stable/). DDEV is a Docker-based PHP development environment that streamlines the creation and management of resources required by a Craft project. While we [strongly recommend](#why-ddev) DDEV for new Craft projects, [alternate installation methods](#alternative-installation-methods) are available for anyone with a preexisting environment or preferred workflow that meets its [requirements](https://craftcms.com/docs/5.x/requirements.html). [Install or update DDEV](https://ddev.readthedocs.io/en/stable/users/install/), then follow these steps: 1. Create a project directory and move into it: ```bash mkdir my-craft-project cd my-craft-project/ ``` 1. Create DDEV configuration files: ```bash ddev config --project-type=craftcms --docroot=web ``` 1. Scaffold the project from the official [starter project](https://github.com/craftcms/craft): ```bash ddev composer create-project "craftcms/craft" ``` The setup wizard will start automatically! Accept all defaults (in `[square brackets]`), and note your chosen username and password. Our [First-Time Setup](kb:first-time-setup) guide in the Knowledge Base has more information about what to expect during setup. Congratulations! You now have a fully-functional Craft application installed and configured. Run `ddev launch` to view the starter project’s welcome screen: A new Craft installation’s welcome screen ## Next Steps Ready to dive in? Sign in to the [control panel](https://craftcms.com/docs/5.x/system/control-panel.html) by clicking **Go to your control panel** 1 from the welcome screen, or running `ddev launch admin`. The username and password you provided during [setup](kb:first-time-setup) were used to create the first admin user. You’re welcome to explore things at your own pace—but here are some great starting points: - Get familiar with the [directory structure](https://craftcms.com/docs/5.x/system/directory-structure.html) that was created during installation; - Review [configuration](https://craftcms.com/docs/5.x/configure.html) methods and options; - Explore Craft’s main content tools: [elements](https://craftcms.com/docs/5.x/system/elements.html) and [custom fields](https://craftcms.com/docs/5.x/system/fields.html); - Run a [console command](https://craftcms.com/docs/5.x/reference/cli.html) using `ddev craft ...` to explore the CLI; - Discover [plugins](https://craftcms.com/docs/5.x/system/plugins.html) to add features or integrate with other services; - Find help and inspiration within our vibrant [community](https://craftcms.com/community)! Done for the day? [`ddev stop`](https://ddev.readthedocs.io/en/stable/users/basics/commands/#stop) will spin down any containers for the project and free up system resources. [`ddev start`](https://ddev.readthedocs.io/en/stable/users/basics/commands/#start) boots everything back up, right where you left off. ### Workflow + Collaboration We believe that starting with a local development environment (rather than directly on a remote server) fosters a workflow that will support the reliability and longevity of your project. Defining a Workflow - deploy.md#workflow To get a collaborator set up, commit your working folder to git (including the `.ddev/` directory) and create a [database backup](https://craftcms.com/docs/5.x/reference/cli.html#db-backup). Have them clone the project, download the backup, and run: ```bash ddev start ddev import-db < path/to/backup.sql ``` ## Further Reading ### Why DDEV? DDEV is our recommended development environment because it isolates software required to run Craft from your local machine—and from your other projects. Each of your sites can be spun up with its own database and PHP version, without the need to manage or switch between specific software packages. The environment for each project is [defined as YAML files](https://ddev.readthedocs.io/en/stable/users/configuration/config/), meaning `ddev start` is usually the only thing required to start working on a project on a new machine—or with a collaborator. The Docker requirement is not taken lightly! We believe that this one-time installation is much more sustainable for new and returning Craft developers than managing a bare-metal development environment—locally or on a remote server. ### Alternative Installation Methods Depending on your experience and preferred development environment, you may find one of these platform-agnostic installation processes more comfortable: - For most environments meeting Craft’s requirements, see [Using the Starter Project](kb:using-the-starter-project) to get started with Composer. - Especially adventurous users may want to read about [Setting up a Craft Project from Scratch](kb:setting-up-a-craft-project-from-scratch). ### Hosting The fastest way to share your work is with a free seven-day trial of our first-party hosting platform, [Craft Cloud](https://craftcms.com/cloud). Selecting a Host - Know your options when looking for a good hosting solution. - deploy.md Craft’s own footprint is relatively light, but it’s important to choose a platform that matches your traffic, storage, and redundancy needs. We maintain a [list of Craft-friendly hosting products](kb:hosting-craft-101) for projects of varying scale. ### Deployment deploy.md There is no one-size-fits-all deployment strategy for a Craft project, but we’ve collected our most salient advice in the [Deployment Best Practices](kb:deployment-best-practices) Knowledge Base article. Regardless of your target infrastructure, it’s important to define a workflow for yourself and your collaborators. Starting a project from a local environment sets a precedent for the flow of code and configuration; while it is _possible_ to scaffold a project directly on a remote host, maintaining a single source of truth for the site will become difficult with changes being made in multiple places, by multiple parties, or without a means of testing those changes in isolation. ### Troubleshooting Having trouble with DDEV? Make sure your [Docker](https://ddev.readthedocs.io/en/stable/users/install/docker-installation/#testing-and-troubleshooting-your-docker-installation) installation is working properly, then head to their [troubleshooting](https://ddev.readthedocs.io/en/stable/users/basics/troubleshooting/) page for specific issues. See the [Troubleshooting a Failed Craft Installation](kb:troubleshooting-failed-installation) Knowledge Base article for more common installation hang-ups. --- # About Craft CMS Craft is a flexible, user-friendly CMS for creating custom digital experiences on the web—and beyond. People who use Craft love its… - …intuitive, user-friendly [control panel](https://craftcms.com/docs/5.x/system/control-panel.html) for content creation and administrative tasks; - …clean-slate approach to content modeling and [front-end development](https://craftcms.com/docs/5.x/development/index.html) that doesn’t make any assumptions about your content or how it should be delivered; - …official Plugin Store with hundreds of free and commercial [plugins](https://plugins.craftcms.com/); - …robust framework for [plugin development](https://craftcms.com/docs/5.x/extend/index.html); - …active, vibrant [community](https://craftcms.com/community); You can learn all about it at [craftcms.com](https://craftcms.com). ## Getting Started Ready to try out the latest version of Craft? Spin up a [new project](https://craftcms.com/docs/5.x/install.html) or [upgrade](https://craftcms.com/docs/5.x/upgrade.html) an existing one. The documentation has been rearranged a bit for this release, in order to better highlight the breadth of Craft’s features and consolidate technical material: - High-level concepts are more visible in the [**System**](https://craftcms.com/docs/5.x/system/index.html) section; - [**Reference**](https://craftcms.com/docs/5.x/reference/index.html) material has been given a dedicated space, including [element types](https://craftcms.com/docs/5.x/system/elements.html), [field types](https://craftcms.com/docs/5.x/system/fields.html), [console commands](https://craftcms.com/docs/5.x/reference/cli.html), controllers, Twig features, and [configuration](https://craftcms.com/docs/5.x/configure.html). - Some pages geared toward front-end development have been moved into the [**Development**](https://craftcms.com/docs/5.x/development/index.html) section, including lots of new information on [eager-loading](https://craftcms.com/docs/5.x/development/eager-loading.html) and [collections](https://craftcms.com/docs/5.x/development/collections.html); - Information about using [forms](https://craftcms.com/docs/5.x/development/forms.html) has been split into a dedicated page, while specific [controller actions](https://craftcms.com/docs/5.x/reference/controller-actions.html) remain in the **Reference** section. ## Tech Specs Craft is a self-hosted PHP application, built on [Yii 2](https://www.yiiframework.com/). It can connect to MySQL and PostgreSQL [databases](https://craftcms.com/docs/5.x/reference/config/db.html) for content storage. Server-side rendering is powered by [Twig](https://twig.symfony.com), and headless applications have access to content via a [GraphQL API](https://craftcms.com/docs/5.x/development/graphql.html). View System Requirements - Craft runs best on the latest PHP and MySQL or Postgres versions, but can be configured to work on most modern hosting platforms. - requirements.md ## Diving In Here are some great ways to begin your journey with Craft: ## Resources --- # Requirements Craft is a PHP application that uses a relational database for content storage. It will run on most modern hosting environments, and can be configured to take advantage of all kinds of advanced infrastructure. You can use the official [server check](https://github.com/craftcms/server-check) script to quickly find out if your server meets Craft’s requirements. ## Minimum System Specs - PHP 8.2+ - MySQL 8.0.17+ using InnoDB, MariaDB 10.4.6+, or PostgreSQL 13+ - 256MB+ memory allocated to PHP - 200MB+ free disk space - Composer 2.0+ ## Recommended System Specs - PHP 8.2+ - MySQL 8.0.36+ using InnoDB, or PostgreSQL 16+ - 512MB+ of memory allocated to PHP Due to its diverging parity with MySQL, we no longer recommend MariaDB for sites with many users or a large volume of content. ## Required PHP Extensions - [BCMath](https://www.php.net/manual/en/book.bc.php) - [ctype](https://secure.php.net/manual/en/book.ctype.php) - [cURL](http://php.net/manual/en/book.curl.php) - [GD](http://php.net/manual/en/book.image.php) Since 5.9.0 - [iconv](http://php.net/manual/en/book.iconv.php) - [Intl](http://php.net/manual/en/book.intl.php) - [JSON](http://php.net/manual/en/book.json.php) - [Multibyte String](http://php.net/manual/en/book.mbstring.php) - [OpenSSL](http://php.net/manual/en/book.openssl.php) - [PCRE](http://php.net/manual/en/book.pcre.php) - [PDO MySQL Driver](http://php.net/manual/en/ref.pdo-mysql.php) or [PDO PostgreSQL Driver](http://php.net/manual/en/ref.pdo-pgsql.php) - [PDO](http://php.net/manual/en/book.pdo.php) - [Reflection](http://php.net/manual/en/class.reflectionextension.php) - [SPL](http://php.net/manual/en/book.spl.php) - [Zip](http://php.net/manual/en/book.zip.php) - [DOM](http://php.net/manual/en/book.dom.php) We recommend [ImageMagick](http://php.net/manual/en/book.imagick.php) over GD for expanded [image handling options](https://craftcms.com/docs/5.x/development/image-transforms.html). When multiple image drivers are available on your platform, Craft prefers ImageMagick. You can explicitly select a driver with the config5:imageDriver setting. ## Optional PHP Methods and Configurations Some shared hosting environments disable common PHP methods and configurations that affect Craft features. - [allow_url_fopen](http://php.net/manual/en/filesystem.configuration.php#ini.allow-url-fopen) — This `ini` setting must be enabled to support updating and installing plugins from the Plugin Store. - [proc_*](http://php.net/manual/en/ref.exec.php) — These methods are required in order to utilize the Plugin Store, install updates, and send emails. - [ignore_user_abort](https://www.php.net/manual/en/function.ignore-user-abort.php) — Required when using the default [web-based queue runner](https://craftcms.com/docs/5.x/system/queue.html#http) to operate. - [pcntl_*](https://www.php.net/manual/en/book.pcntl.php) — The _Process Control_ extension allows [daemonized queue runners](https://craftcms.com/docs/5.x/system/queue.html#daemon) to gracefully exit between jobs. ## File Permissions For Craft to run properly, PHP needs to be able to write to the following files and folders at all times: - `storage/*` - `config/license.key` - `web/cpresources/*` Additionally, during [setup](kb:first-time-setup) or when [updating](https://craftcms.com/docs/5.x/update.html) or installing [plugins](https://craftcms.com/docs/5.x/system/plugins.html) via the [control panel](https://craftcms.com/docs/5.x/system/control-panel.html) or [CLI](https://craftcms.com/docs/5.x/system/cli.html), Craft may touch these files: - `.env` - `config/project/*` - `composer.json` and `composer.lock` - `vendor/*` The exact permissions depend on the relationship between the system user that PHP runs as and the owner of the folders and files: - If they’re the same user, use `744` (`rwxr--r--`). - If they’re in the same group, use `774` (`rwxrwxr--`). - If neither of the above options describe your setup, something may have been misconfigured. Reach out to your system administrator for support. Specifics may vary from platform to platform or host to host! Consult your development or hosting environment’s documentation for more information. **Never** set permissions to `777` in a shared environment or on a live site, and **never** run your HTTP server (or PHP) as `root`. ## Required Database User Privileges The database user you tell Craft to connect with must have the following privileges: #### MySQL/MariaDB - `SELECT` - `INSERT` - `DELETE` - `UPDATE` - `CREATE` - `ALTER` - `INDEX` - `DROP` - `REFERENCES` - `LOCK TABLES` #### PostgreSQL - `SELECT` - `INSERT` - `UPDATE` - `CREATE` - `DELETE` - `REFERENCES` - `CONNECT` reference/config/db.md ## Control Panel Browser Requirements Craft’s [control panel](https://craftcms.com/docs/5.x/system/control-panel.html) requires a browser that [supports JavaScript modules](https://caniuse.com/#feat=es6-module-dynamic-import). #### Windows and macOS - Firefox 67+ - Chrome 63+ - Safari 11.1+ - Edge 79+ #### Mobile - iOS: Safari 11+ - Android: Chrome 81+ or Firefox 68+ Craft’s _control panel_ browser requirements are independent from those of your site’s front-end. Ultimately, Craft is only concerned with delivering [hypermedia](https://en.wikipedia.org/wiki/Hypermedia)—how you choose to enhance that with JavaScript and CSS is entirely up to you! --- # Running Updates Craft has a smart system based on [Composer](https://getcomposer.org) that helps you keep your site and plugins up-to-date. This page covers routine _updates_ to dependencies, which we consider a distinct process from [major-version _upgrades_](#major-versions). ## Using the Control Panel When an update is available, users with the permission to update Craft will see a badge in the [control panel](https://craftcms.com/docs/5.x/system/control-panel.html) next to Journey: Utilities in the global navigation: ![Screenshot of control panel cropped to “Utilities” global navigation item, which contains a circular badge with the number “1” in it](./images/update-badge.png) Click Journey: Utilities, Updates. You can also get to this view directly from the **Updates** widget that’s installed by default in the control panel [dashboard](https://craftcms.com/docs/5.x/system/control-panel.html#dashboard). This section displays updates for Craft CMS and installed plugins, where each version has its own collapsible panel detailing relevant changes. ![Stylized screenshot of “Updates” page, which displays two plugins with newer versions with collapsible panes detailing their changes](./images/updates.png) You can update one dependency at a time using its **Update** button, or update everything with the **Update All** at the top of the screen. If you don’t see these options, ensure the config5:allowUpdates and config5:allowAdminChanges settings are enabled in your current environment. We generally _do not_ recommend applying updates in a non-development environment—see our [workflow](https://craftcms.com/docs/5.x/deploy.html) page for details. Craft’s [changelog](repo:craftcms/cms/blob/5.x/CHANGELOG.md) will warn you of any critical changes at the top of the release notes. While there aren’t _usually_ any warnings, it’s always a good idea to check the changelog and [any upgrade guides](#upgrade-guides) before updating. Urgent messages from changelogs (like security patches) are exposed as a banner in the control panel to all administrators. After Craft successfully installs the updates, it will run any new migrations. ## Updating from the Terminal ### Craft CLI The [`update` console command](https://craftcms.com/docs/5.x/reference/cli.html#update) can be used to update Craft and plugins. To see available updates, navigate to a Craft project directory in your terminal and run this command: ```bash DDEV ddev craft update ``` ```bash Generic php craft update ``` You should see output like this: ``` Fetching available updates ... done You’ve got two available updates: - craft 5.4.8 => 5.4.9 - commerce 5.2.1 => 5.2.2 Run craft update all or craft update to perform an update. ``` To update everything all at once, run this command: ```bash DDEV ddev craft update all ``` ```bash Generic php craft update all ``` To apply a specific update, replace `all` with `craft` (to update Craft itself) or a plugin’s handle: ```bash DDEV ddev craft update commerce ``` ```bash Generic php craft update commerce ``` ``` Fetching available updates ... done Performing one update: - commerce 5.2.1 => 5.2.2 Create database backup? (yes|no) [yes]: Backing up the database ... done Performing update with Composer ... done Applying new migrations ... done Update complete! ``` You can pass multiple handles in at once: ```bash DDEV ddev craft update commerce element-api ``` ```bash Generic php craft update commerce element-api ``` #### Version Behavior The updater installs the latest available version of Craft or a plugin, unless you append `:version` to the handle, where `version` is a legal Composer constraint: ```bash DDEV ddev craft update element-api:4.1.0 ``` ```bash Generic php craft update element-api:4.1.0 ``` After an update is performed from the CLI, Craft will apply any relevant migrations. Use the `--minor` or `--patch` flag when running `craft update` to update in finer increments. Pass `--except craft,plugin-name` to update all but the provided package(s). As of Craft [5.9.0](repo:craftcms/cms/releases/tags/5.9.0), constraints are set using the caret (`^`) operator for better interoperability with direct [Composer](#composer) usage. The updater will _not_ update expired packages. If for any reason you end up with a set of installed packages outside what a license allows (say, by alternating between the built-in updater and Composer CLI), Craft will warn you when running `craft up`: ``` The following licensing issues were detected: - Commerce isn’t licensed to run version 5.5.1. - ... Continue anyway? (yes|no) [no]: ``` ### Composer `craft update` is mostly a wrapper around Composer—but you can use Composer directly for more control over the update process. The main behavioral difference is that `craft update` will _never_ update Craft or plugin versions beyond what is permitted by their respective licenses. When using version constraints, `composer.lock` will still make sure you get a stable set of packages from `composer install`. To update all your packages to the most recent versions allowed by their constraints, run `composer update`. Update a _single_ package by specifying it in the command: `composer update craftcms/cms`. If you have used Craft’s [control panel](#using-the-control-panel) or [CLI](#craft-cli) updater in the past, `composer update` may do nothing! Open `composer.json`, and look at the packages under the `require` key—if you see exact version numbers (i.e. `2.3.4`), Composer will never update those packages. Keep in mind that manually altering constraints _can_ lead to an irreconcilable set of packages—Composer will let you know about this before updating the lockfile. Generally speaking, the “major-version” constraints set automatically when using `composer require ...` should continue to work, while protecting your project from breaking changes in dependencies. ## Workflow Every time you [deploy](https://craftcms.com/docs/5.x/deploy.html) your project, you should run `composer install` and `craft up` to bring that environment’s dependencies and database into agreement with your packages and their expected schema version. The same applies when working with teammates! Any time you pull new code into a project, running `composer install` will guarantee you are working with the same set of packages as your collaborators. If you’re one for automation, you can even have Craft apply migrations and [project config](https://craftcms.com/docs/5.x/system/project-config.html) at the same time by adding a special hook to the `scripts` key in `composer.json`: ```json{9} { "require": { "craftcms/cms": "^5.0.0", "vlucas/phpdotenv": "^5.4.0" // ... }, // ... "scripts": { "post-install-cmd": "@php craft install/check && php craft up --interactive=0 || exit 0" } } ``` This tells Composer that _after_ it has successfully installed packages from a lockfile (typically the result of running `composer install`), it should check if Craft is installed (`@php craft install/check`) and if so, run `craft up` non-interactively to apply migrations and project config. `|| exit 0` ensures that the command exits nominally, so as not to disrupt other processes that expect `composer install` to succeed. Running `craft up` will warn you before running migrations if there are licensing issues with the installed packages—say, as the result of using [Composer](#composer) directly. Since 5.9.0: Warnings about licensing issues when running `craft up` ## Licensing When you buy a Craft or plugin license, you are entitled to use that version (or any version that you update to, during the year of included updates) in perpetuity. To get the most out of your licenses, run updates frequently! ## Upgrade Guides Each version of the Craft documentation contains an equivalent of the [Upgrading from Craft 4](https://craftcms.com/docs/5.x/upgrade.html) page, which covers the process of upgrading from one [major version](#major-versions) to the next. Sometimes, there are significant changes to be aware of—even in minor or “point” releases. The most common changes that fit this criteria are deprecations. Deprecation notices will be accompanied by instructions for updating your code to work with the new APIs. Any features that are subsequently removed in a major release will also be noted in the new version’s upgrade guide. Deprecation warnings will affect plugin developers more frequently than regular users—but it’s still a good idea to keep your eye on the **Deprecation Warnings** utility. ### Major Versions For the smoothest upgrade experience, projects must be updated to the latest available _minor_ version preceding a _major_ version upgrade. We do not support “skipping” versions, so if your site is running Craft 3, you must first update to the latest 3.x version, then perform the 3.x → 4.x upgrade, then perform the 4.x → 5.x upgrade. You should always upgrade to the latest release in a given major Craft version—for example, you can upgrade directly from 4.12.8 (or whatever the latest 4.x release is) to 5.4.9 (or whatever the latest 5.x release is). --- # Upgrading from Craft 4 The smoothest way to upgrade to Craft 5 is to make sure your live site is already running the [latest version of Craft 4](https://craftcms.com/docs/5.x/4.x/updating.html). We recommend approaching the upgrade in three phases: [preparation](#preparing-for-the-upgrade), a [local upgrade](#performing-the-upgrade), and [rollout](#going-live) to live environments. ## Preparing for the Upgrade Let’s take a moment to audit and prepare your project. - Your **live site** must be running the [latest version](repo:craftcms/cms/releases) of Craft 4 (you cannot upgrade directly from Craft 3 to Craft 5); - The most recent Craft 4-compatible versions of all plugins are installed, and Craft 5-compatible versions are available; - Your project is free of deprecation warnings after thorough testing on the latest version of Craft 4; - All your environments meet Craft 5’s [minimum requirements](https://craftcms.com/docs/5.x/requirements.html) (the latest version of Craft 4 will run in any environment that meets Craft 5’s requirements, so it’s safe to update PHP and your database ahead of the 5.x upgrade): - PHP 8.2 - MySQL 8.0.17+ using InnoDB, MariaDB 10.4.6+, or PostgreSQL 13+ Due to its diverging parity with MySQL, we no longer recommend MariaDB for Craft 5. If you are not able to [migrate to MySQL](https://blogs.oracle.com/mysql/post/how-to-migrate-from-mariadb-to-mysql-80), make sure your server is using the latest MariaDB version to avoid stability issues. - You’ve reviewed the breaking changes in Craft 5 further down this page and understand that additional work and testing may lie ahead, post-upgrade; Once you’ve completed everything above, you’re ready to start the upgrade process! If your project uses custom plugins or modules, we have an additional [extension upgrade guide](https://craftcms.com/docs/5.x/extend/updating-plugins.html). ## Performing the Upgrade Like [any other update](https://craftcms.com/docs/5.x/update.html), it’s essential that you have a safe place to test the upgrade prior to rolling it out to a live website. These steps assume you have a local development environment that meets Craft 5’s [requirements](https://craftcms.com/docs/5.x/requirements.html), and that any changes made [in preparation for the upgrade](#preparing-for-the-upgrade) have been deployed to your live site. [DDEV](https://ddev.com/) users should take this opportunity to [update](https://ddev.readthedocs.io/en/stable/users/install/ddev-upgrade/) so that the `craftcms` project type reflects our latest recommendations. In an existing DDEV project, you can change the PHP or database version with the `config` command… ```bash ddev config --php-version=8.2 ddev config --database=mysql:8.0 ddev start ``` …or use `ddev config --update` to reapply project type defaults. 1. Capture a fresh database backup from your live environment and import it. 1. Make sure you don’t have any pending or active jobs in your queue. 1. Run `php craft project-config/rebuild` and allow any new background tasks to complete. 1. Run `php craft utils/fix-field-layout-uids`. 1. Deploy any project config changes to your live environment. (Be sure to run `php craft up` or `php craft pc/apply`.) 1. Note your current **Temp Uploads Location** setting in **Settings** → **Assets** → **Settings**. 1. MySQL users: Add your database’s _current_ character set and collation to `.env`. If you have always used Craft’s defaults, this will be: ```bash CRAFT_DB_CHARSET="utf8mb3" CRAFT_DB_COLLATION="utf8mb3_general_ci" ``` Read more about this process in the [Database Character Set and Collation](#database-character-set-and-collation) section, below. 1. Visit the **Craft 5 Upgrade** utility, and press **Prep `composer.json`** to generate an updated version of your `composer.json`. You may copy this output verbatim into your project, or transcribe individual version constraints. Craft also adjusts [your `platform` config](https://getcomposer.org/doc/06-config.md#platform) to agree with Craft 5’s requirements. ![Screenshot of the Upgrade utility showing a button labeled “Prep composer.json”](./images/upgrade-prep-composer.png) Beta versions of plugins may be suggested! Adjust your [`minimum-stability`](https://getcomposer.org/doc/04-schema.md#minimum-stability) and [`prefer-stable`](https://getcomposer.org/doc/04-schema.md#prefer-stable) settings if those plugins depend on other packages that do not yet have stable releases. This tool does _not_ adjust your project’s other dependencies; many projects require `craftcms/generator`, which has been updated to `^2.0.0` for Craft 5. To perform this step manually, change your `craftcms/cms` constraint to `^5.0.0`, and each plugin’s constraint to the Craft 5-compatible versions listed in the upgrade utility. You must make _all_ of these edits before proceeding! 1. Run `composer update`. 1. Make any required changes to your [configuration](#configuration). 1. Run `php craft up`. 1. MySQL users: Remove your database character set and collation settings from `.env` (and `db.php`—even if you didn’t modify it during the upgrade), then run `php craft db/convert-charset`. Your site is now running Craft 5! If you began this process with no deprecation warnings, you’re nearly done. Thoroughly review the list of changes on this page, making note of any features you use in templates or modules. Only a fraction of your site’s code is actually evaluated during an upgrade, so it’s your responsibility to check templates and modules for consistency. You may also need to follow any plugin-specific upgrade guides. ## Going Live The Craft 5 upgrade can be run largely unattended during a routine deployment. Once you’ve performed the upgrade in your local environment and everything is working as expected, commit your updated `composer.json`, `composer.lock`, and `config/project/` directory, as well as any template, configuration, or module files that required updates. MySQL users must add the correct [character set and collation settings](#database-character-set-and-collation) to each remote environment for the duration of the upgrade. Deploy your changes to each environment, then run `php craft up`. If you added temporary character set or collation settings for the upgrade, remove them now, then run `php craft db/convert-charset` to apply Craft 5’s new defaults to all tables. Read more about this part of the upgrade in the [Database Character Set and Collation](#database-character-set-and-collation) section, below. ## Cleanup + Optional Steps ### Field and Entry Type Consolidation Since 5.3.0: Field type and entry type consolidation commands Craft 5.3 introduced three new commands for consolidating fields and entry types, post-upgrade: - `fields/auto-merge` — Automatically discovers functionally identical fields and merges their uses together. - `fields/merge` — Manually merge one field into another of the same type and update uses of the merged field. - `entry-types/merge` — Merge one entry type into another and update uses of the merged entry type. These tools are particularly useful on projects with many Matrix fields containing similar block types, or multiple sections with similar entry types. While the upgrade doesn’t result in _more_ fields or entry types and block types than existed previously, it can result in some ergonomic problems with the new content architecture! We recommend you approach consolidation in the following order, _after_ getting your project fully upgraded and deployed. In addition to updating project config, this process generates content migrations. You must commit these files to version control and run `craft up` in all other environments to apply the changes! #### Auto-merging Fields Your first step should be to try auto-merging field definitions: ```bash ddev craft fields/auto-merge ``` Craft will audit your fields, and propose batches of merge candidates. To accept a merge, type `y` and press Enter, then choose the field you want to merge the duplicates into. The usage of any merged fields in field layouts and templates will remain the same after merging global field definitions. Any overridden names, handles, instructions, and so on will remain. One exception here is if a name or handle override is present but the same as the merged global field’s name: Craft clears the override so that it can be better kept in sync, moving forward. Don’t like how Craft grouped some of the fields? You can skip any proposed merge and come back to the command later—say, after [manually merging](#manually-merging-fields) a few of the fields. Merged fields’ search keywords are also combined, which may affect the quality of results when [searching by field handles](https://craftcms.com/docs/5.x/system/searching.html#multi-instance-fields). #### Manually Merging Fields For any additional fields you wish to merge (or merge candidates you skipped in the previous step), run the `fields/merge` command as many times as necessary to consolidate them: ```bash ddev craft fields/merge myFirstDropdownField mySecondDropdownField ``` To merge multiple fields together, choose the field you want to persist, and run the command multiple times, passing each redundant field as the first argument, and the target field as the second, i.e: ```bash ddev craft fields/merge ctaDonate ctaAbout ddev craft fields/merge ctaStaff ctaAbout ddev craft fields/merge ctaFoundation ctaAbout ``` #### Merging Entry Types Once your field definitions have been consolidated, merging entry types is greatly simplified. Here’s how Craft handles fields during a merge: - Any field instances that map to the same custom field (and have the same handle) will be left alone in the persistent entry type. - Any single-instance fields that exist in both entry types (but with _different_ handles) will be left alone in the persistent entry type, and the command will note the handle change for fields of the outgoing entry type. - Any other fields in the outgoing entry type will be added to the persistent entry type under a “Merged Fields” tab. Any handle conflicts will be resolved by incrementing the handle of the merged field, and the handle change will be noted in the command’s output. There is no “automatic” equivalent for merging entry types—each merge must be run manually: ```bash ddev craft entry-types/merge quote pullQuote ``` To merge entry types, Craft takes the following actions: 1. The persistent entry type’s field layout is updated with any new fields from the outgoing entry type. 1. Sections and fields that use the outgoing entry type are updated to use the persistent entry type, instead (if it wasn’t already available in that context). 1. The outgoing entry type is removed. 1. A content migration is generated an run, remapping entries of the outgoing entry type to the persistent one, and entries’ JSON content is updated with the correct layout element UUIDs. Merging is concerned only with custom fields. Other field layout elements (like tips, rules, breaks, or templates) are _not_ merged into the persisted entry type. ### Entry Scripts Older projects may use versions of [`vlucas/phpdotenv`](repo:vlucas/phpdotenv) (the library that loads variables from your `.env` file) that depend on features deprecated in PHP 8.1. If you encounter errors during (or after) installation, change your `vlucas/phpdotenv` dependency in `composer.json` to `^5.6.0`, then run `composer update`. Along with this update, you’ll need to integrate changes from our [starter project](repo:craftcms/craft)’s `web/index.php` script and `craft` executable. If you have done any low-level customization of Craft via [bootstrap constants](https://craftcms.com/docs/5.x/configure.html#bootstrap-config), make sure those values are preserved in the appropriate file(s)—constants shared between the two files can be moved to a common [`bootstrap.php` file](repo:craftcms/craft/blob/5.x/bootstrap.php). ## Breaking Changes and Deprecations Features deprecated in Craft 4 may have been fully removed or replaced in Craft 5, and new deprecations have been flagged in Craft 5. This list focuses on high-traffic, user-facing features. Review the [complete changelog](repo:craftcms/cms/blob/5.x/CHANGELOG.md) for information about changes to specific APIs, including class deprecations, method signatures, and so on. ### Configuration The following settings have been changed. #### Database Character Set and Collation MySQL 8.0 [deprecated the `utf8mb3` character set](https://dev.mysql.com/doc/refman/8.0/en/charset-unicode-utf8mb3.html), as well as [the use of `utf8` as an alias for `utf8mb3`](https://dev.mysql.com/doc/refman/8.0/en/charset-unicode-utf8.html). MySQL [recommends `utf8mb4`](https://dev.mysql.com/doc/refman/8.0/en/charset-unicode-utf8mb4.html) instead, and it’s expected that `utf8` will become an alias for `utf8mb4` in MySQL 8.1. To ease that transition, Craft 5 is proactively treating `utf8` as `utf8mb4` for MySQL and MariaDB installs. Explicitly setting the character set and collation ensures compatibility of new tables _during_ the upgrade—afterwards, those settings can be scrubbed and the tables can be converted via Craft’s CLI. Here’s how this will look, in practice. If you have never customized character set or collation settings for your database connection, set these variables in your `.env` file during the upgrade: ```bash CRAFT_DB_CHARSET="utf8mb3" CRAFT_DB_COLLATION="utf8mb3_general_ci" ``` If you _are_ using these settings (either from `.env` or `db.php`), they can be left as-is, for now. Once the upgrade is complete in an environment, convert your tables to `utf8mb4` by _removing_ the `CRAFT_DB_CHARSET` and `CRAFT_DB_COLLATION` environment variables (and clearing any `charset` and `collation` settings from `db.php`)… ```bash # …or set them to Craft’s defaults… CRAFT_DB_CHARSET="utf8mb4" CRAFT_DB_COLLATION="utf8mb4_0900_ai_ci" # MySQL CRAFT_DB_COLLATION="utf8mb4_unicode_ci" # MariaDB ``` …then running `php craft db/convert-charset`. #### Template Priority The config5:defaultTemplateExtensions config setting now lists `twig` before `html`, by default. This means that projects with templates that share a name (i.e. `widget.twig` and `widget.html`, _in the same directory_) may behave differently in Craft 5. Audit your `templates/` directory for any overlap to ensure consistent rendering. If all your templates use one extension or another, no action is required! This setting only affects rendering of templates in the front-end; the control panel will _always_ use `.twig` files before `.html`. #### Volumes & Filesystems Multiple [asset volumes](https://craftcms.com/docs/5.x/reference/element-types/assets.html) can now share a filesystem! However, they must be carefully arranged such that each volume has a unique and non-overlapping base path. When selecting a filesystem, options that would result in a collision between two volumes are not shown. This means that you may need to adjust multiple volumes’ configuration in order to consolidate them into a single filesystem. ### Templates #### Variables With the elimination of Matrix blocks as a discrete element type, we have removed the associated element query factory function from craft5:craft\web\twig\variables\CraftVariable. | Old | New | | --- | --- | | `craft.matrixBlocks()` | `craft.entries()` | See the section on [Matrix fields](#matrix-fields) for more information about these changes. ### GraphQL The reusability of entry types means that some GraphQL queries have changed. - GraphQL types corresponding to entry types no longer include their section names. In Craft 4, entries a section named “Ingredients” with the entry type “Ingredient” might have been represented in GraphQL as a type named `ingredients_ingredient_Entry`. That type would now be named `ingredient_Entry`. Entries can still be queried by section using the corresponding query type (i.e. `ingredientsEntries`) or the `section` argument for the generic `entries` query: ```graphql{2} Section Query query MyIngredients { ingredientsEntries { ... on ingredient_Entry { title } } } ``` ```graphql{2} Generic Query query MyIngredients { entries(section: "ingredients") { ... on ingredient_Entry { title } } } ``` The corresponding queries for entries nested within a Matrix field would look like this: ```graphql{2} Field Query query DifficultSteps { stepsFieldEntries(difficulty: "hard") { ... on step_Entry { title estimatedTime } } } ``` ```graphql{2} Generic Query query DifficultSteps { entries(field: "steps", difficulty: "hard") { ... on step_Entry { title estimatedTime } } } ``` - The names of mutations targeting entries in a particular section remain unchanged, and still include the section and entry type handles, i.e: `save_ingredients_ingredient_Entry` and `save_ingredients_ingredient_Draft`. - Mutations directly targeting nested entries in Matrix fields will use a similar structure, but combining the field handle and entry type handle: `save_stepsField_step_Entry`. - When mutating a Matrix field value via GraphQL, the nested entries must be passed under an `entries` key instead of a `blocks` key: ```graphql{2,5} mutation AddStep { save_recipes_recipe_Entry( title: "Yummy Bits and Bytes" summary: "Chocolate chip cookies that are out of this world!" steps: { entries: [ { step: { difficulty: "hard", instructions: "Preheat oven to 20,000,000°F", estimatedTime: 120, id: "new:1" } } # ... ], sortOrder: [ "new-first" ] } ) { title id } } ``` ### Field Types #### URL → Link Since 5.3.0: Link fields URL fields will be converted to [Link](https://craftcms.com/docs/5.x/reference/field-types/link.html) fields during the upgrade. Your templates _do not_ require any updates, but projects that use custom field values in PHP may need to update type hints or use a specific member of the new craft5:craft\fields\data\LinkData object: ```php Old use craft\base\Element; class MyHelper { public static function getUrl(Element $element, string $fieldHandle): string { return $element->$fieldHandle; } } ``` ```php{6} New (Types) use craft\base\Element; use craft\fields\data\LinkData; class MyHelper { public static function getUrl(Element $element, string $fieldHandle): LinkData { // The return type is now an object instead of a string! return $element->$fieldHandle; } } ``` ```php{9} New (Member) use craft\base\Element; use craft\fields\data\LinkData; class MyHelper { public static function getUrl(Element $element, string $fieldHandle): string { // The signature can stay the same if you return a specific property: return $element->$fieldHandle->url; } } ``` The new field type supports linking to elements, in addition to text-based URLs—[check out its options](https://craftcms.com/docs/5.x/reference/field-types/link.html#settings) to see if it can help simplify some of your authoring interfaces! ## Elements & Content Craft 5 has an entirely new content storage architecture that underpins many other features. ### Content Table The content table has been eliminated! Elements now store their content in the `elements_sites` table, alongside other localized properties. Content is stored as a JSON blob, and is dynamically indexed by the database in such a way that all your existing element queries will work without modification. #### Advanced Queries When using custom fields in [advanced `where()` conditions](https://craftcms.com/docs/5.x/development/element-queries.html#advanced-element-queries), you no longer need to manually assemble a database column prefix/suffix. Instead, Craft can generate the appropriate expression to locate values in the JSON content column: ```twig{12} Twig {% set entriesFromPhysicalMedia = craft.entries() .section('news') .andWhere([ 'or like', fieldValueSql(entryType('post'), 'sourceMedia'), ['print', 'paper', 'press'] ]) .all() %} ``` ```php{12} PHP use Craft; use craft\elements\Entry; $entryType = Craft::$app->getEntries()->getEntryTypeByHandle('post'); $fieldLayout = $entryType->getFieldLayout(); $sourceField = $fieldLayout->getFieldByHandle('sourceMedia'); $entriesFromPhysicalMedia = Entry::find() ->section('news') ->andWhere([ 'or like', $sourceField->getValueSql(), ['print', 'paper', 'press'], ]) ->all(); ``` This ensures that you are querying for the correct instance of a [multi-instance field](https://craftcms.com/docs/5.x/system/fields.html#multi-instance-fields), each of which will store their content under a different UUID in their respective field layouts. Craft already knows how to query against the appropriate instance of a field when using its handle as a query method, so this is only necessary for complex or compound conditions that make use of low-level query builder features. #### Case-Sensitivity MySQL users should review queries against [textual custom field values](https://craftcms.com/docs/5.x/development/element-queries.html#case-sensitivity) for consistency. The following query using a lower-case name will no longer match a capitalized value: ```twig {% set entries = craft.entries() .section('departments') .deptHeadNameFirst('oli') .all() %} ``` If you don’t have control over the query _value_ (say, it comes from a third-party feed), consider updating the query to use the new `caseInsensitive` option: ```twig {% set entries = craft.entries() .section('departments') .deptHeadNameFirst({ value: 'oli', caseInsensitive: true, }) .all() %} ``` Textual condition rules in [custom sources](https://craftcms.com/docs/5.x/system/elements.html#sources) and [field layouts](https://craftcms.com/docs/5.x/system/fields.html#conditions) are always handled case-insensitively. Since 5.9.0 ### Matrix Fields During the upgrade, Craft will automatically migrate all your Matrix field content to entries. In doing so, new globally-accessible fields will be created with a unique name: ``` {Matrix Field Name} - {Block Type Name} - {Field Name} ``` These fields are then assigned to new [entry types](https://craftcms.com/docs/5.x/reference/element-types/entries.html#entry-types) that replace your existing Matrix block types. Field labels and handles are retained, within their field layouts—even if they collide with other fields in the [global space](https://craftcms.com/docs/5.x/system/fields.html#multi-instance-fields)! Queries for nested entries will be largely the same—equivalent methods have been added to craft5:craft\elements\db\EntryQuery to enable familiar usage. The `.owner()` method still accepts a list of elements that the results must be owned by, and the `field()` and `fieldId()` methods narrow the results by those belonging to specific fields. Values passed to the `type()` method of [entry queries](https://craftcms.com/docs/5.x/reference/element-types/entries.html#querying-entries) may require updates, as migrated entry types _can_ receive new handles. For example, a Matrix field that contained a block type with the handle `gallery` might conflict with a preexisting entry type belonging to a section; in that event, the new entry type for that block would get the handle `gallery1` (or potentially `gallery2`, `gallery3`, and so on, if multiple similar Matrix fields are being migrated). Craft will set [local overrides for entry type names and handles](https://craftcms.com/docs/5.x/reference/element-types/entries.html#aliases) so that the control panel experience is as consistent as possible, post-upgrade—but this does _not_ change the query behavior. It _does_, however, make most templates that tested Matrix block type handles (i.e. `myBlock.type.handle == 'carousel'`, or within a [`switch` tag](https://craftcms.com/docs/5.x/reference/twig/tags.html#switch)) compatible. Since 5.6.0: Local overrides for entry type names and handles Visit Journey: Settings, Entry Types to check if any handles appear to have collided in this way, then review and update templates as necessary. _Matrix block type IDs were not stable in Craft 4, and will change as they are converted to entry types. If you maintain code that loaded Matrix block definitions by hard-coded IDs, it will no longer work in Craft 5._ ### Eager-Loading [Eager-loading](https://craftcms.com/docs/5.x/development/eager-loading.html) has been dramatically simplified for most sites. You no longer need to tell Craft which relational fields to load via the `.with()` query method—instead, call `.eagerly()` on any query that _may_ result in an N+1 problem to automatically trigger eager-loading: ```twig Before {% set articles = craft .entries() .section('blog') .with([ ['featureImage'], ]) .all() %} {% for article in articles %} {% set image = article.featureImage|first %} {% if image %} {{ image.getImg() }} {% endif %} {% endfor %} ``` ```twig After {% set articles = craft .entries() .section('blog') .all() %} {% for article in articles %} {% set image = article.featureImage.eagerly().one() %} {# Testing whether there was a result is still a good idea: #} {% if image %} {{ image.getImg() }} {% endif %} {% endfor %} ``` This feature does have _some_ limitations, though. While it will work for all elements connected via a [relational](https://craftcms.com/docs/5.x/system/relations.html) or Matrix field, you will still need to explicitly eager-load native attributes like entries’ `author` (and now `authors`, _plural_) or assets’ `uploader`. Craft can only detect eager-loading opportunities when the original attribute or value is an element query. The two strategies can be safely combined, though: ```twig {% set articles = craft .entries() .section('blog') .with([ ['author'], ]) .all() %} {% for article in articles %} {# Lazily-eager-loaded relation: #} {% set image = article.featureImage.eagerly().one() %} {% if image %} {{ image.getImg() }} {% endif %} {# Explicitly eager-loaded element: #} {{ article.author.fullName }} {% endfor %} ``` Read more about opportunities to optimize your templates with automatic and manual eager-loading. - development/eager-loading.md ## Assets ### Reusable Filesystems Filesystems can now be shared by multiple asset volumes, so long as each volume has a unique and non-overlapping base path. The [assets](https://craftcms.com/docs/5.x/reference/element-types/assets.html) page has more information on this new behavior. ### Temporary Filesystem The new config5:tempAssetUploadFs general config setting has replaced the **Temp Uploads Location** setting in the **Settings** → **Assets** → **Settings** screen. If you noted a custom asset volume in the upgrade process, you will need to follow these steps to set up a distinct filesystem for temporary uploads. Otherwise, Craft will use the legacy behavior and fall back on an instance of craft5:craft\fs\Temp, which puts temporary uploads in a folder in your local [`storage/` directory](https://craftcms.com/docs/5.x/system/directory-structure.html). Load-balanced or ephemeral environments that rely on a centralized storage solution should define a temporary upload filesystem using the steps below. The location of temporary uploads is now defined by way of a _filesystem_ instead of a _volume_: 1. Create a new filesystem by visiting **Settings** → **Filesystems**, giving it an appropriate **Name** and **Handle** (users will not see this, so something like “Temporary” or “Scratch” is fine). 1. Set the **Base Path** to agree with the old volume, keeping in mind that previously, its subpath and its filesystem’s base path were combined. 1. Add the handle you chose to your [general config](https://craftcms.com/docs/5.x/reference/config/general.html#tempassetuploadfs) file or define a `CRAFT_TEMP_ASSET_UPLOAD_FS` [environment override](https://craftcms.com/docs/5.x/configure.html#environment-overrides). A filesystem designated by your `tempAssetUploadFs` setting cannot be reused for volumes or image transforms, so it will not appear in filesystem selection menus. If you elect to define the temporary uploads filesystem handle via an _environment variable_, be sure and add it to your other environments as well! ## Plugins and Modules Plugin authors (and module maintainers) should refer to our guide on [updating Plugins for Craft 5](https://craftcms.com/docs/5.x/extend/updating-plugins.html). --- # Accessibility Websites and applications are used by people (and machines!) with a wide variety of capabilities. Craft gives you a powerful suite of tools to help make your projects accessible to everyone. ## Media When marked up appropriately, text on the web is inherently accessible. The same cannot be said for visual media like images, video, and icons; without explicit descriptions or context clues, unsighted users may miss essential content. ### Alternative text Every asset in Craft has (at minimum) a **Title**, which is generated from its filename on upload. You can supplement this with the [native **Alternative Text** field](https://craftcms.com/docs/5.x/reference/element-types/assets.html#alt-text) by visiting Journey: Settings, Assets, Volumes, *Volume Name and dragging it into the field layout. Craft uses this field’s value when rendering asset thumbnails in the control panel, and when outputting an image in a Twig template: ```twig Twig {{ myAsset.getImg() }} ``` ```html Result Screenshot of Craft’s asset volume settings page, showing the ``` For more control, you can construct `img` tags yourself. The `alt` attribute can come from anywhere, or be defined via a series of fallbacks: ```twig {% set thumb = result.articleThumbnail.one() %} {{ tag('img', { class: 'article-thumbnail', src: thumb.getUrl(), alt: result.thumbnailDescription ?: thumb.alt, }) }} ``` This example checks a local `thumbnailDescription` [custom field](https://craftcms.com/docs/5.x/system/fields.html) on a `result` entry, and defaults to the `alt` text attached directly to the asset. Keep in mind that not _every_ use of an image needs alternative text! This might be a perfectly acceptable way to handle icons attached to [categories](https://craftcms.com/docs/5.x/reference/element-types/categories.html) or [tags](https://craftcms.com/docs/5.x/reference/element-types/tags.html): ```twig {# Decorative or “presentational” icon: #} {{ tag('img', { class: 'icon icon--decorative', src: myAsset.getUrl(), height: myAsset.getHeight(), width: myAsset.getWidth(), alt: '', }) }} ``` The [W3C `alt` Decision Tree](https://www.w3.org/WAI/tutorials/images/decision-tree/) is a great resource for deciding what an appropriate alternative text should be. An asset’s `title` is often _not_ the correct source for descriptions—especially when a file is [uploaded directly to an assets field](https://craftcms.com/docs/5.x/reference/field-types/assets.html): ``` File # Screenshot: screenshot_2025-12-31_11-59-59.png # Photo from digital camera: IMG_4567.jpg ``` ``` Title # Screenshot: Screenshot 2025 12 31 11 59 59 # Photo from digital camera: IMG 4567 ``` For this reason, Craft _does not_ attempt to repair a missing `alt` attribute by falling back to the title in the absence of explicit alternative text. Generic or irrelevant alt text is also a barrier for screen reader users, and one that can be difficult to identify without a manual accessibility audit. Instead, we leave the `alt` attribute off entirely so the image can be flagged by automated accessibility tests. Below, we cover [a few patterns](#alt-text-overrides) that make alternative text easier to maintain. #### Rich Text Inserting an image into a rich text editor may not automatically populate `alt` attributes. - **[Redactor](plugin:redactor)** — Select an inserted image and pick **Edit** to open a modal. Redactor incorrectly labels the alternative text field as its **Title**. - **[CKEditor](plugin:ckeditor)** — Select an inserted image and pick the **Change image text alternative** button to add a description. Both plugins also support captions, which convert the image into `
` and `
` elements. ### Video and captioning Craft also uses the native `alt` attribute for transcripts on video and audio. Transcripts are shown when previewing media in the control panel. There is currently no built-in support for time-based transcripts or closed captioning (in the control panel or front-end), but you are free to implement them via custom fields: ```twig