All skills
michtio avatar

/craft-content-modeling

@e594ffc

Craft CMS 5 content modeling — sections, entry types, fields, Matrix, relations, project config, and content architecture strategy. Covers choosing section types, designing entry types and field layouts, selecting field types, configuring Matrix and nested entries, relations and eager loading, and multi-site propagation. Triggers on: section types (single, channel, structure), entry types, field types, field layout design, field type selection, Matrix, nested entries, relatedTo, eager loading, .with()/.eagerly(), categories, tags, globals, global sets, preloadSingles, propagation, multi-site content, project config, YAML, content strategy, taxonomy, asset volumes, filesystems, image transforms, user groups, content permissions, entrify/entrification, CKEditor vs Matrix, CMS editions, multi-language, language groups, localization, translation method, field translation, content migration, field instances, Formie forms as elements vs project config, cross-environment Formie deployment, multi-site Formie translation, element index sources, elementSources, Entries index sidebar, tableAttributes, defaultSort, authoring schema from code (service layer vs project.yaml + pc/apply). Always use when planning content architecture, creating sections/fields, configuring Matrix, setting up relations, choosing field types, designing field layouts, planning multi-site propagation, or tidying the Entries element index. Do NOT trigger for PHP plugin/module development, custom field type code, front-end Twig, or buildchain.

Use this Skill: https://skilld.dev/gh/michtio/craftcms-claude-skills/craft-content-modeling

This session only. Nothing lands on disk.

referencesrelations-and-eager-loading.md

≈1.6k tokens on demand. Your agent reads this file only when SKILL.md points to it.

Relations & Eager Loading

How content connects in Craft CMS 5 — relatedTo queries, eager loading with .with(), and lazy eager loading with .eagerly().

Documentation

Relation Basics

Every relation has a source (element with the relational field) and a target (the selected element). Relations are stored in a relations table.

Accessing a relational field returns a new element query each time — parameters don't persist between accesses:

{# These are two separate queries #}
{% set query = entry.relatedEntries %}
{% set all = entry.relatedEntries.all() %}

relatedTo() — Four Shapes

1. Simple (bidirectional)

Finds elements related in either direction:

{% set related = craft.entries.relatedTo(myEntry).all() %}
{% set related = craft.entries.relatedTo(42).all() %}

2. Array of elements (OR logic)

Related to any of the given elements:

{% set recipes = craft.entries.relatedTo([protein, veggie]).all() %}

3. AND logic

Related to all of the given elements:

{% set recipes = craft.entries.relatedTo(['and', protein, veggie]).all() %}

4. Directional hash

Specify the relationship direction:

{# "Given this source, find its targets" #}
{% set targets = craft.entries.relatedTo({
    sourceElement: recipe,
}).all() %}

{# "Given this target, find what points to it" #}
{% set sources = craft.entries.relatedTo({
    targetElement: ingredient,
}).all() %}

{# Scope to a specific field #}
{% set sources = craft.entries.relatedTo({
    targetElement: category,
    field: 'topics',
}).all() %}

{# Matrix relations use dot notation #}
{% set sources = craft.entries.relatedTo({
    targetElement: image,
    field: 'contentBlocks.photos',
}).all() %}

Compound Criteria

{# Must be related to origin AND any of the proteins #}
{% set results = craft.entries
    .relatedTo(origin)
    .andRelatedTo(proteins)
    .all() %}

{# Exclude elements related to allergen #}
{% set safe = craft.entries
    .relatedTo(category)
    .notRelatedTo(allergen)
    .all() %}

Eager Loading with .with()

Batch-loads related elements upfront, solving the N+1 query problem. Use on listing pages where you display related content for multiple entries.

Basic Usage

{% set entries = craft.entries.section('blog').with([
    'featuredImage',
    'topics',
    'author',
]).all() %}

{# Access as normal — data is already loaded #}
{% for entry in entries %}
    {{ entry.featuredImage.one().getUrl('thumb') }}
{% endfor %}

With Criteria

Add query criteria to eager-loaded relations:

{% set entries = craft.entries.with([
    ['relatedArticles', { limit: 3, orderBy: 'postDate DESC' }],
    ['featuredImage', { withTransforms: ['hero', 'thumb'] }],
]).all() %}

Nested Eager Loading

Dot notation for relations within relations:

{% set entries = craft.entries.with([
    'topics',
    'topics.thumbnail',
    'author',
    'author.photo',
]).all() %}

Matrix Eager Loading

Prefix with entry type handle for Matrix nested relations:

{% set entries = craft.entries.with([
    'contentBlocks',
    'contentBlocks.image:photos',
    'contentBlocks.quote:author',
]).all() %}

Transform Eager Loading

Pre-generate image transforms:

{% set entries = craft.entries.with([
    ['heroImage', { withTransforms: ['large', 'thumb'] }],
]).all() %}

Lazy Eager Loading with .eagerly()

New in Craft 5. Defers batch-loading to the point of use — simpler API, works in partials without upstream coordination.

{% set posts = craft.entries.section('news').all() %}
{% for post in posts %}
    {% set image = post.featuredImage.eagerly().one() %}
    {% for topic in post.topics.eagerly().all() %}
        {% set icon = topic.thumbnail.eagerly().one() %}
    {% endfor %}
{% endfor %}

When to use which

Scenario Use
You know exactly which relations are needed .with()
Relations are accessed in reusable partials .eagerly()
You need transform preloading .with() + withTransforms
Mixed — some known upfront, some in partials Combine both

.eagerly() Limitations

  1. Native attributes — .eagerly() doesn't work for author, uploader, photo. Use .with().
  2. Custom criteria on relations — .eagerly() loads all related elements. Use .with() to filter/limit.
  3. Entry-type scoping in Matrix — .eagerly() can't scope by entry type. Use .with() with type-prefixed paths.
  4. Transform preloading — No withTransforms equivalent. Use .with().
  5. Auto-injected single entries — preloadSingles entries aren't part of the query chain.

Eager-Loadable Native Attributes

Attribute Element Types
author / authors Entries
uploader Assets
photo Users
addresses Users
parent / ancestors Structure entries, categories
children / descendants Structure entries, categories
localized All elements
drafts / revisions All elements
currentRevision All elements
owner / primaryOwner Nested entries

Common Pitfalls

  • Forgetting .eagerly() in loops — the #1 performance mistake. Every relational field inside a {% for %} should use .eagerly() or be covered by an upstream .with().
  • Using .relatedTo(entry) when you mean .relatedTo({ sourceElement: entry }) — the simple form is bidirectional. If you only want one direction, use the hash form.
  • Not scoping .relatedTo() to a field — without field: 'myField', it matches relations via ANY field. This gives unexpected results when multiple relation fields exist.
  • Eager loading transforms without withTransforms — images load but transforms aren't pre-generated, causing on-demand generation.
  • Deeply nested .with() paths — 'a.b.c.d.e' works but generates complex queries. Keep nesting to 2-3 levels max.

Source: SKILL.md on GitHub

No alerts16d3 checks · Risk SAFE
  • Gen Agent Trust Hub16d

    The skill provides comprehensive guidelines for content modeling in Craft CMS 5, including sections, fields, and project configuration. No malicious patterns, obfuscation, or unauthorized data exfiltration were detected. A low-severity finding is included regarding the potential for indirect prompt injection, as the skill instructs the agent to read local project configuration files which could be manipulated in a supply-chain attack to influence the agent's behavior.

  • Socket16d

    No alerts

  • Snyk16d

    Risk: LOW · No issues

Signed by skilld at e594ffc. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 2 weeks ago.

Activeupdated 3 months ago

README badge

README badge for michtio/craftcms-claude-skills/craft-content-modeling