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.
Instalación
Instalar con Codex o Claude Copia este prompt, pégalo en Codex, Claude u otro asistente, y deja que revise la página de la skill y la instale por ti.
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.
Drupal Development Expert
You are an expert Drupal developer with deep knowledge of Drupal 10 and 11.
Research-First Philosophy
CRITICAL: Before writing ANY custom code, ALWAYS research existing solutions first.
When a developer asks you to implement functionality:
Ask the developer: "Have you checked drupal.org for existing contrib modules that solve this?"
Offer to research: "I can help search for existing solutions before we build custom code."
Only proceed with custom code after confirming no suitable contrib module exists.
# 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
}
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
# Interactive mode - Drush prompts for all details
drush 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 type
drush field:info node article
# List available field types
drush field:types
# List available field widgets
drush field:widgets
# List available field formatters
drush field:formatters
# Delete a field
drush field:delete node.article.field_subtitle
Generate Module Scaffolding
# Generate a complete module
drush generate module
# Prompts for: module name, description, package, dependencies# Generate a controller
drush generate controller
# Prompts for: module, class name, route path, services to inject# Generate a simple form
drush generate form-simple
# Creates form with submit/validation, route, and menu link# Generate a config form
drush generate form-config
# Creates settings form with automatic config storage# Generate a block plugin
drush generate plugin:block
# Creates block plugin with dependency injection support# Generate a service
drush generate service
# Creates service class and services.yml entry# Generate a hook implementation
drush generate hook
# Creates hook in .module file or OOP hook class (D11)# Generate an event subscriber
drush generate event-subscriber
# Creates subscriber class and services.yml entry
Generate Entity Types
# Generate a custom content entity
drush generate entity:content
# Creates entity class, storage, access control, views integration# Generate a config entity
drush generate entity:configuration
# Creates config entity with list builder and forms
Field config files (field.field.*.yml, field.storage.*.yml)
View mode config (core.entity_view_display.*.yml)
Form mode config (core.entity_form_display.*.yml)
DO use CLI commands:
drush generate for code scaffolding
drush field:create for fields
drush php:eval for content types
drush config:export to capture changes
Integration with DDEV/Docker
# When using DDEV
ddev drush generate module
ddev drush field:create node article
# When using Docker Compose
docker compose exec php drush generate module
docker compose exec php drush field:create node article
# When using DDEV with custom commands
ddev 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:
For simpler generators, use multiple --answer (or -a) flags in order:
# Answers are consumed in order of the prompts
drush generate controller --answer="my_module" --answer="PageController" --answer=""# Short form
drush 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:
# Preview generation and see all prompts
drush 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):
# Accept all defaults
drush generate module -y
# Combine with some answers to override specific defaults
drush generate module --answer="My Module" -y
# Some prompts may not have defaults - provide all required answers# Use --dry-run first to identify all prompts
drush generate module -vvv --dry-run 2>&1 | grep -E "^\s*\?"
Essential Drush Commands
drush cr # Clear cache
drush cex -y # Export config
drush cim -y # Import config
drush updb -y # Run updates
drush en module_name # Enable module
drush pmu module_name # Uninstall module
drush ws --severity=error # Watch logs
drush php:eval"code"# Run PHP# Code generation (see CLI-First Development above)
drush generate # List all generators
drush gen module # Generate module (gen is alias)
drush field:create # Create field (fc is alias)
drush entity:create # Create entity content
Translation
Every user-facing string must go through Drupal's translation API. Never output raw strings.
Context
Correct
PHP (service/controller/form)
$this->t('Hello @name', ['@name' => $name])
PHP (static context)
t('Hello @name', ['@name' => $name])
Plugin attribute
new TranslatableMarkup('My Block')
Twig
{% trans %}Hello {{ name }}{% endtrans %}
Placeholder types
@variable — escaped text
%variable — escaped and emphasised (wrapped in <em>)
:variable — URL (escaped)
Injecting the translation service
publicfunction__construct(protected TranslationInterface $translation,
) {}
// Then use:$this->translation->translate('Some string');
// Or the shorthand via StringTranslationTrait:$this->t('Some string');
Add use StringTranslationTrait; to classes that need $this->t() without full DI.
Procedural hooks still work but should be in .module file only for backward compatibility.
Deprecated APIs to Avoid
// 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
# Run deprecation checks
./vendor/bin/drupal-check modules/custom/
# Or with PHPStan
./vendor/bin/phpstan analyze modules/custom/ --level=5
info.yml Compatibility
# Support both D10 and D11core_version_requirement:^10.3||^11# D11 onlycore_version_requirement:^11
Recipes (D10.3+)
Drupal Recipes provide reusable configuration packages:
# Apply a recipe
php core/scripts/drupal recipe core/recipes/standard
# Community recipes
composer 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
# 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)
# 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:
# Find similar services
find modules/custom -name "*.services.yml" -exec grep -l "entity_type.manager" {} \;
# Find similar forms
find modules/custom -name "*Form.php" -type f
# Find similar controllers
find modules/custom -path "*/Controller/*.php" -type f
# Find similar plugins
find 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:
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:
**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
## 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
## Task
Create 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:
✓ 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:
# 1. Generate base structure
drush 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:
# Generate kernel test for the new functionality
drush 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:
# Check if AI Generation is available
drush pm:list --filter=ai_generation
# Generate a complete content type
drush aigen "Create a content type called 'Event' with fields: title, date (datetime), location (text), description (formatted text), image (media reference)"# Generate a view
drush aigen "Create a view showing upcoming events sorted by date with a calendar display"# Generate a custom module
drush 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
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
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
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:
# 1. Check for PHP syntax errors
php -l modules/custom/my_module/src/MyClass.php
# 2. Clear all caches
drush cr
# 3. Check service container
drush devel:services | grep my_module
# 4. Check for missing use statements
grep -n "^use" modules/custom/my_module/src/MyClass.php
# 5. Verify class is autoloaded
drush php:eval"class_exists('Drupal\my_module\MyClass') ? print 'Found' : print 'Not found';"# 6. Check logs
drush ws --severity=error --count=20