All skills
michtio avatar

/craft-php-guidelines

@614a89e

Craft CMS 5 PHP coding standards and conventions. ALWAYS load when writing, editing, reviewing, or discussing any PHP in a Craft plugin or module — even small edits. Also when running ECS, PHPStan, or scaffolding with ddev craft make. Covers: PHPDoc blocks (@author, @since, @throws chains), section headers (=========), class organization, naming conventions (services, queue jobs, records, events, enums), defineRules() and validation, beforePrepare() and addSelect(), MemoizableArray, DateTimeHelper vs Carbon, strict_types/declare(strict_types=1), short nullable notation (?string), typed properties, void returns, control flow (early returns, match over switch), CP Twig template conventions, form macros, translations (Craft::t), ECS/PHPStan config, scaffolding commands, and the verification checklist. Triggers on: writing service classes, models, controllers, elements, element queries, records, queue jobs, migrations, or any PHP class in a Craft context; PHP code review, refactoring, or style questions; requireAdmin vs requirePermission, manage-settings, settings permission, kebab-case permission handles never camelCase, allowAdminChanges, read-only settings, getCpNavItem dead nav item, permission handle constant on owning controller, App::env() never getenv(), App::parseEnv() for $VAR settings, no-em-dash user-facing copy. NOT for front-end Twig (craft-twig-guidelines), template architecture (craft-site), or CP JavaScript/Garnish (craft-garnish). If you are touching PHP in a Craft context, you need this skill.

Use this Skill: https://skilld.dev/gh/michtio/craftcms-claude-skills/craft-php-guidelines

This session only. Nothing lands on disk.

referencestemplates-and-patterns.md

≈1.2k tokens on demand. Your agent reads this file only when SKILL.md points to it.

CP Templates, Validation, Translations, and File Headers

CP Twig Templates

Indent with 4 spaces. Spaces inside delimiters: {{ value }}, {% tag %}, {# comment #}.

File Naming

  • _underscore prefix = partial (not directly accessible via URL). Use for includes, layouts, and components.
  • No underscore = route entry point or publicly accessible template.
  • Lowercase with hyphens for directories: plugin-store/, entry-types/.
  • camelCase for form macro names: textField, lightswitchField.

Template Directory Structure

templates/
├── _components/         # Reusable UI components (sidebars, cards)
├── _includes/           # Shared partials (forms, pagination)
├── _layouts/            # Base layouts for CP pages
├── settings/            # Settings pages (route-accessible)
│   ├── _edit.twig       # Edit partial
│   └── index.twig       # Index entry point

Control Structures

{% if condition %}
    Something
{% endif %}

{% for item in items %}
    {{ item.title }}
{% endfor %}

Form Macros

Import Craft's form helpers and use the object syntax:

{% import '_includes/forms.twig' as forms %}

{{ forms.textField({
    label: "Name"|t('app'),
    id: 'name',
    name: 'name',
    value: entity.name,
    errors: entity.getErrors('name'),
    required: true,
}) }}

Defaults and Null Coalescing

Use ?? for safe defaults in templates:

{% set readOnly = readOnly ?? false %}
{% set title = title ?? 'Untitled'|t('app') %}

Output Safety

Never use |raw on user-provided or admin-provided content rendered inside <style> or <script> tags — even admin-entered values are XSS vectors if an admin account is compromised. For CSS values, sanitize or whitelist. For HTML content, use |purify (Craft's HTML Purifier filter). Reserve |raw for trusted, hardcoded content or content that has already been sanitized.

Whitespace Control

Use {%- and -%} to trim surrounding whitespace in low-level components:

{%- set class = class ?? 'default' -%}

Validation

Use defineRules() with array notation:

protected function defineRules(): array
{
    $rules = parent::defineRules();
    $rules[] = [['name', 'handle'], 'required'];
    $rules[] = [['handle'], UniqueValidator::class, 'targetClass' => MyEntityRecord::class];
    $rules[] = [['batchSize'], 'integer', 'min' => 1, 'max' => 500];
    $rules[] = [['apiUrl'], 'url'];
    return $rules;
}

Always call parent::defineRules() first to inherit base validation. Use Craft's built-in validators (HandleValidator, UniqueValidator, DateTimeValidator) before writing custom ones.

Inline Validators

Use the string method name form for inline validators. Do not use [$this, 'method'] callable arrays or inline closures:

// Correct — matches craft\models\Section, craft\models\EntryType
$rules[] = [['siteSettings'], 'validateSiteSettings'];
$rules[] = [['previewTargets'], 'validatePreviewTargets'];

// Wrong — Craft core does not use this form
$rules[] = [['siteSettings'], [$this, '_validateSiteSettings']];

// Wrong — inline closures make rules unreadable, prevent reuse
$rules[] = [['siteSettings'], function ($attribute) { ... }];

The validator method is public, no underscore prefix. Yii's validator dispatcher invokes it by name on the model instance, making it part of the public API surface:

public function validateSiteSettings(): void
{
    if (empty($this->siteSettings)) {
        $this->addError('siteSettings', Craft::t('my-plugin', 'At least one site is required.'));
    }
}

The when callable in validator rules follows the same pattern — public method, no underscore:

$rules[] = [['maxRows'], 'integer', 'min' => 1, 'when' => [$this, 'hasMaxRows']];

public function hasMaxRows(): bool
{
    return $this->maxRows !== null;
}

Translations

Always use the plugin handle as the translation category:

Craft::t('pluginhandle', 'Some translatable text')
{{ 'Some translatable text'|t('pluginhandle') }}

In CP templates, translate labels inline with the |t() filter:

{{ forms.textField({
    label: "Field Label"|t('pluginhandle'),
    instructions: "Help text for this field."|t('pluginhandle'),
}) }}

Never hardcode user-facing strings. All CP labels, messages, and descriptions must go through Craft::t() or the |t() Twig filter.

File Header

Every PHP file starts with:

<?php
/**
 * <Plugin Name> plugin for Craft CMS
 *
 * <Plugin description>.
 *
 * @link      <Author URL>
 * @copyright Copyright (c) <Year> <Author Name>
 */

namespace vendor\pluginhandle\path\to;

Source: SKILL.md on GitHub

No alerts16d3 checks · Risk SAFE
  • Gen Agent Trust Hub16d

    This skill provides comprehensive PHP coding standards, project organization rules, and development guidelines for Craft CMS 5. It emphasizes secure coding practices, such as implementing authorization parity across multiple application surfaces and using framework-specific helpers for environment access and date handling. No malicious patterns or security risks were detected.

  • Socket16d

    No alerts

  • Snyk16d

    Risk: LOW · No issues

Signed by skilld at 614a89e. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 2 weeks ago.

Activeupdated 2 weeks ago

README badge

README badge for michtio/craftcms-claude-skills/craft-php-guidelines