This article continues the Drupal Modules - Introduction and focuses on what a module can contain in real projects.
Instead of listing files, we look at scenarios and explain why you use controllers, services, plugins, configuration, and access control in the Drupal way.
This helps you think like a Drupal developer, not just write files.
11. What Can a Drupal Module Contain? (Big Picture)
A Drupal module is not just one PHP file.
Depending on the problem you are solving, a module can include many different building blocks.
You do not use everything every time. You choose only what the scenario needs.
A module can contain:
- Controllers – return pages or JSON responses
- Routes – map URLs to controllers
- Services – business logic, database access, API clients
- Database schema – custom tables
- Configuration – admin settings and feature toggles
- Access control – permissions and role-based rules
- Plugins – blocks, REST resources, queue workers, fields
- Event subscribers – react to system-level events
- Hooks – procedural extensions
- Cron jobs – background or scheduled work
Good Drupal modules are scenario-driven, not file-driven.
12. Scenario Example – Event Hosting Module
Let’s walk through a real scenario: building a custom Event Hosting module.
Business requirements
- Display events at
/events - Allow admins to manage events
- Expose events as JSON for frontend or API usage
- Control access based on user roles
- Store event data in a custom database table
Module name:
event_hosting
13. Routing and Controllers (Drupal Way)
Drupal never calls PHP files directly.
All requests go through routing.
The routing file connects a URL to a controller method.
event_hosting.routing.yml
event_hosting.list:
path: '/events'
defaults:
_controller: '\\Drupal\\event_hosting\\Controller\\EventController::list'
_title: 'Events'
requirements:
_permission: 'access event listing'
This means:
- A user visits
/events - Drupal checks permissions
- Drupal calls the controller method
src/Controller/EventController.php
class EventController {
public function list() {
return [
'#markup' => 'Event list goes here',
];
}
}
Controllers:
- Are called by routes
- Return render arrays, JSON, or Response objects
- Should stay thin and simple
Controllers coordinate, they do not do heavy work.
14. Returning JSON (API Scenario)
If the same event data is needed for a frontend app or mobile client, the controller can return JSON.
use Symfony\Component\HttpFoundation\JsonResponse;
public function api() {
return new JsonResponse([
'events' => [],
]);
}
Drupal uses the same request lifecycle for:
- HTML pages
- JSON APIs
- Headless frontends
Only the response type changes.
15. Services – Where the Real Logic Lives
Controllers should not contain:
- Database queries
- External API calls
- Business rules
That logic belongs in services.
event_hosting.services.yml
services:
event_hosting.event_manager:
class: Drupal\\event_hosting\\Service\\EventManager
src/Service/EventManager.php
class EventManager {
public function loadEvents() {
// Database queries
// External API calls
// Business logic
}
}
Services are used when:
- You need database access
- You integrate external systems
- Logic must be reused (controllers, cron, plugins)
This is the Drupal-recommended approach.
16. Database Schema and Storage
If events must be stored, define a database schema in the install file.
event_hosting.install
function event_hosting_schema() {
return [
'event_hosting_events' => [
'fields' => [
'id' => ['type' => 'serial'],
'title' => ['type' => 'varchar', 'length' => 255],
],
'primary key' => ['id'],
],
];
}
This runs automatically when the module is installed.
No manual SQL is required.
17. Access Control and Permissions
Access rules should be defined once and reused.
event_hosting.permissions.yml
access event listing:
title: 'Access event listing'
administer events:
title: 'Administer events'
Permissions are checked automatically:
- During routing
- In controllers
- In the UI
18. Configuration and Admin Settings
If administrators need settings such as:
- API keys
- Feature toggles
- Limits and defaults
Use Drupal configuration with ConfigFormBase.
Configuration is:
- Environment-aware
- Exportable
- Deployment-friendly
19. Plugins – When Drupal Needs Discovery
Plugins are used when Drupal needs to discover and manage components dynamically.
Common plugin types:
- Blocks
- REST resources
- Queue workers
- Field types
Plugins are managed by Drupal, not manually instantiated.
20. Event Subscribers – System-Level Behavior
Event subscribers react to system-level events such as:
- Request start
- Response end
- User login
- Exceptions
They are commonly used for:
- Logging
- Security
- Performance handling
21. Hooks vs Services vs Event Subscribers
A simple mental model:
- Hooks – Simple Drupal extensions
- Services – Business logic and reusable code
- Event subscribers – System-wide reactions
Modern Drupal favors services and event subscribers, with hooks still widely used.
22. Final Takeaway
Drupal modules are powerful because they are flexible.
You combine routing, controllers, services, configuration, and access control based on real-world needs.
Once you understand how to design modules by scenario, Drupal becomes predictable, scalable, and easy to extend.