Help us improve
Share bugs, ideas, or general feedback.
From craft-developer
Patterns and breaking changes for Craft CMS 4 to 5 migrations. Use when upgrading Craft versions, fixing Craft 5 compatibility issues, updating code for Craft 5 patterns, or debugging migration problems. Trigger this skill when the user mentions upgrading Craft, when queries return empty results unexpectedly (the empty array breaking change), when GraphQL types aren't found (section prefix removal), when Matrix blocks aren't rendering (block.type → block.type.handle), or when planning an entrification strategy for categories/tags/globals. Also trigger when checking plugin compatibility for Craft 5 or asking about new features in 5.5–5.9. Covers Matrix changes, empty array behavior, GraphQL updates, entrification, conditional field layouts, and all features through 5.9.
npx claudepluginhub design-machines-studio/depot --plugin craft-developerHow this skill is triggered — by the user, by Claude, or both
Slash command
/craft-developer:craft-5-migration [file or section to migrate][file or section to migrate]The summary Claude sees in its skill listing — used to decide when to auto-load this skill
Key changes, breaking patterns, and migration strategies for Craft 5. Current latest: **5.9** (January 2026).
Guides technical evaluation of code review feedback: read fully, restate for understanding, verify against codebase, respond with reasoning or pushback before implementing.
Share bugs, ideas, or general feedback.
Key changes, breaking patterns, and migration strategies for Craft 5. Current latest: 5.9 (January 2026).
Matrix fields no longer contain "blocks." They contain nested entries.
Terminology:
Architecture:
New capability: Nesting blocks inside other blocks.
Everything is becoming entries:
This simplifies the system: one pattern for everything.
Critical change:
{# Craft 4: Empty array returns ALL results #}
{% set entries = craft.entries()
.relatedTo([])
.all() %}
{# Craft 5: Empty array returns NO results #}
Fix:
{% set entries = craft.entries()
.relatedTo(categoryIds|length ? categoryIds : null)
.all() %}
This is the most common migration bug.
Craft 4:
... on news_article_Entry { }
Craft 5:
... on article_Entry { }
Section prefix removed from entry type names.
Craft 4:
ingredients: {
blocks: [...]
}
Craft 5:
ingredients: {
entries: [...]
}
Query caching behavior changed. Review uses of:
.cache(){% cache %} tags with queries insideCraft 4:
{% for block in entry.contentBuilder.all() %}
{% switch block.type %}
{% case 'text' %}
Craft 5:
{% for block in entry.contentBuilder.all() %}
{% switch block.type.handle %}
{% case 'text' %}
Note: block.type is now an object. Use block.type.handle for comparisons.
Craft 4:
{% set entries = craft.entries()
.with(['contentBuilder'])
.all() %}
Craft 5: Same, but new lazy eager loading option:
{% for entry in entries %}
{% set image = entry.featureImage.eagerly().one() %}
{% endfor %}
Check empty arrays:
{# Before passing to relatedTo #}
{% if categoryIds|length %}
{% set entries = craft.entries()
.relatedTo(categoryIds)
.all() %}
{% else %}
{% set entries = craft.entries().all() %}
{% endif %}
block.type to block.type.handleUse MCP if available:
list_plugins → versions and status
Or check manually:
Create replacement:
Migrate content:
Create replacement:
Create replacement:
globalSet.field → entry.fieldCLI entrify commands (improved in 5.9):
entrify/categories, entrify/tags, entrify/global-set no longer require handlesIf Craft MCP is installed:
get_system_info → Current versions
list_plugins → Plugin compatibility
get_deprecations → Code to update
list_sections → Verify structure
list_fields → Verify fields migrated
list_entry_types → Verify entry types
get_last_error → Migration errors
read_logs → Full error context
run_query → Verify data integrity
Craft 5 offers performance opportunities:
{% for post in posts %}
{% set image = post.featureImage.eagerly().one() %}
{% endfor %}
Cleaner than upfront .with() for complex cases.
Review and optimize cache strategies with new caching behavior.
Entry types in global pool means:
Craft 5 supports showing/hiding field layout tabs and fields based on conditions:
The Matrix field's "Add" button can organize entry types into groups:
Key features added after the initial Craft 5 release:
5.5 — Range field, card view attributes, element partial template fallbacks, encodeUrl() Twig function
5.6 — Link field advanced options (ARIA, target, rel), entry type name overrides per section/Matrix, primarySite Twig variable, custom field handles in where/orderBy/select params, config/redirects.php, X-Craft-Site header
5.7 — Button Group and JSON field types, editability conditions (read-only fields), canonicalsOnly query param, Matrix entry versioning, icons/colors for option fields, searchTermOptions GraphQL argument
5.8 — Content Block field, Generated Fields, Matrix entry type groups with search, <handle>Entry GraphQL queries for Singles, "Validate" element actions, entry type descriptions
5.9 — Multi-page index sources, randomString()/uuid() Twig functions, hash filter algorithm support, inline list/card grid view modes for relational fields, Matrix bulk selection actions, unpublished drafts via GraphQL, UI Label Format and line breaks in titles, field editability conditions based on element, XLSX/YAML export