MCP and OAuth
- Added an authenticated HTTP MCP server, which MCP clients can connect to after a user signs in and authorizes them via OAuth. Clients act as that user, within their permissions and the access level they grant (read only, read and write, or full access for admins). (#20145, #20189)
- The authenticated MCP server’s endpoints default to
/{cpTrigger}/mcpand/{cpTrigger}/mcp/auth, or/mcpand/mcp/authwhen public registration is enabled. Endpoints within the control panel can only be used by users with control panel access. (#20189) - Added a public HTTP MCP server, which exposes only the capabilities and content approved from Settings → MCP, without signing in. Its endpoint defaults to
/mcp. (#20145, #20189) - The HTTP MCP servers are discoverable via
/.well-known/ai-catalog.jsonand a Server Card at<endpoint>/server-card, per SEP-2127. Servers on control panel URLs are never advertised, and the authenticated server is only advertised when public registration is enabled. (#20283) - Added the
mcp/servecommand, which runs a stdio MCP server acting as a given user. (#20145) - Added Settings → MCP, where administrators can enable the authenticated and public MCP servers, set their endpoints, and approve the capabilities and content that the public server exposes. (#20145, #20189)
- Added support for
config/mcp.phpandconfig/oauth.phpconfig files. (#20145) - Added the
oauth/generate-keyscommand, which generates the keys used to sign OAuth access tokens. (#20145) - Added an OAuth authorization server, with discovery metadata, dynamic client registration, PKCE, and refresh token rotation, which the MCP servers and GraphQL API use. (#20145)
- Dynamic OAuth client registration is limited to 10 clients per IP address per hour by default, via the new
registrationRateLimitandregistrationRateLimitDurationOAuth config settings. (#20303) - Garbage collection now deletes expired and revoked OAuth authorization codes and tokens, and registered OAuth clients that were revoked or never authorized. (#20303)
- Plugins can register MCP capabilities via
craft\services\Mcp::EVENT_REGISTER_CAPABILITY_TYPES, OAuth resources viacraft\services\OAuth::EVENT_REGISTER_RESOURCES, and personal access token scopes viacraft\services\PersonalAccessTokens::registerScopeGroup(). (#20145, #20147) - Added
craft\base\ApplicationTrait::getOAuth(). (#20145) - Added
craft\base\InputSchemaFieldInterface. (#20145) - Added
craft\config\McpConfig. (#20145) - Added
craft\config\OAuthConfig. (#20145) - Added
craft\events\RegisterOAuthResourcesEvent. (#20145) - Added
craft\gql\GqlInputSchema. (#20145) - Added the
craft\mcpandcraft\oauthnamespaces. (#20145, #20147) - Added
craft\services\Config::CATEGORY_MCP. (#20145) - Added
craft\services\Config::CATEGORY_OAUTH. (#20145) - Added
craft\services\Config::getMcp(). (#20145) - Added
craft\services\Config::getOAuth(). (#20145) - Added
craft\services\Mcp. (#20145) - Added
craft\services\OAuth. (#20145) - Added
craft\utilities\ClearCaches::clear(). (#20145) - Plain Text, Email, Link, Number, Money, Lightswitch, Date, Time, Color, Country, Table, and option fields now implement
craft\base\InputSchemaFieldInterface. (#20145)
Personal Access Tokens
- Added the My Account → Tokens screen, where users can create personal access tokens for the APIs that offer them (such as the GraphQL API), rename them, and revoke tokens and the apps they’ve authorized. (#20147)
- MCP clients that users have authorized are listed on their My Account → Tokens screen, where they can be revoked. (#20291)
- Added
craft\base\ApplicationTrait::getPersonalAccessTokens(). (#20147) - Added
craft\services\PersonalAccessTokens. (#20147)
GraphQL
- GraphQL API requests can now be authenticated with OAuth access tokens, granting access to the content the authorizing user has permission to access. (#20147)
- GraphQL schema components can now support authenticated requests via an
authenticatedkey on their config. (#20147) - Added the
currentUserGraphQL query, which returns the user that authenticated the request. (#20147) - GraphQL schemas can now include read-only components for sites, sections, entry types, volumes, user groups, category groups, and tag groups. (#20055)
- Added the
sites,site,sections,section,entryTypes,entryType,volumes,volume,userGroups,userGroup,categoryGroups,categoryGroup,tagGroups, andtagGroupGraphQL queries, which are available when the schema includes the corresponding components. (#20055) - Elements now have
sitefields in GraphQL, when the schema includes the “Query for sites” component. (#20055) - Entries now have
sectionandtypefields in GraphQL, when the schema includes the “Query for sections” and “Query for entry types” components. (#20055) - Assets now have
volumefields in GraphQL, when the schema includes the “Query for volumes” component. (#20055) - Users now have
groupsfields in GraphQL, when the schema includes the “Query for user groups” component. (#20055) - Categories and tags now have
groupfields in GraphQL, when the schema includes the “Query for category groups” and “Query for tag groups” components. (#20055) - Added
craft\elements\db\EagerLoadPlan::$configureQuery. (#20147) - Added
craft\gql\base\ElementMutationResolver::requireUserAuthorization(). (#20147) - Added
craft\gql\queries\Settings. (#20055) - Added
craft\gql\types\CategoryGroup. (#20055) - Added
craft\gql\types\EntryType. (#20055) - Added
craft\gql\types\Section. (#20055) - Added
craft\gql\types\Site. (#20055) - Added
craft\gql\types\TagGroup. (#20055) - Added
craft\gql\types\UserGroup. (#20055) - Added
craft\gql\types\Volume. (#20055) - Added
craft\helpers\Gql::getSchemaUser(). (#20147) - Added
craft\models\GqlSchema::$user. (#20147) - Added
craft\services\Gql::getOAuthResource(). (#20147) - Added
craft\services\Gql::getSchemaForUser(). (#20147) - Added
craft\services\Gql::SCOPE_READ. (#20147) - Added
craft\services\Gql::SCOPE_WRITE. (#20147)
Multi-Site Editing
- Element edit pages now have a “Sites” pane in their sidebars on multi-site installs, in place of the “Status” pane, which lists the sites the element is saved to (that the user can edit), with status toggles and links to the element in each site, and lets authors add the element to other sites. (#20061)
- Element editor slideouts can now be switched to other sites, by clicking on a site name within the “Sites” pane. (#20061)
- Sites that are added to elements within the “Sites” pane can now be removed again. (#20061)
- Element edit pages’ sidebar pane headings now line up with the labels within the panes. (#20061)
- Deleting an element for the only site it exists in now soft-deletes it, unless it’s a draft or nested element. (#20060)
- Added
craft\base\Element::showSitesField(). (#20061) - Added
craft\base\Element::sitesFieldHtml(). (#20061) elements/save-draftaction responses now include acanDeleteForSitekey. (#20061)
Assets
- Assets are now initially saved to the site they were uploaded from. (#20060)
- Volumes now have a “Propagation Method” setting, which can be set to “Let each asset choose which sites it should be saved to”. New volumes default to this, as do existing volumes on single-site installs. Existing volumes on multi-site installs continue to save assets to all sites. (#20060)
- Assets in volumes set to let each asset choose which sites it should be saved to now have a “Sites” pane in their edit pages’ sidebars. (#20060)
- Assets in volumes set to let each asset choose which sites it should be saved to can now be deleted for individual sites, as long as they exist in another site. (#20060)
- Files can now be uploaded to Assets fields by pasting them from the clipboard while the field has focus. (#20183, #20212)
- Added support for JPEG XL (
.jxl) images, when ImageMagick supports it. JPEG XL images can now be uploaded, transformed, and set as a transform’s output format. - BMP images can now be transformed and edited in the Image Editor.
- Craft can now determine the dimensions of BMP and JPEG XL images on remote volumes without downloading them.
- Added the “Markdown” file kind, and
mdandmarkdownare now included in the defaultallowedFileExtensionsconfig setting value. Markdown files can be previewed with syntax highlighting. (#20089) - Added
craft\elements\Asset::KIND_MARKDOWN. (#20089) - Added
craft\models\Volume::$propagationMethod. (#20060) - Added
craft\services\Images::getSupportsBmp(). - Added
craft\services\Images::getSupportsJxl(). - Added
Craft.BaseUploader::addFiles(). (#20183, #20212) Craft.BaseUploadernow supports apasteZonesetting. Files pasted while focus is within it are passed toaddFiles(), which custom uploader classes must implement to support pasted files. (#20183, #20212)craft\services\Assets::getAssetById()now returns the asset in another site if it doesn’t exist in the current site, when no site ID is passed. (#20060)
Addresses
- Addresses within Addresses fields can now be edited from drafts of their owner elements, by users who can save the drafts but not the canonical owner elements. (#19881)
- Addresses now support drafts.
Color Fields
- Color fields now support translucent colors. Authors can choose translucent palette colors, and set custom colors’ opacity when the field allows it, via the color picker in browsers that support it or by entering an 8-digit hex value. (#8983, #20274)
- Color fields’ palette colors can now have an alpha channel, by entering an 8-digit hex value or via the color picker in browsers that support it. (#8983, #20274)
- Color fields now have an “Allow opacity for custom colors” setting. (#8983, #20274)
- Color fields now have a “GraphQL Mode” setting, which can be set to “Full data” or “Hex only”. New Color fields default to “Full data”, and existing Color fields default to “Hex only”. (#8983, #20274)
- Color field values’
hexproperties and GraphQL values now include an alpha channel (e.g.#0000ff80) when the color isn’t fully opaque. Fully-opaque colors are still output as 6-digit hex values. (#8983, #20274) - Color field values now have
alpha,rgba,hsla,rgbChannels,oklch, andcontrastColorproperties, and acontrastRatio()method. (#8983, #20274) - Color fields set to the “Full data” GraphQL mode now return objects with
hex,label,red,green,blue,alpha,hue,saturation,lightness,luma,rgb,rgba,rgbChannels,hsl,hsla,oklch,contrastColor, andcontrastRatiofields. (#8983, #20274) - Added
craft\fields\Color::$allowCustomAlpha. (#8983, #20274) - Added
craft\fields\Color::$fullGraphqlData. (#8983, #20274) - Added
craft\fields\data\ColorData::getAlpha(). (#8983, #20274) - Added
craft\fields\data\ColorData::getContrastColor(). (#8983, #20274) - Added
craft\fields\data\ColorData::getContrastRatio(). (#8983, #20274) - Added
craft\fields\data\ColorData::getHsla(). (#8983, #20274) - Added
craft\fields\data\ColorData::getOklch(). (#8983, #20274) - Added
craft\fields\data\ColorData::getRgbChannels(). (#8983, #20274) - Added
craft\fields\data\ColorData::getRgba(). (#8983, #20274) - Added
craft\gql\types\ColorData. (#8983, #20274) - Added
craft\gql\types\generators\ColorDataType. (#8983, #20274) - Added
craft\validators\ColorValidator::$allowAlpha. (#8983, #20274) craft\fields\data\ColorData::getHex()now has a$withAlphaargument, which defaults totrue. (#8983, #20274)craft\validators\ColorValidator::normalizeColor()now expands 4-digit hex values to 8 digits, and removes fully-opaque alpha channels from 8-digit hex values. (#8983, #20274)
Icon Fields
- Icon pickers can now include icons from custom icon sets. (#17101, #20277)
- Icon fields now have an “Icon Sets” setting, when any custom icon sets are registered, which determines which icon sets are selectable and in what order. (#17101, #20277)
- Added the
customIconPathconfig setting, which registers a “Custom” icon set made up of the SVG files within a directory, such as@config/icons. (#17101, #20277) - Icon field values now have a
setproperty, set to the handle of the custom icon set the icon was selected from (ornullfor Font Awesome icons). (#17101, #20277) - Icons selected from custom icon sets are stored as
set-handle:icon-name. (#17101, #20277) IconDataGraphQL objects now haveset,svg, andkeywordsfields. (#17101, #20277)- Icon field values now have
getSvg()andgetKeywords()methods. (#17101, #20277) - Plugins can register custom icon sets via
craft\services\Icons::EVENT_REGISTER_ICON_SETS. (#17101, #20277) - Added
craft\base\ApplicationTrait::getIcons(). (#17101, #20277) - Added
craft\events\RegisterIconSetsEvent. (#17101, #20277) - Added
craft\fields\data\IconData::$set. (#17101, #20277) - Added
craft\fields\data\IconData::getKeywords(). (#17101, #20277) - Added
craft\fields\data\IconData::getSvg(). (#17101, #20277) - Added
craft\fields\Icon::$iconSets. (#17101, #20277) - Added
craft\icons\DirectoryIconSet. (#17101, #20277) - Added
craft\icons\IconSet. (#17101, #20277) - Added
craft\services\Icons. (#17101, #20277) craft\helpers\Cp::iconSvg()now supportsset-handle:icon-namevalues. (#17101, #20277)Craft.IconPickernow supports asetssetting, which can be set to an array of icon set handles (fontawesomefor Font Awesome), or'*'for all icon sets. (#17101, #20277)
Custom Fields
- Custom fields now have “Default Tip” and “Default Warning” settings. (#19976)
- Added
craft\base\FieldTrait::$tip. - Added
craft\base\FieldTrait::$warning. - Added
craft\fieldlayoutelements\BaseField::defaultTip(). - Added
craft\fieldlayoutelements\BaseField::defaultWarning().
Templating
- Added the
{% render %}Twig tag. (#19988) {% render %}tags andrender()methods are allowed within the Twig sandbox. (#19988)- The
capitalize,lower,title, andupperTwig filters now havelanguagearguments, which default to the current application language. (#19558) - Added
craft\base\Renderable. (#19988) - Added
craft\helpers\Template::render(). (#19988) - Added
craft\i18n\Locale::languageId(). Illuminate\Support\Collectionnow has arender()macro, which replacescraft\elements\ElementCollection::render(). (#19988)craft\helpers\ElementHelper::normalizeSlug()now has a$languageargument, which defaults to the current application language. (#19558)craft\helpers\StringHelper::toLowerCase(),::toTitleCase(), and::toUpperCase()now have$languagearguments, which default to the current application language. (#19558)- Deprecated
craft\helpers\ElementHelper::renderElements().craft\helpers\Template::render()should be used instead. (#19988)
Control Panel
- The global Settings page now has an “Extend” section, which contains Plugins and MCP.
- Assets settings are now in the “Content” section of the global Settings page, and the “Media” section has been removed.
- Filesystem settings are now within Assets settings, at Settings → Assets → Filesystems.
- Queue jobs’ Job Data is now syntax-highlighted in the Queue Manager utility.
- The Project Config utility’s “Loaded Project Config Data” is now syntax-highlighted.
- Asset previews and the Project Config utility now use TanStack Highlight for syntax highlighting, rather than Prism. (#20088)
- Added
craft\web\assets\highlight\HighlightAsset. (#20088) - Deprecated
craft\web\assets\prismjs\PrismJsAsset.craft\web\assets\highlight\HighlightAssetshould be used instead.PrismJsAssetnow registers aPrismshim backed by TanStack Highlight, which supports Prism’s commonly-used APIs, but not grammar customization, hooks, or plugins. (#20088)
Security
- A rate limit is now enforced for
users/login,auth/verify-totp, andauth/verify-recovery-coderequests. (#20054) - Added the
defaultRateLimitanddefaultRateLimitDurationconfig settings, which configure the rate limits forusers/login,users/send-password-reset-email,auth/verify-totp, andauth/verify-recovery-coderequests. (#20054) - Removed support for the non-standard
ED256passkey algorithm. (#19701) - Uploaded file names and search keywords no longer retain bidirectional override characters and other invisible characters.
- Added
craft\helpers\StringHelper::areConfusable(). - Added
craft\helpers\StringHelper::stripPrivateAndUnassignedChars(). craft\helpers\StringHelper::invisibleCharsRegex()now also matches bidirectional embedding, override, and isolate characters; non-whitespace control characters; the word joiner, function application, combining grapheme joiner, and Mongolian vowel separator characters; and blank Hangul filler and braille characters.- Fixed a moderate-severity information disclosure vulnerability. (GHSA-hgwm-4xxx-w8jq)
- Fixed a moderate-severity authorization bypass vulnerability. (GHSA-vvm6-wrc8-5fgh)
Performance
- Saving nested elements on multi-site installs now executes fewer database queries. (#20090)
- Search indexing now executes fewer database queries. (#20090)
- User permission checks are now faster for users with many permissions. (#20149)
Other Changes
- Added the
project-config/checkcommand, which checks project config schema compatibility without applying changes. - MySQL database backups and restores now carry over SSL/TLS settings from the system’s MySQL option files (e.g.
ssl-caordisable-ssl-verify-server-cert), if the database connection doesn’t have anyPDO::MYSQL_ATTR_SSL_*attributes set. (#19366) - MySQL database backups and restores now respect the
PDO::MYSQL_ATTR_SSL_CAPATHandPDO::MYSQL_ATTR_SSL_CIPHERdatabase connection attributes. (#19366) - Element fixtures no longer create revisions by default. Set their
createRevisionsproperty totrueto retain the previous behavior. (#19626) - Added
craft\test\ElementFixtureTrait. (#19626) - Added
craft\base\NestedElementTrait::$touchOwnersOnSave. - Added
craft\elements\db\NestedElementQueryTrait::mustHaveField(). - Added
craft\elements\db\NestedElementQueryTrait::mustHaveOwner(). - Added
craft\helpers\Db::isSafeOrderBy(). (#19994)
Dependencies
- Craft now requires
league/oauth2-server,mcp/sdk, andsymfony/finder. (#20145) - Updated Axios to 1.20.0.
- Updated Twig to 3.30.
- Updated Fabric.js to v7. (#19628)
Bug Fixes
- Fixed a bug where entry and address indexes weren’t showing any results if they had a “Field” condition rule set to “is empty”.
- Fixed a bug where
craft\services\Elements::deleteElementsForSite()would remove elements’ site rows without deleting the elements, on single-site installs. (#20060) - Fixed a bug where saving a deeply-nested element on its own wouldn’t update its owners’
dateUpdatedtimestamps, which could cause new revisions to reuse stale nested content. (#19594) - Fixed a bug where deleting an element for a site while editing a provisional draft would delete the provisional draft for all sites. (#20061)
- Fixed a bug where DSNs set via database config arrays weren’t getting parsed, so MySQL’s
utf8charset wasn’t getting normalized toutf8mb4. (#20248) - Fixed a bug where
craft\i18n\Locale::getOrientation()was returningltrfor some right-to-left locales, such as Central Kurdish (ckb) and Punjabi in Arabic script (pa-Arab). (#20301) - Fixed a bug where batched queue jobs weren’t splitting into additional batches before reaching their time limits. (#20304)
- Fixed an error that occurred when passing
withProvisionalDraftsto a Matrix field via GraphQL. (#20308) - Fixed a bug where users who logged into the control panel with “Remember me” checked could get logged out after an hour of inactivity.
- Fixed a bug where default titles of assets with long filenames were taken from the end of the filename. (#20112)
- Fixed a bug where characters were stripped from text that wasn’t UTF-8 when it was converted to UTF-8, such as alt text read from images’ IPTC captions. (#20111)
- Fixed a bug where the
fields/mergeandfields/auto-mergecommands weren’t updating field condition rules that referenced the merged fields. (#19901) - Fixed a bug where the
fields/mergeandfields/auto-mergecommands weren’t updating Content Block fields’ layouts. (#19901) - Fixed a bug where curly quotes were allowed within asset folder and filenames. (#19904)
- Fixed a bug where the “Instructions” setting’s placeholder text wasn’t getting updated when changing a field layout element’s selected field.
- Fixed a bug where renaming an asset without including its file extension resulted in a validation error. (#19950)
- Fixed a bug where the Image Editor’s crop box couldn’t be resized in some cases. (#19980)
- Fixed an error that occurred when opening element selector modals on element edit pages with a
returnUrlparam. (#19983) - Fixed a bug where
orderBycriteria wasn’t getting applied to eager-loaded elements. (#19994) - Fixed a bug where
orderBy,offset, andlimitparams weren’t getting applied to element queries executed viaeagerly(). (#19994) - Fixed a bug where eager-loaded elements were getting reversed twice when both
orderByandinReversecriteria were set. (#19994) - Fixed a bug where skipped revisions were getting reported as root element lookup errors when resaving elements. (#20026)
- Fixed a bug where pruning revisions could delete nested element revisions in other sites that newer revisions still referenced. (#20042)
- Fixed a bug where “Save as a new draft” didn’t carry nested entries over to the new draft. (#20226)
- Fixed an error that occurred when logging in with a passkey that was registered with an older version of Craft. (#20052)
- Fixed a bug where closing an element selector modal while it was loading caused it to keep loading all of the source’s elements in the background. (#20100)
- Fixed a bug where moving assets and replacing existing files with the same name could fail. (#20128)
- Fixed a bug where nested entries added via drafts on multi-site installs could get an incomplete first revision, causing their nested content to be deleted when reverting to it. (#20151)
- Fixed a bug where nested entries owned by drafts could get their own revisions, which could cause a foreign key error when applying the draft on PostgreSQL. (#20151)