agentsclimarketplace

Craftcms ops

Skill 0xDarkMatter/claude-mods/skills/craftcms-ops

Craft CMS 5 development - content modeling, Twig templating, element queries, GraphQL, plugins, and the Craft 4-to-5 Matrix-as-entries change. Use for: craft cms, craftcms, craft 5, twig, pixel & tonic, matrix field, entry types, sections, element query, eager loading, blitz, project config, headless craft, craft graphql, craft plugin, craft 4 to 5 upgrade.From its SKILL.md

Install
npx -y skills add 0xDarkMatter/claude-mods --skill craftcms-ops

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

What its file declares

Copied from the file, not written here

The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.

SKILL.md

9.0 KB, as published. Nobody here has run it

Craft CMS Operations

Facts verified as of 2026-07.

Authoritative reference for Craft CMS 5.x development: content modeling, Twig templating, element-query optimization, GraphQL/headless setups, plugin development, and the Craft 4 → 5 migration. Craft is a self-hosted PHP application built on Yii 2, backed by MySQL or PostgreSQL.

Version note (verified against craftcms.com/docs/5.x, 2026-06): Craft 5 is current; Craft 6 exists. The defining Craft 5 change is that Matrix is now an entries-based field — Matrix "blocks" are gone, replaced by nested entries with entry types. Fields are globally reusable across all field layouts. Don't ship Craft 3/4 "Matrix block" guidance.


Craft 5 architecture at a glance

ConceptWhat it isCraft 5 change
SectionContainer exposing entry types + URL rulesThree kinds: Single, Channel, Structure
Entry TypeAtomic unit of content (fields, title, slug)Now global + reusable across sections, with per-section aliases
EntryAn instance of an entry typeCan be top-level or nested (inside Matrix/CKEditor)
FieldReusable input attached via field layoutsGlobally reusable — no per-field-instance duplication
Matrix fieldRepeatable nested contentNow stores entries (entry types), not "blocks". Nesting supported natively
Project ConfigVersion-controlled schema (config/project/)Source of truth for sections/fields/settings

Section types

TypeUse forHas URLs?Hierarchy?
SingleOne-off pages (home, about)Optional fixed URINo
ChannelStreams (blog, news, products)Yes, per-entry-type URI formatNo
StructureNested/ordered content (docs, nav)YesYes (drag-to-order, levels)

Element queries (the 80/20)

Everything readable in Craft is an element (entries, assets, users, categories, tags). You fetch them with element queries.

{# Channel entries, newest first #}
{% set posts = craft.entries()
  .section('blog')
  .type('article')
  .orderBy('postDate DESC')
  .limit(10)
  .all() %}

{# Eager-load relations to kill N+1 #}
{% set posts = craft.entries()
  .section('blog')
  .with(['author', 'featuredImage', 'categories'])
  .all() %}

{# Single entry by slug #}
{% set page = craft.entries().section('pages').slug('about').one() %}

{# Relations: entries related to a given category #}
{% set related = craft.entries().relatedTo(category).all() %}
NeedMethod
Filter by section.section('handle')
Filter by entry type.type('handle')
Eager-load relations.with(['field', 'field.subfield'])
Status.status('live') / .status(['live','expired'])
One vs many.one() / .all() / .count() / .exists()
Pagination{% paginate query as pageInfo, entries %}
Eager-load nested Matrix entries.with(['matrixField']) then loop nested entries

Eager-loading nested entries (Craft 5): because Matrix content is now entries, eager-load the Matrix field then iterate the nested entries by their entry type:

{% set page = craft.entries().section('pages').with(['body']).one() %}
{% for block in page.body.all() %}
  {% switch block.type.handle %}
    {% case 'text' %}{{ block.richText }}
    {% case 'image' %}{{ block.image.one().url }}
  {% endswitch %}
{% endfor %}

See references/twig-and-queries.md for the full query parameter catalog, pagination, and Twig patterns.


Twig conventions

PatternRule
Private templatesPrefix with _ (_layouts/, _partials/) so they're not directly routable
Layout inheritance{% extends '_layouts/base' %} + {% block content %}
Reusable markup{% include '_partials/card' with { entry: entry } %} or {{ include() }}
Avoid logic in templatesPush business logic to a module/plugin service, not Twig
Caching{% cache %} — only after queries are optimized, never to mask N+1

Headless / GraphQL

Craft ships a GraphQL API for decoupled frontends (Next.js, Nuxt, Astro, etc.).

ConcernApproach
SchemaDefine GraphQL schemas + scopes in Control Panel; generate a token per schema
AuthBearer token per schema; public schema for anonymous reads
AlternativeElement API plugin for custom JSON endpoints when GraphQL is overkill
CORSConfigure allowed origins for the headless frontend
Eager loadingGraphQL resolves relations efficiently; still design queries to avoid over-fetching

See references/graphql-and-plugins.md for schema setup, query shape, and plugin/module development.


Performance decision table

SymptomFix
Slow listing pagesEager-load with .with([...]) — the #1 Craft perf bug is N+1 inside loops
Repeated identical render{% cache %} tag (after query optimization)
Whole-site cache neededBlitz plugin (static page caching, granular invalidation)
Slow orderBy on custom fieldEnsure the underlying column/field is indexed
Heavy asset transformsPre-generate transforms; use Imgix/CDN

Project Config & deployment

  • Project Config (config/project/*.yaml) is the version-controlled source of truth for sections, fields, entry types, settings. Commit it.
  • Apply on deploy: php craft up (runs migrations + applies project config).
  • Environment-specific values go in .env and config/general.php (use App::env() / getenv()).
  • Data transformations belong in content migrations, not manual DB edits.

Craft 4 → 5 upgrade checklist

AreaWhat changedAction
MatrixBlocks → entries with entry typesTemplates iterating .type.handle mostly survive; re-check block-type field handles
FieldsNow globally reusableExpect field/entry-type proliferation post-upgrade — consolidate duplicates
Content storageReworked internal storageRun php craft up; test queries on staging
PHP/DBCraft 5 needs PHP 8.2+Verify host before upgrading
PluginsMany need a Craft 5-compatible releaseAudit plugin compatibility first

Full upgrade guidance: https://craftcms.com/docs/5.x/upgrade.html


Common gotchas

GotchaWhyFix
N+1 queries in loopsElement relations lazy-loadAlways .with([...]) before iterating
{% cache %} masking slow queriesCache hides, doesn't fixOptimize queries first, cache second
Business logic in TwigHard to test/reuseMove to a module/plugin service
Project Config drift in teamsOut-of-band CP editsTreat config/project/ as source of truth; php craft up on deploy
Untested migrations to prodData loss riskTest on staging clone first
Over-using MatrixComplexity + perf costUse simpler structures when nesting isn't needed
Calling old "Matrix block" APIsRemoved in Craft 5Use entry/entry-type APIs

Assets

FileUse
assets/entry-type-field-layout.mdAnnotated content-modeling starter: section + entry type + field layout + Matrix-as-entries shape, mapped to Project Config

See also

  • laravel-ops — shared PHP/Composer/Twig-adjacent tooling, Eloquent patterns for comparison
  • sql-ops — index strategy behind slow orderBy/relation queries
  • nginx-ops — serving Craft, caching headers, reverse proxy for headless

Key external resources

Keep looking

Skills are one crate of 627,801. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.