agentleFS
AnyOrgNameAnyOrgName/php-skills/skills/drupal/module-development/SKILL.md

module-development

Skill4 starsChanged 4 months agoMITView on GitHub

Scaffold a custom Drupal module — the .info.yml manifest, .services.yml service definitions with dependency injection, and hook implementations in the .module file. Use when the user wants to implement a Drupal hook such as hook_form_alter or hook_theme in a custom module .module file; the user wants to scaffold a new custom Drupal module with an .info.yml manifest and a service or hook; the user needs to register a service in a Drupal module .services.yml and inject dependencies into it. Keywords: drupal, module, info-yml, services-yml, hooks, dependency-injection, scaffold, php81.

2 other projects use a version of this.

Adopted by

Most recently taken up by

The file

We split this skill into the rules it gives an agent

6 rules. About 0 are common, 3 are distinctive, and 3 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈3

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 3

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    Do not build a custom Drupal module when a maintained contrib module on drupal.org already covers the requirement.

    line 65
  2. Distinctive:

    Do not use a module when the task is purely Twig templates, CSS, or JavaScript.

    line 66
  3. Very specific:

    Create modules/custom/my_module/my_module.info.yml with name, type: module, and core_version_requirement: ^10 || ^11.

    line 75
  4. Very specific:

    Add my_module.services.yml defining a service class under Drupal\my_module\ with its dependencies listed in arguments.

    line 76
  5. Very specific:

    Add a my_module_HOOK() implementation to my_module.module, then run drush cr so Drupal discovers it.

    line 77
  6. Distinctive:

    Not applicable — no I/O, auth, or untrusted input.

    line 107

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md
<!-- generated: do not edit — source: knowledge/atoms/drupal/module-development/ --><!-- schema: atomVersion=3 skillVersion=2 graphVersion=1 --><!-- compile: @php-skills/compiler v1.0.0 (deterministic build) -->

Drupal Module Development

When to use

  • The user wants to implement a Drupal hook such as hook_form_alter or hook_theme in a custom module .module file.
  • The user wants to scaffold a new custom Drupal module with an .info.yml manifest and a service or hook.
  • The user needs to register a service in a Drupal module .services.yml and inject dependencies into it.

When NOT to use

  • Do not build a custom Drupal module when a maintained contrib module on drupal.org already covers the requirement.
  • Do not use a module when the task is purely Twig templates, CSS, or JavaScript; that belongs in a Drupal theme.

Core guidance

  • Declare type, core_version_requirement, and dependencies in .info.yml — In my_module.info.yml set type: module, core_version_requirement: ^10 || ^11, and list each module dependency as drupal:node or project:module.
  • Match the machine name across file names and namespace — Use one lowercase, underscore machine name (e.g. my_module) for the directory, the .info.yml, the .module file, and map it to the Drupal\my_module PSR-4 namespace under src/.

Quick recipe

  1. Create modules/custom/my_module/my_module.info.yml with name, type: module, and core_version_requirement: ^10 || ^11. (verify: The module appears at /admin/modules under its declared name.)
  2. Add my_module.services.yml defining a service class under Drupal\my_module\ with its dependencies listed in arguments. (verify: drush ev can resolve the service from the container without error.)
  3. Add a my_module_HOOK() implementation to my_module.module, then run drush cr so Drupal discovers it. (verify: The hook fires on the expected event after the cache rebuild.)

Best practices

  • For controllers, forms, and plugins that Drupal instantiates, implement create(ContainerInterface $container) and pull dependencies from the container there rather than declaring them in a .services.yml file
  • Implement hooks as functions named MACHINENAME_hookname (e.g
  • Define services in my_module.services.yml with explicit arguments and inject them via the constructor; call \Drupal::service() only inside hooks and other procedural code that cannot receive injection

Anti-patterns

Anti-patternWhy it failsDo instead
Heavy business logic in the .module fileProcedural .module code is untestable and cannot use dependency injection, so it accretes into an unmaintainable tangle.Keep the .module file to thin hook stubs that delegate to injected services holding the real logic.
Editing contrib or core files directlyDirect edits are overwritten on the next update and silently reintroduce the original behavior or bugs.Alter behavior from your own module via hooks, event subscribers, or service decoration, and track upstream fixes as composer patches.
Using \Drupal::service() inside injectable classesThe static accessor hides dependencies, prevents unit testing, and couples the class to the global container.Declare the dependency as a constructor argument in .services.yml or resolve it in create().

See references/anti-patterns.md.

Common mistakes

  1. New services or hooks not picked up until cache rebuild — A newly added service, route, or hook appears to have no effect because Drupal caches its discovery data. Fix: Run drush cache:rebuild (or drush cr) after adding or renaming services, routes, or hooks.
  2. Module refuses to install on unsupported core — The module is missing from the extend list or errors on install because core_version_requirement is absent or too narrow. Fix: Set core_version_requirement: ^10 || ^11 (or the range you actually support) and remove the legacy core: 8.x key.
  3. Class not found due to PSR-4 namespace mismatch — A service class fails to load with a ClassNotFound error even though the file exists under src/. Fix: Ensure the namespace is exactly Drupal\MACHINENAME\... and the file path under src/ mirrors the sub-namespace.

Version notes

  • core_version_requirement replaced core key in Drupal 9 (PHP >=8.1) — From Drupal 9, .info.yml must use core_version_requirement with a semver constraint; the old core: 8.x key is removed in Drupal 10.

Security

  • Not applicable — no I/O, auth, or untrusted input.

Testing

  • Activation and decision behavior are verified in eval/scenarios/drupal/module-development.yaml.

Additional resources

  • references/anti-patterns.md
  • references/core.md
  • references/examples.md
  • references/pitfalls.md
  • references/recipes.md
  • references/version-differences.md

Knowledge graph

Related: drupal/entity-api, php/typed-properties

<!-- graph-fragment: graph/fragments/drupal/module-development.yaml -->

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.