Home › Drupal › Drupal 11 module development CLAUDE.md
Drupal 11 module development CLAUDE.md
CLAUDE.md for Drupal 11 sites and custom modules: Composer, Drush, services and dependency injection, config management, and PHPUnit.
Claude Code (CLAUDE.md)Drupal
How to use
Save this as CLAUDE.md in your repository root, commit it, and your agent will pick it up automatically. Edit the commands to match your project.
Note: Review and test before use. Provided as is, without warranty. See Terms.
CLAUDE.mdDownload # CLAUDE.md: Drupal 11 site
## Commands
- `composer install` · `drush cr` (rebuild caches) · `drush updb -y` · `drush cim -y` / `drush cex -y`
- Code standards: `vendor/bin/phpcs --standard=Drupal,DrupalPractice web/modules/custom`
- Static analysis: `vendor/bin/phpstan analyse web/modules/custom` (with `mglaman/phpstan-drupal`)
- Tests: `vendor/bin/phpunit -c web/core web/modules/custom/<module>`
- Local env is DDEV if `.ddev/` exists: prefix commands with `ddev` (e.g. `ddev drush cr`).
## Code map
- Custom code only in `web/modules/custom/` and `web/themes/custom/`. Never edit `web/core/` or contrib modules; use patches via `cweagans/composer-patches` in `composer.json`.
- Config lives in `config/sync/`; changes made in the UI must be exported with `drush cex` and committed.
## Conventions
- Modules: `<module>.info.yml` with `core_version_requirement: ^11`, services in `<module>.services.yml`, routes in `<module>.routing.yml` with explicit `_permission` or `_custom_access`.
- Use dependency injection: services via constructor injection; controllers and forms implement `create(ContainerInterface $container)`. No `\Drupal::service()` in classes (OK in `.module` hooks only when unavoidable).
- Prefer OOP hooks with `#[Hook('hook_name')]` attributes in `src/Hook/` classes over procedural `.module` functions where the core version supports them.
- Plugins (blocks, field formatters, queue workers) use PHP attributes, not annotations, for new code.
- Entity queries call `->accessCheck(TRUE)` (or deliberately FALSE with a comment).
- Render arrays with `#cache` metadata (tags, contexts, max-age). Don't disable caching to "fix" stale output.
- Output escaping is automatic in Twig; never use `|raw` on user input. Translate strings with `$this->t()` / `{{ 'Text'|t }}`.
- Schema changes go in `hook_update_N()` or post-update functions, never manual SQL.
## Done
phpcs and phpstan are clean, tests pass, `drush cr` runs, and `drush cex` shows no unexpected config diff.