craftcms — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited craftcms (Agent Skill) and scored it 92/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 2 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 2 flagged
The text {match} tells the agent to skip the normal "ask the user first" gate. Used adversarially it removes the human-in-the-loop check before destructive or sensitive actions, turning a normally-gated agent into a fire-and-forget executor.
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
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.
When this skill triggers, also load:
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.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.Use WebFetch on specific doc pages when a reference file doesn't cover enough detail.
addSelect() in beforePrepare() — it's the Craft convention and safely additive when multiple extensions contribute columns.->site('*') for cross-site queries.id in getConfig() — project config uses UIDs, never database IDs.DateTimeHelper in elements/queries, Carbon in services — never mix in the same class./admin in CP URLs — cpTrigger is configurable. Use UrlHelper::cpUrl() in PHP, cpUrl() in Twig.$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.tokenParam and the request is rejected with a 400 before your controller runs. See controllers.md (Reserved request params).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).Read the relevant reference file(s) for your task. Multiple files often apply together.
Task examples:
elements.md (Architecture section first) + element-index.md + fields.md + migrations.md + cp.mdelements.md (Architecture: One Element Class with Native Structure)controllers.md + events.mdqueue-jobs.md + elements.md + debugging.mdcontrollers.md + cp.md + architecture.mdfields.md + events.mdquality.mdcp-components.md (Dashboard Widgets) + events.md (Widget Types section)events.md (Twig Extensions section)events.md (Behaviors section)events.md (Registration Pattern → Registration scope — never gate by request context)events.md (Registration Pattern → Registration scope)cp-components.md (Utility Pages) + events.md (Utilities section)plugin-vite.md + load craft-garnish skillcraft-garnish skillfields.md + load craft-garnish skillgraphql.md + load craft-site skill for headless.mdcraft-site skill for headless.mdcraft-pest skill → isolation.mdcraft-pest skill → patterns.mdcraft-pest skill → isolation.mdarchitecture.md (Creating or deleting sites at runtime)architecture.md (Creating or deleting sites at runtime — refreshSites() doesn't invalidate the variant queries read)architecture.md (Creating or deleting sites at runtime — deleteSite()'s prune vs memoized service caches)architecture.md (Those strings are naive UTC)architecture.md (Record-to-Model Hydration Boundary)architecture.md (Element-query param setters don't take Yii operator tuples)architecture.md (Element-query param setters don't take Yii operator tuples)config-app.mdconfig-app.md (Session Component — craft\web\DbSession, setup/php-session-table)config-bootstrap.mdconfig-general.mdconfig-bootstrap.md + config-general.mdconfig-general.md + cp.md (Read-Only Mode)config-bootstrap.md (App::parseEnv)config-bootstrap.mdconfig-app.mdconfig-bootstrap.mdconfig-app.mdgraphql.md + config-general.mdauthor/uploader fields disappeared after upgrading to Craft 5.11" → read graphql.md (Craft 5.11 Additions — "Query for users" scope)caching.mdpermissions.mdpermissions.mdarchitecture.md (Plugin Editions section)architecture.md (Render builders: attribute merging, option allowlists)architecture.md (Plugin-registered CSS loads after the site's stylesheet)architecture.md (The JS-to-markup contract is public API)quality.md (Generated Artifacts Must Not Read Runtime Registries)craft-plugin-release skillarchitecture.md (Site deletion is a soft delete)caching.md (Data Caching → boolean sentinel idiom)architecture.md (Settings belong in project config)architecture.md (Settings belong in project config) — this is a misconception to correctquality.md (Rector section)quality.md (CI/CD Integration section)migrations.md (Content Migrations section)migrations.md (Re-runnable index creation and MySQL's 64-key ceiling)migrations.md (Modules and library-shipped modules have no CLI track)migrations.md (Never call ProjectConfig::flush() inside a migration)elements.md (getStatus() hides co-existing states)field-types-custom.md (What settingsAttributes() actually includes)config-app.md (Database Replicas section)config-app.md (Module Registration section)architecture.md (Custom Validators section)events.md (Filesystem Types section)cp-ui-patterns.md (Condition Builders)cp-ui-patterns.md (Tri-State Inheritance Controls)cp.md (Tabbed Settings Pages)cp.md (Settings Pages → Config-file overrides: warn on the field, don't disable it)cp.md (Settings Pages → Config-file overrides)cp.md (Tabs switch panes — they never navigate)cp-ui-patterns.md (Index-screen column scheme) + cp-components.md (VueAdminTable)cp-components.md (VueAdminTable → sole pane content)cp-components.md (Utility Pages)fields.md (Suppressing the condition builders)cp-ui-patterns.md (Field Warning Parameter)cp-ui-patterns.md (Craft CSS Custom Properties — color/status/spacing/radius/control/focus/font tokens)cp.md (Form Macros Reference → Lightswitch vs checkbox)cp.md (Form Macros Reference → copytextField)cp.md (Form Macros Reference → Button modifier classes)cp.md (Form Macros Reference → Inner sidebar navigation)cp-components.md (Editable Table Field)cp-components.md (Modals) + load craft-garnish skill for behaviorelement-index.md (Relation Field Display/Props)fields.md (Field-Layout UI Elements Reference)cp-ui-patterns.md (Element Edit Screen) + elements.md (EVENT_DEFINE_SIDEBAR_HTML)cp-ui-patterns.md (Element Edit Screen → toolbar split button) + elements.md (EVENT_DEFINE_ADDITIONAL_BUTTONS)cp-ui-patterns.md (Element Edit Screen → .meta vs .meta read-only)cp-ui-patterns.md (Element Edit Screen → metaFieldsHtml override)quality.md (Pre-Commit Hooks section)element-authorization.md + permissions.mdelement-authorization.md (Layer 3: Query Scoping)element-authorization.md + elements.mdelement-authorization.md (Defense Patterns)sessions-and-auth.md (Plugin Patterns)sessions-and-auth.mdsessions-and-auth.md (passwordResetRequired Gap)element-index.md (Extending Element Indexes via Events)element-index.md (Adding a custom bulk action)element-index.md (Per-Element Edit-Screen Action Menu)element-index.md (Status Pills in Table Attributes)element-index.md (Adding a sidebar source)field-types-custom.md + fields.mdfield-types-custom.md (Relation Fields)conditions.md + element-index.mdconditions.mdemail.mdemail.md (Registering Custom System Messages)config-app.md + email.mddeployment.mddeployment.md (CI/CD Patterns)deployment.md (Zero-Downtime)deployment.md (Rollback Strategies)drafts-revisions.mddrafts-revisions.md (Creating Drafts)drafts-revisions.md (Plugin Considerations)elements.md (Generated Fields)element-index.md (Element Display Modes)elements.md (Extending User Edit Screens)cp.md (Read-Only Mode)cp.md (Settings Pages → With tabs or custom actions). settingsHtml() is single-pane only — tabs require a custom controller and a template extending _layouts/cp directly.cp.md (Settings Pages → Keep settings inside the plugin's own CP section)craft-cloud skill → plugin-development.md (ephemeral filesystem, asset-bundle constraints, queue cap, CSRF function, cookie-free design)craft-cloud skill → config-file.md + deploy-pipeline.md + extension.mdcraft-cloud skill → migration.mdcraft-cloud skill → plugin-development.md (Ephemeral filesystem) + extension.md (App::isEphemeral)identity-protocols.md + sessions-and-auth.mdidentity-protocols.mdLoad only the reference files your task needs — each file costs input tokens on every turn.
| Task | Read | ~Tokens |
|---|---|---|
| Element core: lifecycle, queries, status, authorization, drafts, revisions, propagation, field layouts, user edit screens, events | references/elements.md | 8.4K |
| Element index: sources, table/card attributes, status pills, sort, conditions, actions (bulk + per-element action menu), exporters, sidebar, metadata, relation-field display/props (viewMode, previewMode, maxRelations), extending via events | references/element-index.md | 6.1K |
| Services, models, records, project config (incl. runtime site creation/deletion caches), MemoizableArray, events, API clients, custom validators | references/architecture.md | 7.4K |
| Controllers: CP CRUD, webhooks, API endpoints, action routing, authorization | references/controllers.md | 3.9K |
| CP templates, form macros (incl. lightswitch vs checkbox, copytext, money, button classes), settings pages, navigation (incl. inner sidebar nav), permissions, read-only mode | references/cp.md | 7.2K |
| CP components: dashboard widgets, utility pages, slideout editors, ajax, editable tables, spinner, modals (markup + shade), alerts | references/cp-components.md | 1.8K |
| CP UI patterns: tri-state controls, status indicators, semantic CSS tokens (color/status/spacing/radius/control/focus/fonts), condition builders, asset bundles, element edit-screen sidebar panels + toolbar split buttons | references/cp-ui-patterns.md | 3.4K |
| Database migrations, Install.php, foreign keys, indexes, idempotency, deployment | references/migrations.md | 3.9K |
| Queue jobs, BaseJob, TTR, retry, progress, batch jobs, site context | references/queue-jobs.md | 4.2K |
| Console commands, arguments, options, progress bars, output helpers, resave actions | references/console-commands.md | 6.0K |
| Debugging, performance, query strategy, profiling, Xdebug, caching, logging | references/debugging.md | 4.6K |
| PHPStan, ECS, code review checklist | references/quality.md | 3.5K |
Testing: pointer to the craft-pest skill (Pest/craft-pest-core lives there) + Codeception basics | references/testing.md | 0.6K |
| Field types, native fields, BaseNativeField, field layout elements (built-in Tip/Heading/LineBreak/HorizontalRule reference), FieldLayoutBehavior | references/fields.md | 3.6K |
| Events: registration, lifecycle, naming conventions, custom events, behaviors, Twig extensions, utilities, widgets, filesystems | references/events.md | 4.4K |
| GraphQL types, queries, mutations, directives, schema components, resolvers | references/graphql.md | 4.6K |
| Plugin Vite: VitePluginService, CP asset bundles, HMR, TypeScript, Vue in CP | references/plugin-vite.md | 2.7K |
| Headless & hybrid: headlessMode, GraphQL API, CORS, preview tokens, front-end frameworks | craft-site skill references/headless.md | 3.4K |
| GeneralConfig (system, routing, security, users, sessions, search, assets, images) | references/config-general.md | 8.4K |
| GeneralConfig (content, templates, performance, GC, localization, headless, GraphQL, accessibility) | references/config-general-extended.md | 7.2K |
| App config: cache, session, queue, mutex, mailer/SMTP, search, logging, CORS, DB replicas | references/config-app.md | 5.5K |
| Config bootstrap: env vars, aliases, priority order, fluent API, custom.php, db.php, routes.php | references/config-bootstrap.md | 3.6K |
| Caching: template cache tag, data cache, static caching (Blitz), CDN, layered strategy, invalidation | references/caching.md | 5.2K |
| Permissions: built-in handles, user groups, custom registration, Twig/PHP checking, authorization events | references/permissions.md | 4.7K |
| Element authorization: four-layer defense model, authorization events, can*() methods, query scoping | references/element-authorization.md | 4.6K |
| Sessions & auth internals: dual-layer session model, auth tokens, session invalidation, elevated sessions | references/sessions-and-auth.md | 3.0K |
| Identity protocols for SSO/provisioning plugins: back-channel vs front-channel logout, signing-alg allowlists, issuer vs tenancy (hd claim), SCIM active:false + PATCH ordering, vendor-vs-RFC deviations | references/identity-protocols.md | 1.3K |
| Custom field types: build pattern, value lifecycle, settings, input HTML, validation, search, GraphQL | references/field-types-custom.md | 3.5K |
| Conditions framework: BaseCondition, ElementCondition, custom condition rules, registering rules | references/conditions.md | 2.3K |
| Email system: system messages, custom messages, programmatic sending, templates, events, testing | references/email.md | 2.4K |
| Deployment: standard pipeline, project config deploy, zero-downtime, CI/CD, rollback | references/deployment.md | 2.5K |
| Drafts & revisions: draft types, provisional drafts, autosave, applying, merge, revisions | references/drafts-revisions.md | 2.5K |
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 EVENT_REGISTER_CP_TEMPLATE_ROOTS |
| Site template root | Manual via event | Same — manual for both |
| Translation category | Automatic (by handle) | Manual PhpMessageSource in init() |
| Settings model | Built-in createSettingsModel() | Env vars, config files, or private plugin (_ prefix) |
| Install migration | migrations/Install.php | Content migrations only |
| Console commands | Automatic controllerNamespace | Must set before parent::init(), must be bootstrapped |
| CP nav section | $hasCpSection = true | EVENT_REGISTER_CP_NAV_ITEMS |
| Project config | Settings auto-tracked | Manual ProjectConfig::set() only |
| Namespace alias | Automatic via Composer | Must call Craft::setAlias() |
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';
}
);Craft::$app->i18n->translations['my-module'] = [
'class' => \craft\i18n\PhpMessageSource::class,
'sourceLanguage' => 'en',
'basePath' => __DIR__ . '/translations',
'allowOverrides' => true,
];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.
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.