Craft CMS Operations
Versions verified 2026-10-05 against Packagist and the official docs. Craft 5.x is
current (5.11); Craft 4 and 3 are past security support; Craft 6 is in alpha.
scripts/check-craft-facts.py --live re-checks the plugin majors on a schedule.
The recurring agency shape: Craft 5 (or a 4/3 site awaiting upgrade) on DDEV, with
SEOmatic, Blitz, Formie, and CKEditor, a Vite (or legacy Laravel Mix) front end, and
modules tested with Codeception and ECS. This file is the procedure and the router;
each topic lives in one reference.
Step 1 - Orient before touching anything
| Check |
Where |
Tells you |
| Craft + plugin versions |
composer.json, ddev composer show craftcms/cms |
Which line of the version matrix applies - Craft 4 and 5 APIs differ |
| Local environment |
.ddev/config.yaml |
PHP/DB versions; run everything as ddev craft, ddev composer, ddev npm |
| Schema |
config/project/*.yaml |
Source of truth for sections, fields, entry types, plugin settings |
| Config |
config/general.php, config/<plugin>.php, .env |
Environment-specific behaviour (devMode, allowAdminChanges, caching) |
| Front-end build |
package.json + config/vite.php, or webpack.mix.js |
craft-vite or Laravel Mix |
| Templates |
templates/_layouts, _partials, _partials/entry/<type>.twig |
Layout inheritance and element partials |
| PHP |
modules/, tests/, codeception.yml, ecs.php |
Where logic and tests live |
| Page cache |
config/blitz.php, Blitz utility |
Whether what you see is cached HTML - check before debugging "my change isn't showing" |
Step 2 - Route the task
| Task |
Read |
| Listing pages, relations, Matrix/nested entries, pagination, multi-site |
element-queries.md |
| Printing anything user-influenced, forms that POST, JS data, rich text output |
twig-security.md |
| Meta tags, JSON-LD, sitemaps, robots.txt, hreflang |
seomatic.md |
| Static caching, "changes don't show", cache warming, dynamic bits on cached pages |
blitz.md |
| Building, theming, or debugging forms; spam; form emails not sending |
formie.md |
| Rich-text fields, nested entries inside rich text, Redactor conversion |
ckeditor.md |
Craft on DDEV: the craftcms type, ddev craft, Craft's upload_dirs (generic DDEV: ddev-ops) |
ddev.md |
| Tests for modules/plugins, fixtures, ECS/PHPStan |
codeception.md |
| Front-end assets, Vite dev server, critical CSS |
craft-vite.md |
| Moving off Laravel Mix/Webpack, or Vue 2 to Vue 3 |
frontend-upgrade-ops (the full migration playbook) |
| Upgrading Craft 3 → 4 → 5, plugin version lines, Craft 6 status |
upgrades.md |
| Slow pages, images, queue setup, production config |
performance.md |
| Core Web Vitals failing (LCP, INP, CLS), PageSpeed or Search Console flags |
web-perf-ops (field data first; its references/craft.md maps the Craft levers) |
| Headless front ends, GraphQL schemas and tokens |
graphql.md |
| Modules, plugins, events, migrations, queue jobs |
plugin-development.md |
| Modeling a new flexible page type |
assets/entry-type-field-layout.md |
Step 3 - Hold the non-negotiables
- Eager-load before you loop.
.with([...]) up front; .eagerly() inside shared
partials (Craft 5 only). One query per card is the most common Craft perf bug.
- Escape by context. Never
|raw user-influenced values; |e('js') inside JS
strings; {{ csrfInput() }} in every POST form - csrfInput({ async: true }) on
cached pages.
- Schema only through Project Config. Change it in the CP locally, commit
config/project/, run php craft up on deploy. Production runs
allowAdminChanges => false; never hand-edit the YAML.
- Data changes are content migrations (
php craft migrate/create), tested on a
copy of production - not CP clicking on live.
- Production runs a queue worker. Blitz regeneration, Formie emails and
integrations, transform pre-generation, and resaves are all queue jobs.
- Fix queries, then cache. Blitz for public pages; on Blitz-cached pages drop
{% cache %} (or set enableTemplateCaching false).
- Logic lives in module services, not Twig - services are testable.
- Stay on the latest Craft 5 patch. Craft ships regular Twig/RCE security fixes.
Version matrix
| Package |
Craft 3 |
Craft 4 |
Craft 5 (current) |
craftcms/cms |
3.9 (EOL) |
4.18 (EOL Apr 2026) |
Craft 5.x (5.11); Craft 6 in alpha |
nystudio107/craft-seomatic |
3.x |
4.x |
SEOmatic 5 |
putyourlightson/craft-blitz |
3.x |
4.x |
Blitz 5 (5.13 needs Craft 5.6+) |
verbb/formie |
1.x |
2.x |
Formie 3 (Formie 4 in beta) |
| Rich text |
Redactor |
Redactor or craftcms/ckeditor 3.x |
CKEditor plugin 5.x (needs Craft 5.10+; 4.x below that) |
nystudio107/craft-vite |
1.x |
4.x |
craft-vite 5 |
nystudio107/craft-imageoptimize |
1.x |
4.x |
ImageOptimize 5 |
spacecatninja/imager-x |
3.x |
4.x |
Imager-X 6 (5.x still maintained) |
codeception/codeception |
match core's require-dev |
match core's require-dev |
Codeception 5 |
Requirements for Craft 5: PHP 8.2+, MySQL 8.0.17+ / MariaDB 10.4.6+ / PostgreSQL 13+.
Details and plugin upgrade notes: upgrades.md.
Everyday commands
Command (prefix ddev locally) |
Does |
craft up |
Pending migrations + Project Config - run on every deploy |
craft project-config/apply / project-config/rebuild |
Apply YAML to the DB / rebuild YAML from the DB |
craft clear-caches/all |
Data, template, and asset caches |
craft queue/run / queue/info / queue/retry all |
Work and inspect the queue |
craft blitz/cache/refresh |
Refresh Blitz after template deploys (Blitz tracks content, not templates) |
craft migrate/create <name> |
New content migration |
craft make <type> |
Scaffold modules/plugins/components (craftcms/generator) |
craft entrify/categories <group> |
Convert categories (or tags, global-set) to entries |
Craft 5 content model
| Concept |
What it is |
Craft 5 change |
| Section |
Single, Channel, or Structure; holds entry types + URI formats |
- |
| Entry type |
The unit of content shape |
Global and reusable across sections and Matrix fields |
| Field |
Reusable input |
Global, with multi-instance use in one layout |
| Matrix field |
Repeatable nested content |
Stores nested entries, not blocks |
| CKEditor field |
Rich text |
Can hold nested entries inline |
| Element partials |
_partials/entry/<typeHandle>.twig |
.render() renders nested entries through them |
| Project Config |
config/project/ YAML |
Source of truth - commit it |
Pick the section type by shape: Single for one-off pages (home, contact),
Channel for streams (news, events), Structure for hierarchies (pages, docs).
Prefer flat entry types plus a Matrix "page builder" field over many near-identical
sections. Starter shape: entry-type-field-layout.md.
Gotchas
| Symptom |
Cause |
Fix |
| Listing page runs hundreds of queries |
Relation fetched per item |
.with() / .eagerly() |
| Change visible in CP, not on site |
Blitz/CDN serving cached HTML |
blitz.md |
| Form submissions save, emails never send |
Queue not running in production |
Queue daemon (performance.md) |
| Assets unstyled in production only |
Vite 5+ manifest is in dist/.vite/ |
Set manifestPath (craft-vite.md) |
{% set seomatic.meta.seoTitle = ... %} does nothing |
set only reads |
{% do seomatic.meta.seoTitle('...') %} |
| Staging copy of the site got indexed / prod de-indexed |
SEOmatic environment wrong |
Check robots in view-source (seomatic.md) |
| Project Config conflicts between developers |
CP edits on shared/prod environments |
allowAdminChanges false outside local; one schema change per PR |
| Event handler fires several times per save |
Drafts, revisions, propagation |
Guard with ElementHelper::isDraftOrRevision() + propagating |
craft.matrixBlocks() errors after upgrade |
Removed in Craft 5 |
craft.entries().field(...).owner(...) |
Bundled resources
| File |
Use |
assets/entry-type-field-layout.md |
Content-modeling starter: section + entry type + Matrix-as-entries, mapped to Project Config |
assets/craft-facts.json |
The version facts this skill documents (package, major, prose token) |
scripts/check-craft-facts.py |
Staleness verifier: --offline (prose still states the facts) / --live (Packagist majors) |
See also
ddev-ops (generic DDEV: version pinning, env files, snapshots and sanitised pulls,
Mutagen, add-ons, Xdebug, and an auditor for .ddev/ mistakes)
laravel-ops (Composer/PHP tooling; Craft 6 is Laravel-based) · sql-ops (indexes behind
slow orderBy) · nginx-ops (serving Craft, Blitz rewrites) · perf-ops (profiling) ·
tailwind-ops · playwright-ops (browser tests against the DDEV URL)
web-perf-ops (Core Web Vitals: CrUX/RUM first, then the failing subpart; Blitz, transforms,
craft-vite critical CSS and SEOmatic/Formie script placement as INP/LCP levers)
frontend-upgrade-ops (Laravel Mix or Webpack to Vite via craft-vite, Vue 2 to Vue 3,
Vue islands in Twig, and when a widget should become Alpine instead)
a11y-ops (WCAG 2.2 for Twig sites: heading levels across partials, asset alt text, Formie
and CKEditor markup, multi-site lang; see its references/server-rendered-templates.md)
security-ops (Craft/Twig/PHP security: CSRF and Formie, allowAnonymous, devMode and the
security key, GraphQL scoping, uploads, advisories and Craft 3/4 end of life; its
references/craft-*.md, twig-*.md, php-*.md)
- Craft 5 docs · Plugin Store ·
Craft security advisories
Why this shape: a 2026 survey of 57 Craft-agency repositories found Craft in 36
(Craft 5 ×17, 4 ×13, 3 ×6), DDEV in 36, SEOmatic in 33, Blitz 21, Formie 18,
CKEditor 18, Laravel Mix 18 versus craft-vite 12, Tailwind 12, ECS 11, and Codeception
10. The references follow that frequency; upgrades matter because half the Craft sites
were on an EOL major.