Getting it into your agent
One page per mod, every tool's command on it. A separate URL per tool would split the same page into five that compete with each other.
npx skills add michtio/craftcms-claude-skills --skill craft-php-guidelinesgit clone --depth 1 https://github.com/michtio/craftcms-claude-skillsWrote this? Show the measurements
A badge with what this costs and how it scanned, read live from this page, so it follows the numbers instead of freezing them. Markdown for a README, HTML for a documentation site or a project page.
[](https://agentmods.dev/skills/michtio/craftcms-claude-skills/craft-php-guidelines)<a href="https://agentmods.dev/skills/michtio/craftcms-claude-skills/craft-php-guidelines"><img src="https://agentmods.dev/badge/skills/michtio/craftcms-claude-skills/craft-php-guidelines/github.svg" alt="Measured on agentmods" height="20"></a>Or the 80×15 button, for a site that already has a row of RSS and ATOM ones. Only the verdict fits; the numbers stay here.
<a href="https://agentmods.dev/skills/michtio/craftcms-claude-skills/craft-php-guidelines"><img src="https://agentmods.dev/badge/skills/michtio/craftcms-claude-skills/craft-php-guidelines.svg" alt="Reviewed on agentmods" width="80" height="20"></a>- NVIDIA SkillSpector pass
What it costs to keep this loaded
Counted locally with the o200k_base tokenizer, which is exact for GPT models; Claude uses its own tokenizer and its counts differ. Treat this as one consistent yardstick across the catalogue rather than a bill. Prices are per million input tokens.
| Model | Per session | Once invoked |
|---|---|---|
| Fable 5.1 | $0.00336 | $0.04132 |
| Opus 5 | $0.00168 | $0.02066 |
| Sonnet 5 | $0.00067 | $0.00826 |
| Haiku 4.5 | $0.00034 | $0.00413 |
Grade A, and why
craft-php-guidelines scanned grade A with 0 findings against 26 rules in 11 categories — prompt injection, anti-refusal, data exfiltration, privilege escalation, supply chain, agent snooping, system-prompt leakage, SSRF and excessive agency — measured 10d ago.
A static scan of the body, not an audit. Every finding is printed with the line that produced it so you can judge whether it matters here. A mod is markdown that instructs an agent; that is exactly why what it instructs is worth reading.
Nothing flagged
None of the 26 patterns this scan looks for appear in this file: no shell pipes, no recursive deletes, no credential paths, no hidden text, no instruction-override or anti-refusal phrasing, no agent-config snooping. That is not a guarantee, it is the absence of the things that are checkable.
How it starts
The opening of the file, as written. The whole thing — 180 lines — stays where its author put it; the contents beside it link to each section on GitHub.
Craft CMS 5 PHP Guidelines
Complete PHP coding standards and conventions for Craft CMS 5 plugin and module development. These extend Craft's official coding guidelines with project-specific conventions.
Core principles: PHPDocs on everything — classes, methods, and properties — regardless of type hints. No declare(strict_types=1) in plugin source files (matching Craft core convention).
Companion Skills — Always Load Together
craftcms— Architecture patterns, element lifecycle, controllers, events, migrations. Required for any Craft plugin or module development.ddev— All commands run through DDEV. Required for running ECS, PHPStan, scaffolding, and tests.
Documentation
- Official coding guidelines: https://craftcms.com/docs/5.x/extend/coding-guidelines.html
- Class reference: https://docs.craftcms.com/api/v5/
- Generator reference: https://craftcms.com/docs/5.x/extend/generator.html
When unsure about a convention, WebFetch the coding guidelines page for the authoritative answer.
Common Pitfalls
addSelect()is the convention inbeforePrepare()— safely additive when multiple extensions contribute columns.$_instancesis not a Craft convention — private properties use underscore prefix but meaningful names like$_items,$_sections.- Records use the same class name as models (namespace distinguishes). Alias when importing both:
use ...\records\MyEntity as MyEntityRecord;. - Queue jobs have no "Job" suffix —
ResaveElements, notResaveElementsJob. declare(strict_types=1)is NOT used in plugin source files. Only in standalone config files likeecs.php.@authorgoes on classes and methods only — never on properties. (Craft core puts@authorat the class level only; placing it on methods too is this project's house convention, not core style.)- Don't use
string|null— use?string(short nullable notation). - Forget
parent::defineRules()and you lose all inherited validation. - Using
[$this, '_validateFoo']callable arrays or inline closures indefineRules()— Craft core uses string method names:[['attr'], 'validateAttr']. The validator method is public, no underscore — Yii invokes it by name. DateTimeHelperin elements/queries,Carbonin services — never mix in the same class.- Parsing a raw DB datetime with
strtotime()ornew DateTime()— those columns are naive UTC strings and the process timezone issystem.timeZone, so the result is off by the full offset on any non-UTC install. Parse with an explicit UTC zone. See Date Handling below. - Missing
@throwschains — document exceptions from called methods too, not just your own throws. - Using magic property access (
$plugin->settings,$app->view) instead of explicit getters ($plugin->getSettings(),$app->getView()) — PHPStan can't resolve__get()calls, so magic access passes at runtime but fails static analysis. Always use explicit getters for Yii2 components and Craft plugin properties. - Calling Craft-specific methods directly on
Craft::$app(Craft::$app->getConfig()) — PHPStan can't resolve them because the static type is Yii's base union. Narrow with a typed local:/** @var \craft\web\Application $app */ $app = Craft::$app;. Don't use@phpstan-ignore-line. - Duplicating contract constants as
private constacross multiple classes with "keep in lockstep" comments — PHPStan can't detect drift. Declarepublic conston the owning service, reference asOwnerService::CONSTANT_NAMEeverywhere else. This applies specifically to permission handles: a handle like'my-plugin:manage-settings'is a contract string referenced from registration (EVENT_REGISTER_PERMISSIONS), the controller gate (requirePermission()), and the nav check (->can()); a bare literal drifts silently and a typo passes for admins (who hold every permission) while denying everyone else. Declare it as apublic conston the controller that enforces it —SettingsController::PERMISSION_MANAGE_SETTINGS— and reference the const everywhere. (Craft core uses bare literals here; the const is a deliberately stricter house rule. See thecraftcmsskill'spermissions.md.) - Writing the same authorization check separately in a CP controller, a console command, and a GraphQL resolver — they drift, and the surface that drifts is the one nobody tests. One shared gate method called by every surface, with a test per surface. Console is not exempt (a documented cron path with no permission check is an unauthenticated capability), and GraphQL schema scope is not the plugin's permission matrix. See
references/authorization-parity.md. - Assuming Craft prevents self-approval / self-review — it has no such concept, and peer permissions are the opposite axis. Write the guard into the shared gate, orthogonal to role checks, with an explicit bypass permission. See
references/authorization-parity.md. - Shipping
../*path repositories in a plugin'scomposer.json— resolution works only on the author's disk. Unpublished sibling deps get avcsentry; Packagist deps need nothing;composer.lockstays gitignored for plugins. Prove it withcomposer config --global repositories(expect empty) then a no-lockcomposer update --dry-run. Seereferences/tooling.md(Composer Hygiene). - Using
Db::parseParam()for a literal comparison — a leading or trailing*becomes a SQLLIKEwildcard, so a uniqueness check on a stored pattern silently becomes a prefix match. Use a rawandWhere(['col' => $value]). See thecraftcmsskill'sarchitecture.md. - Registering
EVENT_REGISTER_ELEMENT_TYPES/EVENT_REGISTER_FIELD_TYPESinside agetIsCpRequest()(or other request-context) branch ininit()— component-type registration must run in every context (CP, console, site) or the type disappears fromgetAllElementTypes()in console/queue requests, andGc::hardDeleteElements()silently stops purging its trashed rows. Register unconditionally; only CP-rendering/routing (URL rules, asset bundles, nav) may be gated. See thecraftcmsskill'sevents.md→ "Registration scope".
What ships with it
6 files beside SKILL.md in the same directory: the scripts, references and assets a skill reads on demand. Not counted in the per-session cost; read them before you install if any of them is executable.
What this file has done since we first saw it
Hashed on every crawl. A supply-chain change to an agent config is a question of when, not whether, so the history is kept rather than the latest state alone.
- 10d ago First seen · 180 lines · 336 tokens per session scan A b24a6f20f81a
craft-php-guidelines is a skill published in the GitHub repository michtio/craftcms-claude-skills (78 stars, last pushed 7d ago), licensed MIT. It adds 336 tokens to every session and 4,132 once invoked, about $0.0017 per session on Opus 5. A static security scan graded it A with 0 findings. No closer match exists in the catalogue, so it is treated as the original; first seen 2026-08-30.
Other skills, from other repositories
phx-deps-audit
Audit Hex deps for supply-chain security risk — bidi chars, compile-time exec, maintainer changes, typosquats, CVEs. Use after mix deps.update, when checking if a package upgrade is safe, or reviewing mix.lock PR diffs.
deps-update
Bump outdated Hex deps — inventory, snapshot changelogs, update, fix breaks, split reviewable PRs (patches bundled, majors solo). Use to upgrade/bump Elixir dependencies or when versions fall behind. NOT for deps.get failures (/phx:investigate).
deps-vet
Record a vetted Hex package version in hexvet.exs after a security review — manages the audit ledger, not the scanner. Use to approve a dep after /phx:deps-audit findings or to initialize hexvet.exs.
phx-deps-update
Bump outdated Hex deps — inventory, snapshot changelogs, update, fix breaks, split reviewable PRs (patches bundled, majors solo). Use to upgrade/bump Elixir dependencies or when versions fall behind. NOT for deps.get failures (phx-investigate).
phx-plan
Plan features spanning multiple domains: billing (Stripe), auth (RBAC), real-time (Presence), webhooks, jobs (Oban). Use when designing interconnected systems or converting review findings into tasks.
phx-full
Run a portable sequential plan-work-verify-review-compound lifecycle. Use optional generic workers only when Amp makes them available.