michtio/craftcms-claude-skills/craftcms
Claude Code and agent skill by michtio/craftcms-claude-skills
- community
- ext:[{"type":"hook","description":"Pre/post lifecycle hooks"}]
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/craftcmsSemi-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-coreharness, 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.yamlat the repo root orcraftcms/cloudincomposer.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 thecloud/updeploy 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()inbeforePrepare()— 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
idingetConfig()— 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.
DateTimeHelperin elements/queries,Carbonin services — never mix in the same class.- Hardcoding
/adminin CP URLs —cpTriggeris configurable. UseUrlHelper::cpUrl()in PHP,cpUrl()in Twig. - Passing
$request->getBodyParams()directly tosavePluginSettings()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
tokenParamand the request is rejected with a 400 before your controller runs. Seecontrollers.md(Reserved request params). - `EVENT_DEFINE_RULES` on `Entry::class` runs for every entry, including nested entries the CP builds before
fieldId/ownerIdexist.getType()/typeIdthrowsEither \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 anisset($entry->fieldId) || isset($entry->sectionId)guard, and validate with aValidatorsubclass. Seeevents.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 upfirst, require a cleanproject-config/diff, and checkgit diff -- config/projectafterwards. Schema saves from PHP belong in a migration or console command, never a bare bootstrap script (YAML is only flushed onEVENT_AFTER_REQUEST). Seedeployment.md(project-config/write safely) andmigrations.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'sbeforeAction()gates. Underscore-prefix every template that isn't an intentional direct route. Seecp.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
sectionIdorfieldId+ownerIdmust be set" → readevents.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+ loadcraft-garnishskill - "Add drag-to-reorder or interactive JS to a CP page" → load
craft-garnishskill - "Write CP JavaScript for a custom field type" → read
fields.md+ loadcraft-garnishskill - "Build a headless Craft API" → read
graphql.md+ loadcraft-siteskill forheadless.md - "Configure preview for a Next.js front-end" → load
craft-siteskill forheadless.md - "Set up Pest tests for a plugin" → load
craft-pestskill →isolation.md - "Write a test for a controller action" → load
craft-pestskill →patterns.md - "Tests are writing to my dev database / created thousands of stray elements" → load
craft-pestskill →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/uploaderfields disappeared after upgrading to Craft 5.11" → readgraphql.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-releaseskill - "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
- SKILL.md in the repositoryCheckingskills/craftcms/SKILL.md
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
[](https://verified-skill.com/skills/michtio/craftcms-claude-skills/craftcms)