Drupal 10/11 development expertise. Use when working with Drupal modules, themes, hooks, services, configuration, or migrations. Triggers on mentions of Drupal, Drush, Twig, modules, themes, or Drupal API.
# Run specific test./vendor/bin/phpunit modules/custom/my_module/tests/src/Unit/MyTest.php
# Run all module tests./vendor/bin/phpunit modules/custom/my_module
# Run with coverage./vendor/bin/phpunit --coverage-html coverage modules/custom/my_module
#[Block(id:"my_block",admin_label:newTranslatableMarkup("My Block"),)]classMyBlockextendsBlockBaseimplementsContainerFactoryPluginInterface{// Always use ContainerFactoryPluginInterface for DI in plugins}
// CORRECT - parameterized query$query=$this->database->select('node','n');$query->fields('n',['nid','title']);$query->condition('n.type',$type);$query->range(0,10);$results=$query->execute();// NEVER do this - SQL injection risk$result=$this->database->query("SELECT * FROM node WHERE type = '$type'");
Before writing custom code, use Drush generators to scaffold boilerplate code.
Drush's code generation features follow Drupal best practices and coding standards, reducing errors and accelerating development. Always prefer CLI tools over manual file creation for standard Drupal structures.
Content Types and Fields
CRITICAL: Use CLI commands to create content types and fields instead of manual configuration or PHP code.
Create Content Types
bash
# Interactive mode - Drush prompts for all detailsdrush generate content-entity
# Create via PHP eval (for scripts/automation)drush php:eval "
\$type = \Drupal\node\Entity\NodeType::create([
'type' => 'article',
'name' => 'Article',
'description' => 'Articles with images and tags',
'new_revision' => TRUE,
'display_submitted' => TRUE,
'preview_mode' => 1,
]);
\$type->save();
echo 'Content type created.';
"
# List all fields on a content typedrush field:info node article
# List available field typesdrush field:types
# List available field widgetsdrush field:widgets
# List available field formattersdrush field:formatters
# Delete a fielddrush field:delete node.article.field_subtitle
Generate Module Scaffolding
bash
# Generate a complete moduledrush generate module
# Prompts for: module name, description, package, dependencies# Generate a controllerdrush generate controller
# Prompts for: module, class name, route path, services to inject# Generate a simple formdrush generate form-simple
# Creates form with submit/validation, route, and menu link# Generate a config formdrush generate form-config
# Creates settings form with automatic config storage# Generate a block plugindrush generate plugin:block
# Creates block plugin with dependency injection support# Generate a servicedrush generate service# Creates service class and services.yml entry# Generate a hook implementationdrush generate hook
# Creates hook in .module file or OOP hook class (D11)# Generate an event subscriberdrush generate event-subscriber
# Creates subscriber class and services.yml entry
Generate Entity Types
bash
# Generate a custom content entitydrush generate entity:content
# Creates entity class, storage, access control, views integration# Generate a config entitydrush generate entity:configuration
# Creates config entity with list builder and forms
# When using DDEVddev drush generate module
ddev drush field:create node article
# When using Docker Composedocker compose exec php drush generate module
docker compose exec php drush field:create node article
# When using DDEV with custom commandsddev exec drush generate controller
Non-Interactive Mode for Automation and AI Agents
CRITICAL: Drush generators are interactive by default. Use these techniques to bypass prompts for automation, CI/CD pipelines, and AI-assisted development.
Method 1:
--answers
with JSON (Recommended)
Pass all answers as a JSON object. This is the most reliable method for complete automation:
# Answers are consumed in order of the promptsdrush generate controller --answer="my_module"--answer="PageController"--answer=""# Short formdrush gen controller -a my_module -a PageController -a""
Method 3: Discover Required Answers
Use
--dry-run
with verbose output to discover all prompts and their expected values:
bash
# Preview generation and see all promptsdrush generate module -vvv --dry-run
# This shows you exactly what answers are needed# Then re-run with --answers JSON
Method 4: Auto-Accept Defaults
Use
-y
or
--yes
to accept all default values (useful when defaults are acceptable):
bash
# Accept all defaultsdrush generate module -y# Combine with some answers to override specific defaultsdrush generate module --answer="My Module"-y
# Some prompts may not have defaults - provide all required answers# Use --dry-run first to identify all promptsdrush generate module -vvv --dry-run 2>&1|grep-E"^\s*\?"
Essential Drush Commands
bash
drush cr # Clear cachedrush cex -y# Export configdrush cim -y# Import configdrush updb -y# Run updatesdrush en module_name # Enable moduledrush pmu module_name # Uninstall moduledrush ws --severity=error # Watch logsdrush php:eval "code"# Run PHP# Code generation (see CLI-First Development above)drush generate # List all generatorsdrush gen module # Generate module (gen is alias)drush field:create # Create field (fc is alias)drush entity:create # Create entity content
// DEPRECATED - don't usedrupal_set_message()// Use messenger serviceformat_date()// Use date.formatter serviceentity_load()// Use entity_type.managerdb_select()// Use database servicedrupal_render()// Use renderer service\Drupal::l()// Use Link::fromTextAndUrl()
Check Deprecations
bash
# Run deprecation checks./vendor/bin/drupal-check modules/custom/
# Or with PHPStan./vendor/bin/phpstan analyze modules/custom/ --level=5
info.yml Compatibility
yaml
# Support both D10 and D11core_version_requirement: ^10.3 || ^11
# D11 onlycore_version_requirement: ^11
Recipes (D10.3+)
Drupal Recipes provide reusable configuration packages:
bash
# Apply a recipephp core/scripts/drupal recipe core/recipes/standard
# Community recipescomposer require drupal/recipe_name
php core/scripts/drupal recipe recipes/contrib/recipe_name
When to use Recipes vs Modules:
Recipes: Configuration-only, site building, content types, views
Modules: Custom PHP code, new functionality, APIs
Testing Compatibility
bash
# Test against both versions in CIjobs:
test-d10:
env:
DRUPAL_CORE: ^10.3
test-d11:
env:
DRUPAL_CORE: ^11
Migration Planning
Before upgrading D10 → D11:
Run
drupal-check
for deprecations
Update all contrib modules to D11-compatible versions
Convert annotations to attributes
Consider moving hooks to OOP style
Test thoroughly in staging environment
Pre-Commit Checks
CRITICAL: Always run these checks locally BEFORE committing or pushing code.
CI pipeline failures are embarrassing and waste time. Catch issues locally first.
Required: Coding Standards (PHPCS)
bash
# Check for coding standard violations./vendor/bin/phpcs -p--colors modules/custom/
# Auto-fix what can be fixed./vendor/bin/phpcbf modules/custom/
# Check specific file./vendor/bin/phpcs path/to/MyClass.php
Common PHPCS errors to watch for:
Missing trailing commas in multi-line function declarations
This section describes methodologies for effective AI-assisted Drupal development, based on patterns from the Drupal community's AI tooling.
The Context-First Approach
CRITICAL: Always gather context before generating code. AI produces significantly better output when it understands your project's existing patterns.
Step 1: Find Similar Files
Before generating new code, locate similar implementations in your codebase:
bash
# Find similar servicesfind modules/custom -name"*.services.yml"-execgrep-l"entity_type.manager"{}\;# Find similar formsfind modules/custom -name"*Form.php"-type f
# Find similar controllersfind modules/custom -path"*/Controller/*.php"-type f
# Find similar pluginsfind modules/custom -path"*/Plugin/Block/*.php"-type f
Why this matters: When you show existing code patterns to AI, it will:
Match your naming conventions
Use the same dependency injection patterns
Follow your project's architectural style
Integrate consistently with existing code
Step 2: Understand Project Patterns
Before requesting code generation, identify:
markdown
1.**Naming patterns**- Service naming: `my_module.helper` vs `my_module_helper`- Class naming: `MyModuleHelper` vs `HelperService`- File organization: flat vs nested directories
2.**Dependency patterns**- Which services are commonly injected?
- How is logging handled?
- How are entities loaded?
3.**Configuration patterns**- Where is config stored?
- How are settings forms structured?
- What schema patterns are used?
Step 3: Provide Context in Requests
Structure your requests with explicit context:
markdown
**Bad request:**"Create a service that processes nodes"
**Good request:**"Create a service that processes article nodes.
Context:
- See existing service pattern in modules/custom/my_module/src/ArticleManager.php
- Inject entity_type.manager and logger.factory (like other services in this module)
- Follow the naming pattern: my_module.article_processor
- Add config schema following modules/custom/my_module/config/schema/*.yml pattern"
Structured Prompting for Drupal Tasks
Use hierarchical prompts for complex generation tasks. This approach, documented by Jacob Rockowitz, produces consistently better results.
Prompt Template Structure
markdown
## Task[One sentence describing what you want to create]
## Module Context- Module name: my_custom_module
- Module path: modules/custom/my_custom_module
- Drupal version: 10.3+ / 11
- PHP version: 8.2+
## Requirements- [Specific requirement 1]
- [Specific requirement 2]
- [Specific requirement 3]
## Code Standards- Use constructor property promotion
- Use PHP 8 attributes for plugins
- Inject all dependencies (no \Drupal::service())
- Include proper docblocks
- Follow Drupal coding standards
## Similar Files (for reference)- [Path to similar implementation]
- [Path to similar implementation]
## Expected Output-[File 1]: [Description]-[File 2]: [Description]
Example: Creating a Block Plugin
markdown
## TaskCreate a block that displays recent articles with a configurable limit.
## Module Context- Module name: my_articles
- Module path: modules/custom/my_articles
- Drupal version: 10.3+
- PHP version: 8.2+
## Requirements- Display recent article nodes (type: article)
- Configurable number of items (default: 5)
- Show title, date, and teaser
- Cache per page with article list tag
- Access: view published content permission
## Code Standards- Use #[Block] attribute (not annotation)
- Inject entity_type.manager and date.formatter
- Use ContainerFactoryPluginInterface
- Include config schema
## Similar Files- modules/custom/my_articles/src/Plugin/Block/FeaturedArticleBlock.php
## Expected Output- src/Plugin/Block/RecentArticlesBlock.php
- config/schema/my_articles.schema.yml (update)
The Inside-Out Approach
Based on the Drupal AI CodeGenerator pattern, this methodology breaks complex tasks into deterministic steps:
Phase 1: Task Classification
Determine what type of task is being requested:
Type
Description
Approach
Create
New file/component needed
Generate with DCG, then customize
Edit
Modify existing code
Read first, then targeted changes
Information
Question about code/architecture
Search and explain
Composite
Multiple steps needed
Break down, execute sequentially
Phase 2: Solvability Check
Before generating, verify:
markdown
✓ Required dependencies available?
✓ Target directory exists and is writable?
✓ No conflicting files/classes?
✓ All referenced services/classes exist?
✓ Compatible with Drupal version?
Phase 3: Scaffolding First
Use DCG to scaffold, then customize. This ensures Drupal best practices:
bash
# 1. Generate base structuredrush generate plugin:block --answers='{
"module": "my_module",
"plugin_id": "recent_articles",
"admin_label": "Recent Articles",
"class": "RecentArticlesBlock"
}'# 2. Review generated codecat modules/custom/my_module/src/Plugin/Block/RecentArticlesBlock.php
# 3. Customize with specific requirements# (AI edits the generated file to add business logic)
Phase 4: Auto-Generate Tests
Always generate tests alongside code:
bash
# Generate kernel test for the new functionalitydrush generate test:kernel --answers='{
"module": "my_module",
"class": "RecentArticlesBlockTest"
}'
Iterative Development Workflow
Expect 80% completion from AI-generated code. Plan for refinement cycles.
The Realistic Workflow
┌─────────────────────────────────────────────────────────────┐
│ 1. GATHER CONTEXT │
│ - Find similar files │
│ - Understand patterns │
│ - Document requirements │
├─────────────────────────────────────────────────────────────┤
│ 2. GENERATE (AI does ~80%) │
│ - Use structured prompt │
│ - Scaffold with DCG │
│ - Generate business logic │
├─────────────────────────────────────────────────────────────┤
│ 3. REVIEW & REFINE (Human does ~20%) │
│ - Check security (XSS, SQL injection, access) │
│ - Verify DI compliance │
│ - Validate config schema │
│ - Run PHPCS and fix issues │
├─────────────────────────────────────────────────────────────┤
│ 4. TEST │
│ - Run generated tests │
│ - Add edge case tests │
│ - Manual smoke testing │
├─────────────────────────────────────────────────────────────┤
│ 5. ITERATE (if needed) │
│ - Fix failing tests │
│ - Address review feedback │
│ - Refine based on testing │
└─────────────────────────────────────────────────────────────┘
Common Refinement Tasks
Issue
Solution
PHPCS errors
Run
phpcbf
for auto-fix, manual fix for complex issues
Missing DI
Add to constructor, update
create()
method
No cache metadata
Add
#cache
with tags, contexts, max-age
Missing access check
Add permission check or access handler
No config schema
Create schema file matching config structure
Hardcoded strings
Wrap in
$this->t()
with proper placeholders
Integration with Drupal AI Module
When the AI module is available, leverage
drush aigen
for rapid prototyping:
bash
# Check if AI Generation is availabledrush pm:list --filter=ai_generation
# Generate a complete content typedrush aigen "Create a content type called 'Event' with fields: title, date (datetime), location (text), description (formatted text), image (media reference)"# Generate a viewdrush aigen "Create a view showing upcoming events sorted by date with a calendar display"# Generate a custom moduledrush aigen "Create a module that sends email notifications when new events are created"
Important: Always review AI-generated code. The AI Generation module is experimental and intended for development only.
Prompt Patterns for Common Tasks
Content Type with Fields
markdown
Create a content type for [purpose].
Content type:
- Machine name: [machine_name]
- Label: [Human Label]
- Description: [Description]
- Publishing options: published by default, create new revision
- Display author and date: no
Fields:
1. [field_name] ([field_type]): [description] - [required/optional]
2. [field_name] ([field_type]): [description] - [required/optional]
After creation, export config with: drush cex -y
Custom Service
markdown
Create a service for [purpose].
Service:
- Name: [module].service_name
- Class: Drupal\[module]\[ServiceClass]
- Inject: [service1], [service2]
Methods:
- methodName(params): return_type - [description]
- methodName(params): return_type - [description]
Include:
- Interface definition
- services.yml entry
- PHPDoc with @param and @return
Event Subscriber
markdown
Create an event subscriber for [purpose].
Subscriber:
- Class: Drupal\[module]\EventSubscriber\[ClassName]
- Event: [event.name]
- Priority: [0-100]
Behavior:
- [Describe what should happen when event fires]
Include:
- services.yml entry with tags
- Proper type hints
Debugging AI-Generated Code
When generated code doesn't work:
bash
# 1. Check for PHP syntax errorsphp -l modules/custom/my_module/src/MyClass.php
# 2. Clear all cachesdrush cr
# 3. Check service containerdrush devel:services |grep my_module
# 4. Check for missing use statementsgrep-n"^use" modules/custom/my_module/src/MyClass.php
# 5. Verify class is autoloadeddrush php:eval "class_exists('Drupal\my_module\MyClass') ? print 'Found' : print 'Not found';"# 6. Check logsdrush ws --severity=error --count=20