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 permissions - •
src/- PHP classes (PSR-4 autoloading) - •
config/- Configuration files
Reference documentation:
- •
references/module_structure.md- Complete module patterns - •
references/hooks.md- Hook implementations
2. Hooks System
Implement hooks to alter Drupal behavior:
Common hooks:
- •
hook_entity_presave()- Modify entities before saving - •
hook_entity_insert/update/delete()- React to entity changes - •
hook_form_alter()- Modify any form - •
hook_node_access()- Control node access - •
hook_cron()- Perform periodic tasks - •
hook_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:
bashcp -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:
bashddev 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