Upgrading from Craft 4

You are viewing documentation for an unreleased version of Craft CMS. Please be aware that some pages, screenshots, and technical reference may still reflect older versions.

The smoothest way to upgrade to Craft 5 is to start with a fully-updated Craft 4 project.

# Preparing for the Upgrade

Let’s take a moment to audit and prepare your project.

  • Your live site must be running the latest version (opens new window) of Craft 4;
  • 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 (opens new window) after thorough testing on the latest version of Craft 4;
  • All your environments meet Craft 5’s minimum requirements (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);
  • 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.

# Performing the Upgrade

Like any other update, 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, and that any changes made in preparation for the upgrade have been deployed to your live site.

DDEV (opens new window) users should take this opportunity to update 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:

ddev config --php-version=8.2
ddev config --database=mysql:8.0
ddev start
  1. Capture a fresh database backup from your live environment and import it.

  2. Make sure you don’t have any pending or active jobs in your queue.

  3. Run php craft project-config/rebuild and allow any new background tasks to complete.

  4. Capture a database backup of your local environment, just in case things go sideways.

  5. Note your current Temp Uploads Location setting in SettingsAssetsSettings.

  6. Add your database’s current character set and collation to .env. If you have always used Craft’s defaults, this will be:


    If you have changed the character set or collation, make sure your settings agree with these recommendations.

  7. Edit your project’s composer.json to require "craftcms/cms": "^5.0.0" and Craft-5-compatible plugins, all at once.

    You’ll need to manually edit each plugin version in composer.json. If any plugins are still in beta, you may need to change your minimum-stability (opens new window) and prefer-stable (opens new window) settings.

    You may also need to add "php": "8.2" to your platform (opens new window) requirements, or remove it altogether.

  8. Run composer update.

  9. Make any required changes to your configuration.

  10. Run php craft up.

  11. 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, like Upgrading to Commerce 4.

Once you’ve verified everything’s in order, commit your updated composer.json, composer.lock, and config/project/ directory (along with any template, configuration, or module files that required updates) and deploy those changes normally in each additional environment.

# Breaking Changes and Deprecations

Features deprecated in Craft 4 may have been fully removed or replaced in Craft 4, and new deprecations have been flagged in Craft 5.

This list focuses on high-traffic, user-facing features. Review the complete changelog (opens new window) 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

Craft 5 requires MySQL 8.0, which has deprecated the use of utf8 as an alias for the utf8mb3 character set. A future version is expected to switch this alias to utf8mb4 (opens new window), so we are proactively adopting the unambiguous utf8mb4 character set and utf8mb4_0900_ai_ci collation for new tables.

Custom collation settings that do not use an explicit byte-length suffix (i.e. utf8_general_ci) may be incompatible with the new handling of utf8. For example, a Craft 4 project that uses the utf8_unicode_ci collation (set via the CRAFT_DB_COLLATION environment variable or collation key in db.php) but has not explicitly set the corresponding character set will encounter an “incompatible collation” error during the upgrade.

To avoid this, add your database’s current character set and collation to your .env file, ensuring they both include the byte-length suffix:


Even if you have never manually configured a character set or collation for a project, we still recommend running php craft db/convert-charset after the upgrade.

# 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 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\CraftVariable.

Old New
craft.matrixBlocks() craft.entries()

See the section on Matrix fields for more information about these changes.

# 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, you no longer need to assemble a column prefix/suffix. Instead, Craft can generate the appropriate expression to locate values in the JSON content column:


{# Locate the field layout element that would save to the desired column: #}
{% set entryType = craft.app.entries.getEntryTypeByHandle('post') %}
{% set fieldLayout = entryType.getFieldLayout() %}
{% set sourceField = fieldLayout.getFieldByHandle('source') %}

{% set entriesFromPhysicalMedia = craft.entries()
    ['print', 'paper', 'press']
  .all() %}

While this may appear more convoluted, initially, it ensures that you are querying for the correct instance of a multi-instance field, each of which will store their content under a different key in their respective field layouts.

# 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 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!

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 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).

Visit SettingsEntry Types to check if any handles appear to have collided in this way, then review and update templates as necessary.

# Consolidating Fields

We plan to introduce a console command to handle merging similar fields, after the upgrade. There is no harm in running your project with extra fields during the beta period.

# Eager-Loading

Eager-loading 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 eager-loading:

{% set articles = craft
    .all() %}

{% for article in article %}
  {% set image = article.featureImage|first %}
  {% if image %}
    {{ image.getImg() }}
  {% endif %}
{% endfor %}

This feature does have some limitations, though. While it will work for all elements connected via a relational 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:

{% set articles = craft
    .all() %}

{% for article in article %}
  {# Lazily-eager-loaded relation: #}
  {% set image = article.featureImage.eagerly().one() %}

  {% if image %}
    {{ image.getImg() }}
  {% endif %}

  {# Explicitly eager-loaded element: #}
  <span>{{ entry.author.fullName }}</span>
{% endfor %}
Eager-Loading Elements
Read more about opportunities to optimize your templates with automatic and manual eager-loading.

# Assets

# Reusable Filesystems

Filesystems can now be shared by multiple asset volumes, so long as each volume as a unique and non-overlapping base path. The assets 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 SettingsAssetsSettings 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.

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 SettingsFilesystems, giving it an appropriate Name and Handle (users will not see this, so something like “Temporary” or “Scratch” is fine).
  2. 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.
  3. Add the handle you chose to your general config file or define a CRAFT_TEMP_ASSET_UPLOAD_FS environment override.

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.