- Updating Craft CMS
- Configuration
- URL Rules
- PHP Constants
- Static Translation Files
- Remote Volumes
- User Photos
- Twig 2
- Template Tags
- Template Functions
- Date Formatting
- Element Queries
- Elements
- Models
- Locales
- Request Params
- Memcache
- Plugins
The first step to upgrading your site to Craft 3 is updating the CMS itself.
Before you begin, make sure that:
- your server meets Craft 3’s minimum requirements
- your site is running at least Craft 2.6.2788
- you’ve got a fresh database backup in case everything goes horribly wrong
- Composer is installed (see step 1 of the installation instructions)
Once everything’s in order, follow these steps to update Craft:
-
Create a
composer.json
file at the root of your Craft project (if you don’t have one already), and add the following properties:{ "minimum-stability": "dev", "repositories": [ { "type": "composer", "url": "https://asset-packagist.org" } ] }
-
Open your terminal and go to your Craft project:
cd /path/to/project
-
Then run the following command to load Craft 3 (this will take a few minutes):
composer require craftcms/cms:~3.0.0-beta.1
{tip} If Composer complains that your system doesn’t have PHP 7 installed, but you know it’s not an issue because Craft will run with a different PHP install (e.g. through MAMP or Vagrant), use the
--ignore-platform-reqs
flag. -
Then run the following command to install the Craft plugin installer for Composer:
composer require craftcms/plugin-installer
-
Once all the files are in place, replace the contents of your
public/index.php
file (orpublic_htm/index.php
, etc.) with this code: (Be sure to preserve any PHP constants and other custom code you may have.)<?php // Project root path $root = dirname(__DIR__); // Load and run Craft define('CRAFT_BASE_PATH', $root.'/craft'); require_once $root.'/vendor/autoload.php'; $app = require $root.'/vendor/craftcms/cms/bootstrap/web.php'; $app->run();
-
Point your browser to your Control Panel URL (e.g.
http://example.dev/admin
). If you see the update prompt, you did everything right! Go ahead and click “Finish up” to update your database. -
Delete your old
craft/app/
folder. It’s no longer needed; Craft 3 is located invendor/craftcms/cms/
now.
The following config settings have been renamed or removed:
File | Old Setting | New Setting |
---|---|---|
general.php |
appId |
(n/a) |
general.php |
defaultFilePermissions |
defaultFileMode 1 |
general.php |
defaultFolderPermissions |
defaultDirMode |
general.php |
useWriteFileLock |
useFileLocks |
general.php |
backupDbOnUpdate |
backupOnUpdate 2 |
general.php |
restoreDbOnUpdateFailure |
restoreOnUpdateFailure |
general.php |
activateAccountFailurePath |
invalidUserTokenPath |
db.php |
collation |
(n/a) |
db.php |
initSQLs |
(n/a) |
1 defaultFileMode
is now null
by default, meaning it will be determined by the current environment.
2 Performance should no longer be a major factor when setting backupOnUpdate
to false
, since backups aren’t generated by PHP anymore.
The concept of “Environment Variables” has been removed in Craft 3. Previously, they provided a way to define custom token values that would get parsed in a couple places:
- The Site URL setting in Settings → General
- The File System Path and URL settings for “Local Folder” asset sources
You can tell if your site defined any environment variables by searching for environmentVariables
in your config/general.php
file. If it’s there, here’s how to handle this:
The recommended way to set your site URL is with the siteUrl
config setting in config/general.php
:
return [
'siteUrl' => 'https://foo.com/',
// ...
]
For multi-site Craft installs, you can also set that to an array:
return [
'siteUrl' => [
'siteHandle1' => 'https://foo.com/',
'siteHandle2' => 'https://bar.com/',
],
// ...
]
Craft 3 makes it possible to completely override all asset volume settings, not just the File System Path and URL settings for “Local” volumes. See Overriding Volume Settings on the Configuration page for more info.
Your Redactor configs in config/redactor/
must now be valid JSON. That means:
- No comments
- All object properties (the config setting names) must be wrapped in double quotes
- All strings must use double quotes rather than single quotes
// Bad:
{
/* interesting comment */
buttons: ['bold', 'italic']
}
// Good:
{
"buttons": ["bold", "italic"]
}
If you have any URL rules saved in config/routes.php
, you will need to update them to Yii 2’s pattern-route syntax.
- Named parameters in the pattern should be defined using the format (
<paramName:regex>
) rather than as a regular expression subpattern ((?P<paramName>regex)
). - Controller action routes should be defined as a string (
'action/path'
) rather than an array with anaction
key (['action' => 'action/path']
). - Template routes should be defined as as an array with a
template
key (['template' => 'template/path']
) rather than a string ('template/path'
).
// Old:
'dashboard' => ['action' => 'dashboard/index'],
'settings/fields/new' => 'settings/fields/_edit',
'settings/fields/edit/(?P<fieldId>\d+)' => 'settings/fields/_edit',
// New:
'dashboard' => 'dashboard/index',
'settings/fields/new' => ['template' => 'settings/fields/_edit'],
'settings/fields/edit/<fieldId:\d+>' => ['template' => 'settings/fields/_edit'],
The following PHP constants have been deprecated, and will no longer be supported in Craft 4:
Old | New |
---|---|
CRAFT_LOCALE |
CRAFT_SITE |
CRAFT_SITE_URL |
Use the siteUrl config setting instead |
Craft 3 still supports static translations, but the directory structure has changed. Now within your translations/
folder, you should create subdirectories for each locale, and within them, PHP files for each translation category.
The acceptable translation categories are:
Category | Description |
---|---|
app |
Craft’s translation messages |
yii |
Yii’s translation messages |
site |
custom site-specific translation messages |
pluginhandle |
Plugins’ translation messages |
In Craft 3, your translations/
folder might look something like this:
translations/
en-US/
app.php
site.php
Support for Amazon S3, Rackspace Cloud Files, and Google Cloud Storage have been moved into plugins. If you have any asset volumes that were using those services in Craft 2, you will need to install the new plugins:
- Amazon S3
- Rackspace Cloud Files coming soon
- Google Cloud Storage coming soon
User photos are stored as assets now. When upgrading to Craft 3, Craft will automatically create a new asset volume called “User Photos”, set to the storage/userphotos/
folder, where Craft previously stored all user photos. However this folder is be above your web root and inaccessible to HTTP requests, so until you make this volume publicly accessible, user photos will not work on the front end.
Here’s how you can resolve this:
- Move the
storage/userphotos/
folder somewhere below your web root (e.g.public_html/userphotos/
) - Go to Settings → Assets → Volumes → User Photos and configure the volume based on the new folder location:
- Update the File System Path setting to point to the new folder location
- Enable the “Assets in this volume have public URLs” setting
- Set the correct URL setting for the folder
- Save the volume
Craft 3 uses Twig 2, which has its own breaking changes for templates:
Twig 1 let you call macros defined by the same template using _self.macroName()
:
{% macro foo %}...{% endmacro %}
{{ _self.foo() }}
Twig 2 requires you to explicitly import them first:
Import just one macro:
{% from _self import foo %}
{{ foo() }}
Or all of them:
{% import _self as macros %}
{{ macros.foo() }}
Twig 1 let you call block()
even for blocks that didn’t exist:
{% if block('foo') is not empty %}
{{ block('foo') }}
{% endif %}
Twig 2 will throw an error unless it’s a defined
test:
{% if block('foo') is defined %}
{{ block('foo') }}
{% endif %}
The following Twig template tags have been removed:
Old | New |
---|---|
{% endpaginate %} |
(no replacement needed; just delete it) |
The following Twig template tags have been deprecated, and will be removed in Craft 4:
Old | New |
---|---|
{% includecss %} |
{% css %} |
{% includehirescss %} |
{% css %} (write your own media selector) |
{% includejs %} |
{% js %} |
{% includecssfile url %} |
{% do view.registerCssFile(url) %} |
{% includejsfile url %} |
{% do view.registerJsFile(url) %} |
{% includecssresource path %} |
{% do view.registerCssResource(path) %} |
{% includejsresource path %} |
{% do view.registerJsResource(path) %} |
The following template functions have been removed:
Old | New |
---|---|
craft.hasPackage() |
(n/a) |
craft.entryRevisions.getDraftByOffset() |
(n/a) |
craft.entryRevisions.getVersionByOffset() |
(n/a) |
craft.fields.getFieldType(type) |
craft.app.fields.createField(type) |
craft.fields.populateFieldType() |
(n/a) |
The following template functions have been deprecated, and will be removed in Craft 4:
Old | New |
---|---|
round(num) |
`num |
getCsrfInput() |
csrfInput() |
getHeadHtml() |
head() |
getFootHtml() |
endBody() |
getTranslations() |
`view.getTranslations() |
craft.categoryGroups.getAllGroupIds() |
craft.app.categoryGroups.allGroupIds |
craft.categoryGroups.getEditableGroupIds() |
craft.app.categories.editableGroupIds |
craft.categoryGroups.getAllGroups() |
craft.app.categoryGroups.allGroups |
craft.categoryGroups.getEditableGroups() |
craft.app.categories.editableGroups |
craft.categoryGroups.getTotalGroups() |
craft.app.categories.totalGroups |
craft.categoryGroups.getGroupById(id) |
craft.app.categories.getGroupById(id) |
craft.categoryGroups.getGroupByHandle(handle) |
craft.app.categories.getGroupByHandle(handle) |
craft.config.[setting] (magic getter) |
craft.app.config.get('setting') |
craft.config.get(setting) |
craft.app.config.get(setting) |
craft.config.usePathInfo() |
craft.app.config.usePathInfo |
craft.config.omitScriptNameInUrls() |
craft.app.config.omitScriptNameInUrls |
craft.config.getResourceTrigger() |
craft.app.config.resourceTrigger |
craft.locale() |
craft.app.language |
craft.isLocalized() |
craft.app.isMultiSite |
craft.deprecator.getTotalLogs() |
craft.app.deprecator.totalLogs |
craft.elementIndexes.getSources() |
craft.app.elementIndexes.sources |
craft.emailMessages.getAllMessages() |
craft.emailMessages.allMessages |
craft.emailMessages.getMessage(key) |
craft.app.emailMessages.getMessage(key) |
craft.entryRevisions.getDraftsByEntryId(id) |
craft.app.entryRevisions.getDraftsByEntryId(id) |
craft.entryRevisions.getEditableDraftsByEntryId(id) |
craft.entryRevisions.getEditableDraftsByEntryId(id) |
craft.entryRevisions.getDraftById(id) |
craft.app.entryRevisions.getDraftById(id) |
craft.entryRevisions.getVersionsByEntryId(id) |
craft.app.entryRevisions.getVersionsByEntryId(id) |
craft.entryRevisions.getVersionById(id) |
craft.app.entryRevisions.getVersionById(id) |
craft.feeds.getFeedItems(url) |
craft.app.feeds.getFeedItems(url) |
craft.fields.getAllGroups() |
craft.app.fields.allGroups |
craft.fields.getGroupById(id) |
craft.app.fields.getGroupById(id) |
craft.fields.getFieldById(id) |
craft.app.fields.getFieldById(id) |
craft.fields.getFieldByHandle(handle) |
craft.app.fields.getFieldByHandle(handle) |
craft.fields.getAllFields() |
craft.app.fields.allFields |
craft.fields.getFieldsByGroupId(id) |
craft.app.fields.getFieldsByGroupId(id) |
craft.fields.getLayoutById(id) |
craft.app.fields.getLayoutById(id) |
craft.fields.getLayoutByType(type) |
craft.app.fields.getLayoutByType(type) |
craft.fields.getAllFieldTypes() |
craft.app.fields.allFieldTypes |
craft.globals.getAllSets() |
craft.app.globals.allSets |
craft.globals.getEditableSets() |
craft.app.globals.editableSets |
craft.globals.getTotalSets() |
craft.app.globals.totalSets |
craft.globals.getTotalEditableSets() |
craft.app.globals.totalEditableSets |
craft.globals.getSetById(id) |
craft.app.globals.getSetById(id) |
craft.globals.getSetByHandle(handle) |
craft.app.globals.getSetByHandle(handle) |
craft.i18n.getAllLocales() |
craft.app.i18n.allLocales |
craft.i18n.getAppLocales() |
craft.app.i18n.appLocales |
craft.i18n.getCurrentLocale() |
craft.app.locale |
craft.i18n.getLocaleById(id) |
craft.app.i18n.getLocaleById(id) |
craft.i18n.getSiteLocales() |
craft.app.i18n.siteLocales |
craft.i18n.getSiteLocaleIds() |
craft.app.i18n.siteLocaleIds |
craft.i18n.getPrimarySiteLocale() |
craft.app.i18n.primarySiteLocale |
craft.i18n.getEditableLocales() |
craft.app.i18n.editableLocales |
craft.i18n.getEditableLocaleIds() |
craft.app.i18n.editableLocaleIds |
craft.i18n.getLocaleData() |
craft.app.i18n.getLocaleById(id) |
craft.i18n.getDatepickerJsFormat() |
craft.app.locale.getDateFormat('short', 'jui') |
craft.i18n.getTimepickerJsFormat() |
craft.app.locale.getTimeFormat('short', 'php') |
craft.request.isGet() |
craft.app.request.isGet |
craft.request.isPost() |
craft.app.request.isPost |
craft.request.isDelete() |
craft.app.request.isDelete |
craft.request.isPut() |
craft.app.request.isPut |
craft.request.isAjax() |
craft.app.request.isAjax |
craft.request.isSecure() |
craft.app.request.isSecureConnection |
craft.request.isLivePreview() |
craft.app.request.isLivePreview |
craft.request.getScriptName() |
craft.app.request.scriptFilename |
craft.request.getPath() |
craft.app.request.pathInfo |
craft.request.getUrl() |
url(craft.app.request.pathInfo) |
craft.request.getSegments() |
craft.app.request.segments |
craft.request.getSegment(num) |
craft.app.request.getSegment(num) |
craft.request.getFirstSegment() |
`craft.app.request.segments |
craft.request.getLastSegment() |
`craft.app.request.segments |
craft.request.getParam(name) |
craft.app.request.getParam(name) |
craft.request.getQuery(name) |
craft.app.request.getQueryParam(name) |
craft.request.getPost(name) |
craft.app.request.getBodyParam(name) |
craft.request.getCookie(name) |
craft.app.request.cookies.get(name) |
craft.request.getServerName() |
craft.app.request.serverName |
craft.request.getUrlFormat() |
craft.app.config.usePathInfo |
craft.request.isMobileBrowser() |
craft.app.request.isMobileBrowser() |
craft.request.getPageNum() |
craft.app.request.pageNum |
craft.request.getHostInfo() |
craft.app.request.hostInfo |
craft.request.getScriptUrl() |
craft.app.request.scriptUrl |
craft.request.getPathInfo() |
craft.app.request.getPathInfo(true) |
craft.request.getRequestUri() |
craft.app.request.url |
craft.request.getServerPort() |
craft.app.request.serverPort |
craft.request.getUrlReferrer() |
craft.app.request.referrer |
craft.request.getUserAgent() |
craft.app.request.userAgent |
craft.request.getUserHostAddress() |
craft.app.request.userIP |
craft.request.getUserHost() |
craft.app.request.userHost |
craft.request.getPort() |
craft.app.request.port |
craft.request.getCsrfToken() |
craft.app.request.csrfToken |
craft.request.getQueryString() |
craft.app.request.queryString |
craft.request.getQueryStringWithoutPath() |
craft.app.request.queryStringWithoutPath |
craft.request.getIpAddress() |
craft.app.request.userIP |
craft.request.getClientOs() |
craft.app.request.clientOs |
craft.sections.getAllSections() |
craft.app.sections.allSections |
craft.sections.getEditableSections() |
craft.app.sections.editableSections |
craft.sections.getTotalSections() |
craft.app.sections.totalSections |
craft.sections.getTotalEditableSections() |
craft.app.sections.totalEditableSections |
craft.sections.getSectionById(id) |
craft.app.sections.getSectionById(id) |
craft.sections.getSectionByHandle(handle) |
craft.app.sections.getSectionByHandle(handle) |
craft.systemSettings.[category] (magic getter) |
craft.app.systemSettings.getSettings('category') |
craft.tasks.getRunningTask() |
craft.app.tasks.runningTask |
craft.tasks.isTaskRunning() |
craft.app.tasks.isTaskRunning |
craft.tasks.areTasksPending() |
craft.app.tasks.areTasksPending() |
craft.tasks.haveTasksFailed() |
craft.app.tasks.haveTasksFailed |
craft.tasks.getTotalTasks() |
craft.app.tasks.totalTasks |
craft.userGroups.getAllGroups() |
craft.app.userGroups.allGroups |
craft.userGroups.getGroupById(id) |
craft.app.userGroups.getGroupById(id) |
craft.userGroups.getGroupByHandle(handle) |
craft.app.userGroups.getGroupByHandle(handle) |
craft.userPermissions.getAllPermissions() |
craft.app.userPermissions.allPermissions |
craft.userPermissions.getGroupPermissionsByUserId(id) |
craft.app.userPermissions.getGroupPermissionsByUserId(id) |
craft.session.isLoggedIn() |
not craft.app.user.isGuest |
craft.session.getUser() |
currentUser |
craft.session.getRemainingSessionTime() |
craft.app.user.remainingSessionTime |
craft.session.getRememberedUsername() |
craft.app.user.rememberedUsername |
craft.session.getReturnUrl() |
craft.app.user.getReturnUrl() |
craft.session.getFlashes() |
craft.app.session.getAllFlashes() |
craft.session.getFlash() |
craft.app.session.getFlash() |
craft.session.hasFlash() |
craft.app.session.hasFlash() |
Craft’s extended DateTime class has been removed in Craft 3. Here’s a list of things you used to be able to do in your templates, and what the Craft 3 equivalent is. (The DateTime object is represented by the d
variable. In reality it could be entry.postDate
, now
, etc.)
Old | New |
---|---|
{{ d }} (treated as a string) |
`{{ d |
{{ d.day() }} |
`{{ d |
{{ d.month() }} |
`{{ d |
{{ d.year() }} |
`{{ d |
{{ d.atom() }} |
`{{ d |
{{ d.rss() }} |
`{{ d |
{{ d.localeDate() }} |
`{{ d |
{{ d.localeTime() }} |
`{{ d |
{{ d.nice() }} |
`{{ d |
{{ d.uiTimestamp() }} |
`{{ d |
{{ d.cookie() }} |
`{{ d |
{{ d.iso8601() }} |
`{{ d |
{{ d.rfc822() }} |
`{{ d |
{{ d.rfc850() }} |
`{{ d |
{{ d.rfc1036() }} |
`{{ d |
{{ d.rfc1123() }} |
`{{ d |
{{ d.rfc2822() }} |
`{{ d |
{{ d.rfc3339() }} |
`{{ d |
{{ d.w3c() }} |
`{{ d |
{{ d.w3cDate() }} |
`{{ d |
{{ d.mySqlDateTime() }} |
`{{ d |
The following params have been removed:
Element Type | Old Param | New Param |
---|---|---|
All of them | childOf |
relatedTo.sourceElement |
All of them | childField |
relatedTo.field |
All of them | parentOf |
relatedTo.targetElement |
All of them | parentField |
relatedTo.field |
All of them | depth |
level |
Tag | name |
title |
Tag | setId |
groupId |
Tag | set |
group |
Tag | orderBy:"name" |
orderBy:"title" |
The following params are now deprecated, and will be removed in Craft 4:
Element Type | Old Param | New Param |
---|---|---|
All of them | order |
orderBy |
All of them | locale |
siteId or site |
All of them | localeEnabled |
enabledForSite |
All of them | relatedTo.sourceLocale |
relatedTo.sourceSite |
Asset | source |
volume |
Asset | sourceId |
volumeId |
Matrix Block | ownerLocale |
ownerSite or ownerSiteId |
Also note that the limit
param is now set to null
(no limit) by default, rather than 100.
The following methods have been removed:
Old | New |
---|---|
findElementAtOffset(offset) |
nth(offset) |
The following methods are now deprecated, and will be removed in Craft 4:
Old | New |
---|---|
ids(criteria) |
ids() (setting criteria params here is now deprecated) |
find() |
all() |
first() |
one() |
last() |
nth(query.count() - 1) |
total() |
count() |
The following element properties have been removed:
Element Type | Old Property | New Property |
---|---|---|
Tag | name |
title |
The following element properties have been deprecated, and will be removed in Craft 4:
Old | New |
---|---|
locale |
siteId , site.handle , or site.language |
The following model methods have been deprecated, and will be removed in Craft 4:
Old | New |
---|---|
getError('field') |
getFirstError('field') |
The following locale methods have been deprecated, and will be removed in Craft 4:
Old | New |
---|---|
getId() |
id |
getName() |
getDisplayName(craft.app.language) |
getNativeName() |
getDisplayName() |
Your front-end <form>
s and JS scripts that submit to a controller action will need to be updated with the following changes.
action
params must be rewritten in in snake-case
rather than camelCase
.
Old:
<input type="hidden" name="action" value="entries/saveEntry">
New:
<input type="hidden" name="action" value="entries/save-entry">
The following controller actions have been removed:
Old | New |
---|---|
categories/create-category |
categories/save-category |
users/validate |
users/verify-email |
users/save-profile |
users/save-user |
redirect
params must be hashed now.
Old:
<input type="hidden" name="redirect" value="foo/bar">
New:
<input type="hidden" name="redirect" value="{{ 'foo/bar'|hash }}">
The redirectInput()
function is provided as a shortcut.
{{ redirectInput('foo/bar') }}
The following redirect
param tokens are no longer supported:
Controller Action | Old Token | New Token |
---|---|---|
entries/save-entry |
{entryId} |
{id} |
entry-revisions/save-draft |
{entryId} |
{id} |
entry-revisions/publish-draft |
{entryId} |
{id} |
fields/save-field |
{fieldId} |
{id} |
globals/save-set |
{setId} |
{id} |
sections/save-section |
{sectionId} |
{id} |
users/save-user |
{userId} |
{id} |
CSRF protection is enabled by default in Craft 3. If you didn’t already have it enabled (via the enableCsrfProtection
config setting), each of your front-end <form>
s and JS scripts that submit to a controller action will need to be updated with a new CSRF token param, named after your csrfTokenName
config setting value (set to 'CRAFT_CSRF_TOKEN'
by default).
{% set csrfTokenName = craft.app.config.get('csrfTokenName') %}
{% set csrfToken = craft.app.request.csrfToken %}
<input type="hidden" name="{{ csrfTokenName }}" value="{{ csrfToken }}">
The csrfInput()
function is provided as a shortcut.
{{ csrfInput() }}
If you are using memcache
for your cacheMethod config setting and you did not have useMemcached
set to true
in your craft/config/memcache.php
config file, you'll need to install memcached on your server. Craft 3 will only use it because there is not a PHP 7 compatible version of memcache available.