michtio/craftcms-claude-skills/craftcms

Claude Code and agent skill by michtio/craftcms-claude-skills

Trust tier 2
  • community
  • ext:[{"type":"hook","description":"Pre/post lifecycle hooks"}]
Source check pending

Checking that the SKILL.md file is still in the repository.

Craft CMS 5 plugin and module development — extending Craft with PHP. Covers elements, element queries, services, models, records, controllers, migrations, queue jobs, console commands, field types, native fields, events, behaviors, Twig extensions, widgets, filesystems, permissions, project config, GraphQL, testing, and debugging.

Install

npx vskill@latest install michtio/craftcms-claude-skills/craftcms

Semi-extensibleMentions customization, but needs skill-specific knowledge. Read the extensible skills standard

SKILL.md

Craft CMS 5 — Extending (Plugins & Modules)

Reference for extending Craft CMS 5 through plugins and modules. Covers everything from elements and services to controllers, migrations, fields, and events.

This skill is scoped to extending Craft — building plugins, modules, custom element types, field types, and backend integrations. For site/platform development (content modeling, sections, entry types, Twig templating, plugin selection), see the craft-site skill.

Companion Skills — Always Load Together

When this skill triggers, also load:

  • `craft-php-guidelines` — PHPDoc standards, section headers, naming conventions, class organization, ECS/PHPStan, verification checklist. Required for any PHP code.
  • `ddev` — All commands run through DDEV. Required for running ECS, PHPStan, scaffolding, and tests.
  • `craft-garnish` — When working on CP JavaScript, asset bundles, or interactive CP components. Covers Garnish's class system, UI widgets (Modal, HUD, DisclosureMenu, Select), drag system, and the Craft.* JS class pattern.
  • `craft-pest` — When writing, running, fixing, or reviewing tests. Covers the markhuot/craft-pest-core harness, database isolation (its rollback is opt-in and its env overrides are cwd-bound — both cause silent writes to the dev database), factories, HTTP/queue/DB assertions, and CI test jobs.
  • `craft-cloud` — When the project is hosted on Craft Cloud (detect via craft-cloud.yaml at the repo root or craftcms/cloud in composer.json). Required for plugin Cloud-compatibility constraints — App::isEphemeral() guards, asset-bundle CDN publishing, 15-minute queue-job cap, csrfInput() function over raw token output, and the cloud/up deploy lifecycle events.

Documentation

  • Extend guide: https://craftcms.com/docs/5.x/extend/
  • Class reference: https://docs.craftcms.com/api/v5/
  • Generator: https://craftcms.com/docs/5.x/extend/generator.html

Use WebFetch on specific doc pages when a reference file doesn't cover enough detail.

Common Pitfalls (Cross-Cutting)

  • Always use addSelect() in beforePrepare() — it's the Craft convention and safely additive when multiple extensions contribute columns.
  • Queue workers run in primary site context — use ->site('*') for cross-site queries.
  • Including id in getConfig() — project config uses UIDs, never database IDs.
  • Business logic in models or controllers — services are where logic belongs.
  • Modules need manual template root, translation, and controllerNamespace registration — nothing is automatic.
  • DateTimeHelper in elements/queries, Carbon in services — never mix in the same class.
  • Hardcoding /admin in CP URLs — cpTrigger is configurable. Use UrlHelper::cpUrl() in PHP, cpUrl() in Twig.
  • Passing $request->getBodyParams() directly to savePluginSettings() on split-settings pages — only submitted keys persist, other settings are silently dropped. Load the full settings model first, update properties, then save.
  • Naming a route or query param `token` — it collides with Craft's reserved tokenParam and the request is rejected with a 400 before your controller runs. See controllers.md (Reserved request params).
  • `EVENT_DEFINE_RULES` on `Entry::class` runs for every entry, including nested entries the CP builds before fieldId/ownerId exist. getType()/typeId throws Either \sectionId\ or \fieldId\ + \ownerId\ must be set on the entry. there, which breaks adding any nested entry. Always add the rule, decide in 'when' behind an isset($entry->fieldId) || isset($entry->sectionId) guard, and validate with a Validator subclass. See events.md (Validation Rules on Core Elements From a Module).
  • `project-config/write` empties `config/project/` and rewrites it from the DB. On a stale local DB it deletes teammates' YAML. Run craft up first, require a clean project-config/diff, and check git diff -- config/project afterwards. Schema saves from PHP belong in a migration or console command, never a bare bootstrap script (YAML is only flushed on EVENT_AFTER_REQUEST). See deployment.md (project-config/write safely) and migrations.md (Project config writes outside a request).
  • Any non-underscore-prefixed template in a plugin's templates/ dir is directly routable in the CP, bypassing your controller's beforeAction() gates. Underscore-prefix every template that isn't an intentional direct route. See cp.md (CP template routing bypasses controllers).

Reference Files

Read the relevant reference file(s) for your task. Multiple files often apply together.

Task examples:

  • "Build a custom element type" → read elements.md (Architecture section first) + element-index.md + fields.md + migrations.md + cp.md
  • "Build a hierarchical/tree element type" → read elements.md (Architecture: One Element Class with Native Structure)
  • "Add a webhook endpoint" → read controllers.md + events.md
  • "Create a queue job that syncs elements" → read queue-jobs.md + elements.md + debugging.md
  • "Add a settings page with form fields" → read controllers.md + cp.md + architecture.md
  • "Register a custom field type" → read fields.md + events.md
  • "Require a field only on one entry type from a module / add a rule to Entry" → read events.md (Validation Rules on Core Elements From a Module)
  • "Adding a nested entry fails: Either sectionId or fieldId + ownerId must be set" → read events.md (Validation Rules on Core Elements From a Module)
  • "Write project config to YAML after a local change / project-config/write deleted YAML" → read deployment.md (project-config/write safely)
  • "saveField()/saveEntryType() from a script changed nothing in YAML" → read migrations.md (Project config writes outside a request)
  • "Fix PHPStan errors" → read quality.md
  • "Add a dashboard widget" → read cp-components.md (Dashboard Widgets) + events.md (Widget Types section)
  • "Expose template variables for plugin users" → read events.md (Twig Extensions section)
  • "Attach custom methods to entries" → read events.md (Behaviors section)
  • "Register a custom element or field type" → read events.md (Registration Pattern → Registration scope — never gate by request context)
  • "Trashed custom elements never get deleted / gc not purging / rows piling up in the elements table" → read events.md (Registration Pattern → Registration scope)
  • "Build a CP utility page" → read cp-components.md (Utility Pages) + events.md (Utilities section)
  • "Set up Vite for a plugin's CP assets" → read plugin-vite.md + load craft-garnish skill
  • "Add drag-to-reorder or interactive JS to a CP page" → load craft-garnish skill
  • "Write CP JavaScript for a custom field type" → read fields.md + load craft-garnish skill
  • "Build a headless Craft API" → read graphql.md + load craft-site skill for headless.md
  • "Configure preview for a Next.js front-end" → load craft-site skill for headless.md
  • "Set up Pest tests for a plugin" → load craft-pest skill → isolation.md
  • "Write a test for a controller action" → load craft-pest skill → patterns.md
  • "Tests are writing to my dev database / created thousands of stray elements" → load craft-pest skill → isolation.md
  • "Create or delete sites from code (importer, provisioning command, fixture)" → read architecture.md (Creating or deleting sites at runtime)
  • "Element queries stopped filtering by siteId after I created a site" → read architecture.md (Creating or deleting sites at runtime — refreshSites() doesn't invalidate the variant queries read)
  • "Deleted a site but sections/category groups still reference it" → read architecture.md (Creating or deleting sites at runtime — deleteSite()'s prune vs memoized service caches)
  • "Compare a record's dateUpdated against a cutoff / 'changed since' filter is off by hours" → read architecture.md (Those strings are naive UTC)
  • "Read a datetime column out of an ActiveRecord into a model" → read architecture.md (Record-to-Model Hydration Boundary)
  • "My element query with ['like', ...] returns nothing" → read architecture.md (Element-query param setters don't take Yii operator tuples)
  • "Match elements by title/slug prefix" → read architecture.md (Element-query param setters don't take Yii operator tuples)
  • "Configure Redis for caching and sessions" → read config-app.md
  • "Store PHP sessions in the database (load balancer, no Redis)" → read config-app.md (Session Component — craft\web\DbSession, setup/php-session-table)
  • "Set up environment variables for production" → read config-bootstrap.md
  • "Find a GeneralConfig setting" → read config-general.md
  • "Read a config value in plugin code (App::env, parseEnv, GeneralConfig)" → read config-bootstrap.md + config-general.md
  • "Check if allowAdminChanges is enabled in plugin code" → read config-general.md + cp.md (Read-Only Mode)
  • "Resolve env vars in plugin settings ($MY_API_KEY)" → read config-bootstrap.md (App::parseEnv)
  • "Understand CRAFT_* env var conventions" → read config-bootstrap.md
  • "Configure mail transport / SMTP" → read config-app.md
  • "Set up custom URL routes" → read config-bootstrap.md
  • "Configure search to find short words" → read config-app.md
  • "Set up GraphQL tokens and schemas" → read graphql.md + config-general.md
  • "GraphQL author/uploader fields disappeared after upgrading to Craft 5.11" → read graphql.md (Craft 5.11 Additions — "Query for users" scope)
  • "Set up caching for a high-traffic site" → read caching.md
  • "Register custom permissions for my plugin" → read permissions.md
  • "Check user permissions in templates" → read permissions.md
  • "Set up plugin editions / feature gating" → read architecture.md (Plugin Editions section)
  • "Build a fluent render builder / craft.plugin.widget().render()" → read architecture.md (Render builders: attribute merging, option allowlists)
  • "Site can't override my plugin's front-end CSS / plugin styles win" → read architecture.md (Plugin-registered CSS loads after the site's stylesheet)
  • "Plugin JS works with shipped templates but not user-written ones" → read architecture.md (The JS-to-markup contract is public API)
  • "Generate docs/schemas/types from a registry / generator output differs per install or edition" → read quality.md (Generated Artifacts Must Not Read Runtime Registries)
  • "Cut a release / tag a version / Packagist isn't serving the new version" → load the craft-plugin-release skill
  • "Deleted a site but my plugin's per-site rows are still there" → read architecture.md (Site deletion is a soft delete)
  • "Cached false / cache returns false and I can't tell if it's a miss" → read caching.md (Data Caching → boolean sentinel idiom)
  • "Where should plugin/operational settings (thresholds, notification routing, workflow mappings) live — project config or DB?" → read architecture.md (Settings belong in project config)
  • "Should thresholds be DB-only to avoid cross-environment churn?" → read architecture.md (Settings belong in project config) — this is a misconception to correct
  • "Upgrade a plugin from Craft 4 to 5" → read quality.md (Rector section)
  • "Set up CI for a Craft plugin" → read quality.md (CI/CD Integration section)
  • "Create sections or fields in a migration" → read migrations.md (Content Migrations section)
  • "Install fails with 'Too many keys specified; max 64 keys allowed' / duplicate indexes piling up" → read migrations.md (Re-runnable index creation and MySQL's 64-key ceiling)
  • "Run a module's migrations / --track=module: rejected / library-shipped module schema never applies" → read migrations.md (Modules and library-shipped modules have no CLI track)
  • "Fix a bad stored project-config value from a plugin upgrade" → read migrations.md (Never call ProjectConfig::flush() inside a migration)
  • "Unsuspend/restore a user programmatically / user still locked out after unsuspend" →

This is the start of the file. Read the full SKILL.md on GitHub.

Security

  • Security scanPassed, 100 of 100Pattern checks on the skill's files.
  • Model reviewPassed

Quality evaluation

  • Task evaluationNot runNo evaluation yet. Run one in Skill Studio.

Source

Popularity

Repository stars
71
Repository forks
6
7-day trend score
21

Stars and forks are for the whole GitHub repository, not this skill alone. Not refreshed yet.

Extension points

  • hookPre/post lifecycle hooks

Works with

Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.

  • Claude Code
  • Cursor
  • GitHub Copilot
  • Windsurf
  • Codex
  • Gemini CLI
  • Cline
  • Amp
  • Roo Code
  • Goose
  • Continue
  • Kilo Code
  • Junie
  • Trae
  • OpenHands
  • Qwen Code
  • AI Agent
  • Tabnine Chat
  • Sourcegraph Cody
  • Amazon Q
  • Firebase Genkit
  • Sweep
  • Devin
  • Phind
  • Mentat
  • Claude Desktop
  • Aider
  • GPT Pilot
  • Plandex
  • bolt.new
  • v0
  • Replit Agent
  • Anse
  • TypingMind
  • BetterChatGPT
  • LibreChat
  • Warp AI
  • Pieces
  • Fig AI
Embed badge
vskill VERIFIED badge
[![vskill VERIFIED](https://verified-skill.com/api/v1/skills/michtio/craftcms-claude-skills/craftcms/badge)](https://verified-skill.com/skills/michtio/craftcms-claude-skills/craftcms)

Something wrong with this skill or its page? Tell us.