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.
Resources
2Install
npx skillscat add omedia/drupal-skill/drupal-backend Install via the SkillsCat registry.
We need to produce a 2-3 sentence plain-text summary, objective, factual, no marketing language, no superlatives, no calls to action, natural prose, no bullet points, no headings, no markdown. At most 60 words. Must be only the summary text.
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.ymlUpdate 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 crDevelop 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: trueCustom 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