Craftcms-claude-skills craftcms
Craft CMS 5 plugin and module development — extending Craft with PHP. Covers the full extend surface: elements, element queries, services, models, records, project config, controllers, CP templates, migrations, queue jobs, console commands, field types, native fields, events, behaviors, Twig extensions, utilities, widgets, filesystems, permissions, debugging, testing, GraphQL, and Craft configuration (config/app.php, config/general.php, Redis, SMTP, database replicas). Triggers on: beforePrepare(), afterSave(), defineSources(), defineTableAttributes(), attributeHtml(), MemoizableArray, getConfig(), handleChanged, $allowAnonymous, $enableCsrfValidation, BaseNativeField, EVENT_DEFINE_NATIVE_FIELDS, FieldLayoutBehavior, EVENT_REGISTER, EVENT_DEFINE, EVENT_BEFORE, EVENT_AFTER, CraftVariable, registerTwigExtension, DefineConsoleActionsEvent, PHPStan, Pest, plugin development, module development, custom element type, custom field type, webhook, API endpoint, queue job, batch processing, data sync, migration, CP section, control panel, Craft plugin, Craft module, extending Craft, element action, element exporter, element condition, dashboard widget, utility page, permissions, registerUserPermissions, requirePermission, GraphQL custom types, GraphQL custom mutations, GraphQL schema building, Rector, Craft 4 to 5, upgrade plugin, CI/CD, GitHub Actions, GitLab CI, custom validator, defineRules, EVENT_AUTHORIZE_VIEW, EVENT_AUTHORIZE_SAVE, canView, canSave, canDelete, element authorization, defense-in-depth, query scoping, EVENT_BEFORE_PREPARE, session invalidation, passwordResetRequired, elevated session, Table::SESSIONS, custom field type build, field type development, normalizeValue, serializeValue, inputHtml, BaseCondition, ElementCondition, condition rule, condition builder, system messages, composeFromKey, email sending, Mailer, deployment, zero-downtime deploy, atomic deploy, craft up, project-config/apply, allowAdminChanges, drafts, revisions, provisional draft, canCreateDrafts, applyDraft, getIsDraft, getIsRevision. Always use when writing, editing, or reviewing any Craft CMS plugin or module PHP code — even when the user asks about plugin architecture, Craft internals, or extending Craft without naming specific APIs. Do NOT trigger for front-end Twig templates, content modeling decisions, site-building without PHP, or consuming GraphQL/headless APIs from front-end frameworks (Next.js, Nuxt, Astro) — those belong in craft-site.
git clone https://github.com/michtio/craftcms-claude-skills
T=$(mktemp -d) && git clone --depth=1 https://github.com/michtio/craftcms-claude-skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/craftcms" ~/.claude/skills/michtio-craftcms-claude-skills-craftcms && rm -rf "$T"
skills/craftcms/SKILL.mdCraft 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:
— PHPDoc standards, section headers, naming conventions, class organization, ECS/PHPStan, verification checklist. Required for any PHP code.craft-php-guidelines
— All commands run through DDEV. Required for running ECS, PHPStan, scaffolding, and tests.ddev
— 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-garnish
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
inaddSelect()
— it's the Craft convention and safely additive when multiple extensions contribute columns.beforePrepare() - Queue workers run in primary site context — use
for cross-site queries.->site('*') - Including
inid
— project config uses UIDs, never database IDs.getConfig() - Business logic in models or controllers — services are where logic belongs.
- Modules need manual template root, translation, and controllerNamespace registration — nothing is automatic.
in elements/queries,DateTimeHelper
in services — never mix in the same class.Carbon
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
+element-index.md
+fields.md
+migrations.mdcp.md - "Add a webhook endpoint" → read
+controllers.mdevents.md - "Create a queue job that syncs elements" → read
+queue-jobs.md
+elements.mddebugging.md - "Add a settings page with form fields" → read
+controllers.md
+cp.mdarchitecture.md - "Register a custom field type" → read
+fields.mdevents.md - "Fix PHPStan errors" → read
quality.md - "Add a dashboard widget" → read
(Dashboard Widgets) +cp.md
(Widget Types section)events.md - "Expose template variables for plugin users" → read
(Twig Extensions section)events.md - "Attach custom methods to entries" → read
(Behaviors section)events.md - "Build a CP utility page" → read
(Utilities section) +events.mdcp.md - "Set up Vite for a plugin's CP assets" → read
+ loadplugin-vite.md
skillcraft-garnish - "Add drag-to-reorder or interactive JS to a CP page" → load
skillcraft-garnish - "Write CP JavaScript for a custom field type" → read
+ loadfields.md
skillcraft-garnish - "Build a headless Craft API" → read
+ loadgraphql.md
skill forcraft-siteheadless.md - "Configure preview for a Next.js front-end" → load
skill forcraft-siteheadless.md - "Set up Pest tests for a plugin" → read
testing.md - "Write a test for a controller action" → read
testing.md - "Configure Redis for caching and sessions" → read
config-app.md - "Set up environment variables for production" → read
config-bootstrap.md - "Find a GeneralConfig setting" → read
config-general.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.mdconfig-general.md - "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
(Plugin Editions section)architecture.md - "Upgrade a plugin from Craft 4 to 5" → read
(Rector section)quality.md - "Set up CI for a Craft plugin" → read
(CI/CD Integration section)quality.md - "Create sections or fields in a migration" → read
(Content Migrations section)migrations.md - "Set up database read replicas" → read
(Database Replicas section)config-app.md - "Register a module in app.php" → read
(Module Registration section)config-app.md - "Create a custom validator" → read
(Custom Validators section)architecture.md - "Create a custom filesystem type" → read
(Filesystem Types section)events.md - "Build a custom condition rule for an element index" → read
(Condition Builders section)cp.md - "Set up pre-commit hooks for code quality" → read
(Pre-Commit Hooks section)quality.md - "Restrict element access by user group" → read
+element-authorization.mdpermissions.md - "Scope CP element index by permission" → read
(Layer 3: Query Scoping)element-authorization.md - "Add authorization events to a custom element" → read
+element-authorization.mdelements.md - "Build defense-in-depth for a security plugin" → read
(Defense Patterns)element-authorization.md - "Force-logout a user from all devices" → read
(Plugin Patterns)sessions-and-auth.md - "Understand how Craft sessions work" → read
sessions-and-auth.md - "Implement password reset required" → read
(passwordResetRequired Gap)sessions-and-auth.md - "Add a column to the Users element index" → read
(Extending Element Indexes via Events)element-index.md - "Add a bulk action to an element index" → read
(Adding a custom bulk action)element-index.md - "Add a custom sidebar source to the element index" → read
(Adding a sidebar source)element-index.md - "Build a custom field type" → read
+field-types-custom.mdfields.md - "Build a relation field type" → read
(Relation Fields)field-types-custom.md - "Add a condition rule to the entry index" → read
+conditions.mdelement-index.md - "Build a custom condition rule" → read
conditions.md - "Send email from a plugin" → read
email.md - "Register a custom system message" → read
(Registering Custom System Messages)email.md - "Configure SMTP transport" → read
+config-app.mdemail.md - "Deploy Craft CMS to production" → read
deployment.md - "Set up CI/CD for a Craft project" → read
(CI/CD Patterns)deployment.md - "Zero-downtime deploy" → read
(Zero-Downtime)deployment.md - "Roll back a failed deploy" → read
(Rollback Strategies)deployment.md - "Work with drafts and revisions" → read
drafts-revisions.md - "Create a draft programmatically" → read
(Creating Drafts)drafts-revisions.md - "Skip side effects for drafts in afterSave" → read
(Plugin Considerations)drafts-revisions.md
| Task | Read |
|---|---|
| Element core: lifecycle, queries, status, authorization, drafts, revisions, propagation, field layouts, events | |
| Element index: sources, table/card attributes, sort, conditions, actions, exporters, sidebar, metadata, extending via events (columns, sources, bulk actions, condition rules, sort) | |
| Services, models, records, project config, MemoizableArray, events, API clients, custom validators | |
| Controllers: CP CRUD, webhooks, API endpoints, action routing, authorization | |
CP templates, form macros, admin changes, VueAdminTable, asset bundles, CP layout, permissions. For CP JavaScript interactions, also load skill. | |
| Database migrations, Install.php, foreign keys, indexes, idempotency, deployment | |
| Queue jobs, BaseJob, TTR, retry, progress, batch jobs, site context | |
| Console commands, arguments, options, progress bars, output helpers, resave actions | |
| Debugging, performance, query strategy, profiling, Xdebug, caching, logging | |
| PHPStan, ECS, code review checklist | |
| Testing: Pest setup, element factories, HTTP/queue/DB assertions, mocking, multi-site, console, events | |
| Field types, native fields, BaseNativeField, field layout elements, FieldLayoutBehavior | |
| Events: registration, lifecycle, naming conventions, custom events, behaviors, Twig extensions, utilities, widgets, filesystems, discovering events | |
| GraphQL types, queries, mutations, directives, schema components, resolvers | |
| Plugin Vite: VitePluginService, CP asset bundles, HMR, TypeScript, Vue in CP | |
| Headless & hybrid: headlessMode, GraphQL API, CORS, preview tokens, front-end frameworks | craft-site skill |
| GeneralConfig (system, routing, security, users, sessions, search, assets, images) | |
| GeneralConfig (content, templates, performance, GC, localization, headless, GraphQL, accessibility, preview, dev, dangerous interactions) | |
| App config: cache, session, queue, mutex, mailer/SMTP, search, logging, CORS, DB replicas, web/console split | |
| Config bootstrap: env vars, aliases, priority order, fluent API, custom.php, db.php, routes.php, htmlpurifier | |
| Caching: template cache tag, data cache, static caching (Blitz), CDN, layered strategy, invalidation | |
| Permissions: built-in handles, user groups, custom registration, Twig/PHP checking, authorization events, strategies | |
| Element authorization: four-layer defense model, authorization events, can*() methods, EVENT_BEFORE_PREPARE query scoping, controller enforcement | |
| Sessions & auth internals: dual-layer session model, auth tokens, session invalidation, passwordResetRequired, elevated sessions, plugin patterns | |
| Custom field types: build pattern, value lifecycle, settings, input HTML, validation, search, GraphQL, relation fields | |
| Conditions framework: BaseCondition, ElementCondition, custom condition rules, registering rules, condition builder UI | |
| Email system: system messages, custom messages, programmatic sending, templates, events, testing | |
| Deployment: standard pipeline, project config deploy, zero-downtime, CI/CD, rollback, environment management | |
| Drafts & revisions: draft types, provisional drafts, autosave, applying, merge, revisions, plugin considerations | |
Plugin vs Module Differences
Plugins and modules share the same architecture patterns. The differences are in bootstrapping and registration:
| Feature | Plugin | Module |
|---|---|---|
| CP template root | Automatic (by handle) | Manual via |
| Site template root | Manual via event | Same — manual for both |
| Translation category | Automatic (by handle) | Manual in |
| Settings model | Built-in | Env vars, config files, or private plugin ( prefix) |
| Install migration | | Content migrations only |
| Console commands | Automatic | Must set before , must be bootstrapped |
| CP nav section | | |
| Project config | Settings auto-tracked | Manual only |
| Namespace alias | Automatic via Composer | Must call |
Module Template Root Registration
use craft\events\RegisterTemplateRootsEvent; use craft\web\View; Event::on(View::class, View::EVENT_REGISTER_CP_TEMPLATE_ROOTS, function(RegisterTemplateRootsEvent $event) { $event->roots['my-module'] = __DIR__ . '/templates'; } );
Module Translation Registration
Craft::$app->i18n->translations['my-module'] = [ 'class' => \craft\i18n\PhpMessageSource::class, 'sourceLanguage' => 'en', 'basePath' => __DIR__ . '/translations', 'allowOverrides' => true, ];
Module Console Command Registration
public function init() { Craft::setAlias('@mymodule', __DIR__); if (Craft::$app->getRequest()->getIsConsoleRequest()) { $this->controllerNamespace = 'modules\\mymodule\\console\\controllers'; } else { $this->controllerNamespace = 'modules\\mymodule\\controllers'; } parent::init(); // MUST come after setting controllerNamespace }
The module must be bootstrapped in
config/app.php for console commands to be discoverable.