Install
$ agentstack add skill-chrisrowe-craftcms-claude-skills-craft-content-modeling ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
Craft CMS 5 — Content Modeling
How to structure content in Craft CMS 5. Sections, entry types, fields, Matrix, relations, asset management, and strategic patterns for real projects.
This skill covers content architecture — what goes in the CP, how it's organized, and how templates access it. For extending Craft with PHP (plugins, modules, custom element types), see the craftcms skill. For Twig template patterns, see craft-site and craft-twig-guidelines.
Documentation
- Entries: https://craftcms.com/docs/5.x/reference/element-types/entries.html
- Sections: https://craftcms.com/docs/5.x/reference/element-types/entries.html#sections
- Fields: https://craftcms.com/docs/5.x/system/fields.html
- Field types: https://craftcms.com/docs/5.x/reference/field-types/
- Matrix: https://craftcms.com/docs/5.x/reference/field-types/matrix.html
- Relations: https://craftcms.com/docs/5.x/system/relations.html
- Eager loading: https://craftcms.com/docs/5.x/development/eager-loading.html
- Project config: https://craftcms.com/docs/5.x/system/project-config.html
Use web_fetch on specific doc pages when a reference file doesn't cover enough detail.
The Craft 5 Mental Model
Everything is an entry. Entry types are global (shared across sections and Matrix fields). Fields come from a global pool. Categories, tags, and globals are being phased out — use entries instead.
Three decisions define your content architecture:
- Which section type organizes the content (Single, Channel, Structure)
- Which entry types define its shape (global, reusable across contexts)
- Which relation strategy connects content together (Entries fields, Matrix, or both)
Section Type Decision
| Need | Section Type | URI Example | |------|-------------|-------------| | One-off page (homepage, about, contact) | Single | __home__, about | | Site-wide settings (footer, header config) | Single (no URI, preloadSingles) | — | | Flat collection (blog, news, events) | Channel | blog/{slug} | | Hierarchical pages (docs, services) | Structure | {parent.uri}/{slug} | | Taxonomy (topics, categories) | Structure (replaces categories) | topics/{slug} | | Flat tags | Channel (replaces tags) | — |
Singles replace globals
Set preloadSingles => true in config/general.php to access singles as global Twig variables by handle — identical to the old globals behavior but with drafts, revisions, live preview, and scheduling.
{# With preloadSingles enabled #}
{{ siteSettings.footerText }}
{{ siteSettings.socialLinks.all() }}
Caveat: Singles always propagate to all sites. This is hard-coded.
Structure queries for navigation
{% set topLevel = craft.entries.section('pages').level(1).all() %}
{% set children = craft.entries.descendantOf(entry).descendantDist(1).all() %}
{% set breadcrumbs = craft.entries.ancestorOf(entry).all() %}
{% set siblings = craft.entries.siblingOf(entry).all() %}
Entry Types in Craft 5
Entry types are defined globally (Settings → Entry Types), then attached to sections and Matrix fields. One entry type can serve multiple contexts.
Key implications:
- Changing an entry type's field layout affects every section and Matrix field using it
- Fields come from the global pool — same field definition reused everywhere
- Local name/handle overrides per context available (5.6.0+)
- The global pool demands careful field naming — use specific handles
Reserved handles (will collide with native attributes)
title, slug, id, uid, dateCreated, dateUpdated, status, url, uri, enabled, archived, siteId, level, lft, rgt, root, postDate, expiryDate
Common Pitfalls
- Over-using Matrix — if content needs its own URL, independent querying, or permissions, it should be a separate section with an Entries relation field, not a Matrix block.
- Vague field handles —
image,text,linkcollide fast in the global pool. UseblogFeaturedImage,serviceDescription,ctaLink. - Not planning multi-site from the start — propagation method, field translation methods, and site settings must be configured before content exists. Changing propagation later resaves all entries.
- Using categories/tags in new projects — they're deprecated. Use Structure sections (hierarchical taxonomy) and Channel sections (flat taxonomy) with Entries fields.
- Forgetting
preloadSingles— without it, singles aren't available as global variables and you need explicit queries. - Matrix for everything — 15+ entry types in one Matrix field is a red flag. Deeply nested Matrix hits
max_input_varslimits and degrades CP performance. - Not using
.eagerly()— every relational field access inside a loop should use.eagerly()to prevent N+1 queries. - Editing project config YAML manually — let Craft manage
config/project/. Usephp craft project-config/rebuildto regenerate from DB if needed. - Using database IDs in URI formats — IDs differ across environments. Use
{slug},{canonicalUid}, or custom fields. - Not setting
allowAdminChanges => falsein production — without this, production schema changes won't sync back to dev.
Reference Files
Read the relevant reference file(s) for your task.
Task examples:
- "Plan a blog content architecture" → read
content-patterns.md - "Which field type should I use for X?" → read
field-types.md - "Set up relatedTo queries" → read
relations-and-eager-loading.md - "Configure Matrix with nested entries" → read
field-types.md(Matrix section) - "Plan a multi-site content model" → read
content-patterns.md+ propagation in SKILL.md - "Understand project config workflow" → this SKILL.md covers the essentials
| Reference | Scope | |-----------|-------| | references/field-types.md | All 26 built-in field types: settings, Twig access patterns, query syntax, gotchas. Matrix configuration, view modes, nesting. | | references/relations-and-eager-loading.md | relatedTo() shapes (4 forms), .with() eager loading, .eagerly() lazy eager loading, nested eager loading, native eager-loadable attributes. | | references/content-patterns.md | Strategic patterns for blog, portfolio, multi-site corporate, e-commerce. Section/field/relation architecture per pattern. Categories-to-entries migration. |
Propagation Methods (Multi-Site)
Available for channels and structures. Singles always propagate to all sites.
| Method | Behavior | |--------|----------| | Only save to site created in | Entries exist in one site only | | Same site group | Entries propagate within the same site group | | Same language | Entries propagate to sites sharing the same language | | All enabled sites | Entries exist in all enabled sites (default) | | Let each entry choose | Per-entry control via Status sidebar |
Matrix fields have their own propagation method, independent of the section's.
Field Translation Methods
Per-field setting controlling how values behave across sites:
| Method | Behavior | |--------|----------| | Not translatable | Same value across all sites | | Per site | Independent value per site | | Per site group | Shared within site group, independent across groups | | Per language | Shared across sites with same language | | Custom | User-defined grouping key |
Configure translation methods before populating content.
Project Config Essentials
All schema changes (sections, entry types, fields, volumes, transforms, sites, plugins, permissions) are stored as YAML in config/project/.
Workflow
- Make CP changes in development environment
- YAML auto-updates in
config/project/ - Commit to Git
- Deploy to staging/production
- Run
ddev craft up(applies migrations + project config)
Rules
- Never manually edit YAML — let Craft manage it
- Always set
allowAdminChanges => falsein production - Use UIDs (not IDs) — they're stable across environments
- After resolving Git merge conflicts in YAML:
ddev craft project-config/touchthenddev craft project-config/apply - Use
$ENV_VARsyntax in YAML for environment-specific values
Asset Volumes and Transforms
Volumes define content organization. Filesystems define storage (local, S3, Google Cloud, Azure). Multiple volumes can share one filesystem.
Image Transforms
{# Named transform (defined in Settings → Assets → Image Transforms) #}
{# Ad-hoc transform #}
{% set transform = { width: 300, height: 200, mode: 'crop', format: 'webp' } %}
{# Srcset #}
{{ asset.getImg({ width: 300 }, ['1.5x', '2x', '3x']) }}
Always add the Alternative Text field layout element to asset volumes for accessibility.
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: chrisrowe
- Source: chrisrowe/craftcms-claude-skills
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.