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.

referencestwig-collections.md

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

Twig Collections Reference

collect() method reference for Craft CMS Twig templates. Craft CMS exposes Laravel's Collection class to Twig via the collect() function.

Documentation

Common Pitfalls

  • .all() returns arrays, .collect returns Collections — use .collect when you need Collection methods.
  • Arrow functions in Twig have no multiline support — keep them as single expressions.
  • merge() overwrites keys — if you merge { color: 'new' } into a collection with a color key, the old value is gone. This is the intended override mechanism.
  • implode() joins null values as empty strings, producing extra spaces — harmless for HTML class attributes (browsers normalize whitespace). Use classes.filter(v => v).implode(' ') for pristine output if needed.
  • Collections are immutable — merge(), filter(), etc. return new Collections. Reassign: set classes = classes.merge({}).

Creating Collections

{# From a hash (most common — props and classes) #}
{%- set props = collect({
    heading: heading ?? null,
    content: content ?? null,
    utilities: utilities ?? null,
}) -%}

{# From an array #}
{%- set items = collect(['one', 'two', 'three']) -%}

{# From a query result #}
{%- set entries = craft.entries.section('blog').collect -%}

Methods Used in Templates

Accessing Values

Method Description Example
get(key) Get value by key, null if missing props.get('heading')
get(key, default) Get value with fallback props.get('size', 'text-base')
first First item entries.first
last Last item entries.last
count Number of items entries.count
isEmpty True if empty props.get('items').isEmpty
isNotEmpty True if not empty entries.isNotEmpty

Transforming

Method Description Example
merge(hash) Merge values (overwrites existing keys) props.merge({ color: 'bg-red-500' })
map(arrow) Transform each item entries.map(e => e.title)
pluck(key) Extract single field entries.pluck('title')
implode(glue) Join values as string classes.implode(' ')
join(glue) Alias for implode items.join(', ')
values Reset keys to sequential collection.values
keys Get all keys props.keys
flatten Flatten nested arrays nested.flatten

Filtering

Method Description Example
filter(arrow) Keep items matching condition classes.filter(v => v)
reject(arrow) Remove items matching condition items.reject(i => i.isEmpty)
where(key, value) Filter by key-value entries.where('type', 'article')
whereIn(key, values) Filter by key in array entries.whereIn('sectionId', [1, 2])
unique Remove duplicates tags.unique
contains(value) Check if value exists items.contains('featured')

Sorting

Method Description Example
sort Sort ascending items.sort
sortBy(key) Sort by field entries.sortBy('title')
sortByDesc(key) Sort descending by field entries.sortByDesc('postDate')
reverse Reverse order items.reverse

Slicing

Method Description Example
take(n) First n items entries.take(3)
skip(n) Skip first n items entries.skip(1)
slice(start, length) Subset entries.slice(0, 5)
chunk(size) Split into groups entries.chunk(3)
groupBy(key) Group by field entries.groupBy('section')

Checking

Method Description Example
has(key) Key exists props.has('icon')
contains(value) Value exists in collection tags.contains('featured')
every(arrow) All items match items.every(i => i > 0)
some(arrow) Any item matches items.some(i => i == 'active')

Arrow Functions in Twig

Twig supports arrow functions for Collection methods:

{# Single parameter #}
entries.map(e => e.title)
entries.filter(e => e.heroImage.exists())
entries.sortBy(e => e.postDate)

{# With index/key #}
items.filter((value, key) => key != 'utilities')

{# Chaining #}
entries
    .filter(e => e.heroImage.exists())
    .sortByDesc(e => e.postDate)
    .take(6)
    .map(e => {
        title: e.title,
        url: e.url,
        image: e.heroImage.one(),
    })

Common Patterns

Props Collection

{# Standard props pattern #}
{%- set props = collect({
    heading: heading ?? null,
    content: content ?? null,
    button: button ?? null,
    image: image ?? null,
    utilities: utilities ?? null,
}) -%}

{# Access #}
props.get('heading')
props.get('size', 'text-base')

{# Merge additional props in variant #}
{%- set props = props.merge({
    icon: icon ?? null,
    badge: badge ?? null,
}) -%}

Class Collection (Named Keys)

{# Build classes with named concerns #}
{%- set classes = collect({
    layout: 'flex flex-col gap-4',
    color: 'bg-brand-surface text-brand-heading',
    spacing: 'p-6',
    radius: 'rounded-lg',
    shadow: 'shadow-md',
    utilities: props.get('utilities'),
}) -%}

{# Render #}
class="{{ classes.implode(' ') }}"

{# Override a slot #}
{%- set classes = classes.merge({ color: 'bg-brand-primary text-brand-on-primary' }) -%}

{# Filter out null/empty values before imploding #}
class="{{ classes.filter(v => v).implode(' ') }}"

Entry Queries as Collections

{# Use .collect instead of .all() for Collection methods #}
{%- set articles = craft.entries
    .section('blog')
    .limit(10)
    .eagerly()
    .collect
-%}

{# Now use Collection methods #}
{%- set featured = articles.filter(a => a.featured).first -%}
{%- set rest = articles.reject(a => a.featured).take(6) -%}
{%- set tags = articles.pluck('tags').flatten.unique -%}

One Result Per Author (Greatest-N-Per-Group)

To limit a query to one entry per author (e.g. each author's latest), dedupe in a Collection — don't reach for a SQL GROUP BY. In Craft 5 authors moved to the entries_authors junction, and .author()/.authorId() resolve via an EXISTS subquery rather than a JOIN, so there's no authorId column to group on. (GROUP BY also returns an arbitrary row per group, so it can't reliably hand you the latest per author anyway.)

Order the query first, then unique keeps the first occurrence per key:

{%- set latestPerAuthor = craft.entries
    .section('blog')
    .with(['author'])          {# native attribute — eager-load with .with(), never .eagerly() #}
    .orderBy('postDate DESC')
    .collect
    .unique('authorId')        {# keeps the first (newest) entry per author #}
    .take(6)
-%}

unique('authorId') keys on the primary author (entry.authorId = the first author). If sections allow multiple authors (maxAuthors > 1) and you need every co-author represented, iterate entry.authors and dedupe differently. This fetches more rows than the final count — fine for bounded front-end lists; for genuinely large sets the only DB-level option is a raw greatest-n-per-group query via craft.db.

Building Data Structures

{# Build navigation items from entries #}
{%- set items = craft.entries
    .section('pages')
    .level(1)
    .collect
    .map(e => collect({
        title: e.title,
        url: e.url,
        active: craft.app.request.url == e.url,
        children: e.children.collect.map(c => collect({
            title: c.title,
            url: c.url,
        })),
    }))
-%}

Conditional Class Composition

{# Classes that vary based on props #}
{%- set classes = collect({
    layout: 'grid gap-4',
    columns: props.get('columns') == 2 ? 'lg:grid-cols-2' : 'lg:grid-cols-3',
    color: props.get('inverted') ? 'bg-brand-primary text-brand-on-primary' : 'bg-brand-surface',
    utilities: props.get('utilities'),
}) -%}

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