12 KiB
name, description
| name | description |
|---|---|
| drupal-backend | Drupal Back End Specialist skill for custom module development, hooks, APIs, and PHP programming (Drupal 8-11+). Use when building custom modules, implementing hooks, working with entities, forms, plugins, or services. |
Drupal Back End Development
Overview
Enable expert-level Drupal back end development capabilities. Provide comprehensive guidance for custom module development, PHP programming, API usage, hooks, plugins, services, and database operations for Drupal 8, 9, 10, and 11+.
When to Use This Skill
Invoke this skill when working with:
- Custom module development: Creating new Drupal modules
- Hooks: Implementing Drupal hooks to alter system behavior
- Controllers & routing: Building custom pages and routes
- Forms: Creating configuration or custom forms
- Entities: Working with content entities or config entities
- Plugins: Building blocks, field types, formatters, or other plugins
- Services: Creating reusable business logic services
- Database operations: Custom queries and schema definitions
- APIs: Entity API, Form API, Database API, Plugin API
Core Capabilities
1. Custom Module Development
Create complete, standards-compliant Drupal modules:
Quick start workflow:
- Use module template from
assets/module-template/ - Replace
MODULENAMEwith machine name (lowercase, underscores) - Replace
MODULELABELwith human-readable name - Implement functionality using Drupal APIs
- Enable module and test:
ddev drush en mymodule -y
Module structure:
.info.yml- Module metadata and dependencies.module- Hook implementations.routing.yml- Route definitions.services.yml- Service definitions.permissions.yml- Custom permissionssrc/- PHP classes (PSR-4 autoloading)config/- Configuration files
Reference documentation:
references/module_structure.md- Complete module patternsreferences/hooks.md- Hook implementations
2. Hooks System
Implement hooks to alter Drupal behavior:
Common hooks:
hook_entity_presave()- Modify entities before savinghook_entity_insert/update/delete()- React to entity changeshook_form_alter()- Modify any formhook_node_access()- Control node accesshook_cron()- Perform periodic taskshook_install/uninstall()- Module installation tasks
Hook pattern:
function MODULENAME_hook_name($param1, &$param2) {
// Implementation
}
Best practices:
- Implement hooks in
.modulefile only - Use proper type hints
- Document with PHPDoc blocks
- Check for entity types before processing
- Be mindful of performance
3. Controllers & Routing
Create custom pages and endpoints:
Route definition (.routing.yml):
mymodule.example:
path: '/example/{param}'
defaults:
_controller: '\Drupal\mymodule\Controller\ExampleController::content'
_title: 'Example Page'
requirements:
_permission: 'access content'
param: \d+
Controller pattern:
namespace Drupal\mymodule\Controller;
use Drupal\Core\Controller\ControllerBase;
class ExampleController extends ControllerBase {
public function content() {
return ['#markup' => $this->t('Hello!')];
}
}
With dependency injection:
public function __construct(EntityTypeManagerInterface $entity_type_manager) {
$this->entityTypeManager = $entity_type_manager;
}
public static function create(ContainerInterface $container) {
return new static($container->get('entity_type.manager'));
}
4. Forms API
Build configuration and custom forms:
Configuration form:
class SettingsForm extends ConfigFormBase {
protected function getEditableConfigNames() {
return ['mymodule.settings'];
}
public function buildForm(array $form, FormStateInterface $form_state) {
$config = $this->config('mymodule.settings');
$form['api_key'] = [
'#type' => 'textfield',
'#title' => $this->t('API Key'),
'#default_value' => $config->get('api_key'),
];
return parent::buildForm($form, $form_state);
}
public function submitForm(array &$form, FormStateInterface $form_state) {
$this->config('mymodule.settings')
->set('api_key', $form_state->getValue('api_key'))
->save();
parent::submitForm($form, $form_state);
}
}
Form validation:
public function validateForm(array &$form, FormStateInterface $form_state) {
if (strlen($form_state->getValue('field')) < 5) {
$form_state->setErrorByName('field', $this->t('Too short.'));
}
}
5. Entity API
Work with content and configuration entities:
Loading entities:
// Load single entity
$node = \Drupal::entityTypeManager()->getStorage('node')->load($nid);
// Load multiple entities
$nodes = \Drupal::entityTypeManager()->getStorage('node')->loadMultiple([1, 2, 3]);
// Load by properties
$nodes = \Drupal::entityTypeManager()->getStorage('node')->loadByProperties([
'type' => 'article',
'status' => 1,
]);
Creating/saving entities:
$node = \Drupal::entityTypeManager()->getStorage('node')->create([
'type' => 'article',
'title' => 'My Article',
'body' => ['value' => 'Content', 'format' => 'basic_html'],
]);
$node->save();
Entity queries:
$query = \Drupal::entityQuery('node')
->condition('type', 'article')
->condition('status', 1)
->accessCheck(TRUE)
->sort('created', 'DESC')
->range(0, 10);
$nids = $query->execute();
6. Plugin System
Create custom plugins (blocks, fields, etc.):
Block plugin:
/**
* @Block(
* id = "mymodule_custom_block",
* admin_label = @Translation("Custom Block"),
* )
*/
class CustomBlock extends BlockBase {
public function build() {
return ['#markup' => 'Block content'];
}
public function blockForm($form, FormStateInterface $form_state) {
$form['setting'] = [
'#type' => 'textfield',
'#title' => $this->t('Setting'),
'#default_value' => $this->configuration['setting'] ?? '',
];
return $form;
}
public function blockSubmit($form, FormStateInterface $form_state) {
$this->configuration['setting'] = $form_state->getValue('setting');
}
}
7. Services & Dependency Injection
Create reusable services:
Service definition (.services.yml):
services:
mymodule.custom_service:
class: Drupal\mymodule\Service\CustomService
arguments: ['@entity_type.manager', '@current_user']
Service class:
namespace Drupal\mymodule\Service;
class CustomService {
protected $entityTypeManager;
protected $currentUser;
public function __construct(EntityTypeManagerInterface $entity_type_manager, AccountProxyInterface $current_user) {
$this->entityTypeManager = $entity_type_manager;
$this->currentUser = $current_user;
}
public function doSomething() {
// Business logic
}
}
8. Database Operations
Execute custom queries:
Using Database API:
$database = \Drupal::database();
// Select query
$query = $database->select('node_field_data', 'n')
->fields('n', ['nid', 'title'])
->condition('type', 'article')
->condition('status', 1)
->range(0, 10);
$results = $query->execute()->fetchAll();
// Insert
$database->insert('mymodule_table')
->fields(['name' => 'Example', 'value' => 123])
->execute();
// Update
$database->update('mymodule_table')
->fields(['value' => 456])
->condition('name', 'Example')
->execute();
Schema definition (.install file):
function mymodule_schema() {
$schema['mymodule_table'] = [
'fields' => [
'id' => ['type' => 'serial', 'not null' => TRUE],
'name' => ['type' => 'varchar', 'length' => 255, 'not null' => TRUE],
'value' => ['type' => 'int', 'not null' => TRUE, 'default' => 0],
],
'primary key' => ['id'],
'indexes' => ['name' => ['name']],
];
return $schema;
}
Development Workflow
Creating a Custom Module
-
Scaffold the module:
cp -r assets/module-template/ /path/to/drupal/modules/custom/mymodule/ cd /path/to/drupal/modules/custom/mymodule/ mv MODULENAME.info.yml mymodule.info.yml mv MODULENAME.module mymodule.module mv MODULENAME.routing.yml mymodule.routing.yml -
Update module files:
- Replace
MODULENAMEwith machine name - Replace
MODULELABELwith readable name - Update
.info.ymldependencies - Customize controller, routes, logic
- Replace
-
Enable and test:
ddev drush en mymodule -y ddev drush cr -
Develop iteratively:
- Implement functionality
- Clear cache:
ddev drush cr - Test thoroughly
- Check logs:
ddev drush watchdog:tail
Standard Development Workflow
- Plan: Identify what APIs/hooks you need
- Code: Implement using Drupal APIs
- Test: Enable module, clear cache, test functionality
- Debug: Check logs, use Xdebug if needed
- Refine: Optimize, add tests, improve code quality
Best Practices
Module Development
- PSR-4 autoloading: Follow namespace conventions
- Dependency injection: Use DI in classes
- Hooks: Implement only in
.modulefiles - Type hints: Use proper PHP type declarations
- Documentation: Add PHPDoc blocks
- Testing: Write automated tests
Code Quality
- Drupal coding standards: Follow PHPCS rules
- Security: Validate input, sanitize output, check permissions
- Performance: Cache when possible, optimize queries
- Accessibility: Ensure forms and pages are accessible
- Internationalization: Use
$this->t()for all strings
API Usage
- Entity API: Prefer entity operations over direct DB queries
- Configuration: Use Config API for settings
- State API: Use for temporary/non-exportable data
- Cache API: Implement caching for expensive operations
- Queue API: Use for long-running tasks
Common Patterns
Event Subscriber
class MyModuleSubscriber implements EventSubscriberInterface {
public static function getSubscribedEvents() {
return [KernelEvents::REQUEST => ['onRequest', 0]];
}
public function onRequest(RequestEvent $event) {
// Handle event
}
}
Custom Permission
# mymodule.permissions.yml
administer mymodule:
title: 'Administer My Module'
restrict access: true
Custom Access Check
class CustomAccessCheck implements AccessInterface {
public function access(AccountInterface $account) {
return AccessResult::allowedIf($account->hasPermission('access content'));
}
}
Troubleshooting
Module Not Appearing
- Check
.info.ymlsyntax - Verify
core_version_requirement - Clear cache:
ddev drush cr
Hooks Not Working
- Verify hook name is correct
- Clear cache after adding hooks
- Check module weight if hook order matters
Class Not Found
- Verify PSR-4 namespace matches directory
- Clear cache to rebuild class registry
- Check autoloading with
composer dump-autoload
Database Errors
- Check schema definition syntax
- Run
ddev drush updbafter schema changes - Verify table/column names in queries
Resources
Reference Documentation
-
references/hooks.md- Common hooks with examples- Entity hooks
- Form hooks
- Node hooks
- Installation hooks
- Token hooks
-
references/module_structure.md- Module patterns- Controllers and routing
- Forms (config and custom)
- Plugins (blocks, fields, etc.)
- Services and DI
- Event subscribers
Asset Templates
assets/module-template/- Module scaffold.info.ymlmetadata.modulehook file.routing.ymlroutes- Example controller
Version Compatibility
Drupal 8 vs 9 vs 10 vs 11
- Core APIs: Largely consistent across versions
- PHP requirements: 8.x (7.0+), 9.x (7.3+), 10.x (8.1+), 11.x (8.3+)
- Deprecations: Check change records when upgrading
- Symfony: Different Symfony versions in each Drupal version
See Also
- drupal-frontend - Theme development, Twig templates
- drupal-tooling - DDEV and Drush development tools
- Drupal API - Official API documentation