All skills

Craft CMS 5 front-end Twig development — atomic design, template architecture, components, Vite buildchain. Covers atoms/molecules/organisms, props/extends/block patterns, layout chains, view routing, content builders, image presets, Tailwind named-key collections, multi-brand CSS tokens, JavaScript boundaries (Alpine/DataStar/Vue, tabs, accordions), Vite asset loading, and front-end auth (login, registration, password reset, profiles). Triggers on: {% include ... only %}, {% embed %}, _atoms/_molecules/_organisms/_views/_builders, component--variant.twig, _component--props.twig, collect({}), utilities prop, data-brand theming, hero/card components, Matrix block rendering, craft.vite.script, vite.php, vite.config.ts, buildchain, per-page scripts, Blitz static/page caching, ImageOptimize, Imager-X, responsive images, srcset, image transforms, SEOmatic meta/OpenGraph/JSON-LD, Sprig, htmx, multi-language, hreflang, localization, Formie form styling, login/registration form, RSS/Atom/JSON feeds, XML sitemap, search page, .search(), headless GraphQL, Next.js/Nuxt/Astro integration, example-templates command, render builder, fluent BaseTag {{ tag.render() }}, progressive enhancement. Always use when creating, editing, or reviewing Craft front-end Twig templates, components, layouts, views, builders, buildchain, or front-end auth — including plugin template integration (Blitz, SEOmatic, Sprig, Formie, Imager-X). Do NOT trigger for PHP plugin/module development (craftcms) or content modeling (craft-content-modeling).

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

This session only. Nothing lands on disk.

referenceselement-partials.md

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

Element Partials

How to use entry.render() and the _partials/ template directory in Craft CMS 5 for reusable element rendering. Element partials are a front-end feature — the CP does not use them. For CP element display (chips, cards, table rows), see the craftcms skill's element-index.md.

Documentation

Common Pitfalls

  • Forgetting to create the partial template — render() silently falls back to a <p> tag with the element's title. No error, no warning — just a plain text title where your card should be.
  • N+1 queries inside partials — use .eagerly() for any related element queries. Each partial renders independently, so without eager loading, every card in a grid fires separate queries.
  • Assuming render() works in the CP — it's front-end only (TEMPLATE_MODE_SITE).
  • Hardcoding the partial path instead of using the convention — the lookup path is automatic. Don't {% include %} the partial manually when entry.render() works.
  • Overriding an entry type's handle per field and expecting the partial to follow — it does, but only on Craft 5.10.14+. On older versions the override is ignored for lookup, so the partial must live under the original handle.

Contents

How It Works

Every element in Craft 5 has a render() method that looks for a matching template in _partials/, renders it, and returns the HTML as a Twig\Markup object (safe for direct output).

{# Render a single entry #}
{{ entry.render() }}

{# Render a list of entries #}
{% for entry in craft.entries().section('blog').limit(10).all() %}
    {{ entry.render() }}
{% endfor %}

{# Render with custom variables #}
{{ entry.render({ size: 'compact', showExcerpt: false }) }}

If no matching template exists, render() falls back to a <p> tag containing the element's title — no error is thrown.

Template Lookup Path

render() builds a list of candidate templates and renders the first one that exists (lowest priority number wins). Since 5.8.0 plugins can add or reorder candidates through Element::EVENT_RENDER.

Priority 1: Overridden entry type handle (5.10.14+)

_partials/{refHandle}/{overriddenHandle}.twig

Entries only, and only when the entry type's handle is overridden for the field or section it's used in (per-usage overrides, 5.6.0+). Before 5.10.14 this candidate didn't exist: a cta type renamed to richTextCta inside a CKEditor field still rendered _partials/entry/cta.twig (craftcms/cms#18968).

Priority 2: Type-specific partial

_partials/{refHandle}/{providerHandle}.twig
  • {refHandle} — the element's reference handle: entry, asset, user, address
  • {providerHandle} — the field layout provider's handle: for entries, this is the original entry type handle

Examples:

_partials/entry/article.twig       ← article entry type
_partials/entry/blogPost.twig      ← blogPost entry type
_partials/asset/image.twig         ← (if assets had type-specific layouts)
_partials/user.twig                ← user fallback (no type handle)

Priority 3: Generic fallback

_partials/{refHandle}.twig

Examples:

_partials/entry.twig               ← fallback for all entry types
_partials/asset.twig               ← fallback for all assets

The most specific existing template wins. The generic fallback catches everything else.

Available Variables

Inside the partial template, the element is available under its reference handle:

Element Type Variable Name
Entry entry
Asset asset
User user
Address address
Custom element The element's refHandle() return value

For nested entries (Matrix, CKEditor), the entry variable has owner and field properties for accessing the parent context.

Passing Custom Variables

Pass an associative array to render(). The variables merge into the template context:

{{ entry.render({ size: 'hero', showMeta: true, imagePreset: 'wide' }) }}

In the partial, guard with defaults:

{# _partials/entry/article.twig #}
{% set size = size ?? 'default' %}
{% set showMeta = showMeta ?? true %}
{% set imagePreset = imagePreset ?? 'card' %}

<article class="article article--{{ size }}">
    {% set image = entry.featuredImage.eagerly().one() %}
    {% if image %}
        {{ image.render({ preset: imagePreset }) }}
    {% endif %}

    <h3><a href="{{ entry.url }}">{{ entry.title }}</a></h3>

    {% if showMeta %}
        <time datetime="{{ entry.postDate | atom }}">
            {{ entry.postDate | date('M j, Y') }}
        </time>
    {% endif %}
</article>

Common Patterns

Blog card grid

{# templates/blog/index.twig #}
{% set entries = craft.entries()
    .section('blog')
    .with(['featuredImage', 'author', 'topics'])
    .limit(12)
    .all() %}

<div class="grid grid-cols-3 gap-6">
    {% for entry in entries %}
        {{ entry.render({ size: 'card' }) }}
    {% endfor %}
</div>

Varying layout by entry type

The type-specific lookup means different entry types render differently without conditionals:

_partials/entry/article.twig      ← article layout (image + excerpt)
_partials/entry/video.twig        ← video layout (embed + duration)
_partials/entry/event.twig        ← event layout (date + venue)
{# All entries render with their own partial — no if/switch needed #}
{% for entry in entries %}
    {{ entry.render() }}
{% endfor %}

Nested element rendering

Matrix entries and CKEditor nested entries also support render():

{# Render Matrix blocks #}
{% for block in entry.contentBlocks.all() %}
    {{ block.render() }}
{% endfor %}

Each block type gets its own partial: _partials/entry/heroBlock.twig, _partials/entry/textBlock.twig, etc.

Eager loading inside partials

Use .eagerly() on custom relation fields to avoid N+1 queries when rendering a list:

{# _partials/entry/article.twig #}
{% set image = entry.featuredImage.eagerly().one() %}   {# custom Assets field #}
{% set categories = entry.topics.eagerly().all() %}     {# custom relation field #}
{% set author = entry.author %}                          {# native attribute — access directly #}

.eagerly() batches queries across all partials in the same render cycle.

Native attributes can't be .eagerly()-loaded. entry.author (also entry.authors, an asset's uploader, a user's photo) returns the element directly — not a query — so there's no .eagerly()/.one() to chain; entry.author.eagerly() throws. Cover them with .with(['author']) on the outer query instead, as the Blog card grid above does. See craft-content-modeling → relations-and-eager-loading.md for the full .with()-vs-.eagerly() rules.

Asset partials

{# _partials/asset.twig #}
{% set preset = preset ?? 'default' %}

{% if asset.kind == 'image' %}
    <img src="{{ asset.url }}"
         width="{{ asset.width }}"
         height="{{ asset.height }}"
         alt="{{ asset.alt ?? asset.title }}"
         loading="lazy">
{% else %}
    <a href="{{ asset.url }}">{{ asset.title }}</a>
{% endif %}

Configuration

Setting Default Purpose
partialTemplatesPath '_partials' Base directory within templates/ for partial lookup
// config/general.php
->partialTemplatesPath('_components/partials')

Or via environment variable: CRAFT_PARTIAL_TEMPLATES_PATH.

Source: SKILL.md on GitHub

1 warning16d3 checks · Risk SAFE
  • Gen Agent Trust Hub16d

    The skill is generally safe and follows professional Craft CMS development practices. A low-severity risk regarding indirect prompt injection was identified due to the typical architectural pattern of rendering rich-text content from the CMS database into the front-end templates.

  • Socket16d

    No alerts

  • Snyk16d

    Risk: MEDIUM · 1 issue

Signed by skilld at d3a91c1. 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 2 months ago

README badge

README badge for michtio/craftcms-claude-skills/craft-site