The days of procedural, hook_ heavy Drupal modules are over. While those methods served the community well for years, Drupal is now a modern, object-oriented framework. This shift, which began in Drupal 8 and has been solidified in Drupal 11, has made development cleaner, more efficient, and far more professional. If you're still writing modules the old way, it's time to upgrade your skills.
Here are the seven key concepts every modern Drupal developer should master in 2025:
1. PHP Attributes Instead of Doctrine Annotations
In the past, plugins, entities, or routes were described using proprietary Doctrine annotations. This was error-prone and only partially supported by IDEs.
With native PHP attributes (#[...]), that era is over. Definitions now live directly in your code, with proper typing and no need for extra parsers. The result: less “magic,” more clarity, and stronger support from modern IDEs and tools. (ok, it's not completely over yet, as not every plugin already supports the new syntax. But with every new minor version release, the number raises, as well as plugin types provided by contributed modules).
2. OOP Hooks (New in Drupal 11.2)
Drupal’s hook system has always been powerful but strictly procedural. Since Drupal 11.2, many hooks can now be implemented as OOP hooks.
Example:
#[Hook('form_node_form_alter')]
public function alterNodeForm(FormStateInterface $form_state): void {
// Adjustments here …
}
This new syntax is cleaner, allows you to group hooks into classes, use dependency injection and makes them far easier to maintain and test.
3. Event Subscribers as Flexible Entry Points
Alongside OOP hooks, event subscribers are a core pattern in modern Drupal (already since 8.x). They don’t just replace old procedural hooks—they extend what’s possible.
Events are well-defined, can be prioritized, and live in services for easy reuse. This makes system alterations modular, testable, and future-ready, fully aligned with Symfony’s best practices.
4. Dependency Injection
Global calls like \Drupal::service()
belong in the past. Instead, modern Drupal code relies on dependency injection (DI):
- Constructor injection for clean, testable dependencies.
- The create() factory method for cases where you need access to the service container.
The result: reduced coupling, greater readability, better testability and classes with clearly defined responsibilities.
5. Code Quality with PHPStan & PHP CodeSniffer
Clean code doesn’t happen by accident. Two tools are essential today:
- PHPStan (Drupal level): static analysis that finds bugs and type errors before they hit your logs.
- PHP CodeSniffer (PHPCS): enforces consistent code style according to Drupal standards.
Together, they ensure your module is not only functional but also robust, maintainable, and team-friendly.
6. Swapping and Decorating Services
One of Drupal’s most powerful features is the ability to swap or decorate services.
Instead of patching core or hacking third-party code, you can extend or replace existing services cleanly through the service container.
Examples: custom logging, alternative mail handling, or specialized access checks.
This gives you maximum flexibility without messy workarounds.
7. Modern JavaScript Patterns
Drupal’s frontend is evolving, too. jQuery has been largely removed, and developers are encouraged to write clean ES6+ JavaScript and rely on lightweight, focused libraries.
Drupal behaviors remain central, but without the baggage. Writing modern JavaScript ensures better performance, leaner themes, and future-ready custom modules.
Conclusion
Drupal is no longer an “old-school CMS” like it was in days of Drupal 7 and older — it’s a modern framework. By embracing these seven concepts — from PHP attributes and OOP hooks to service decoration and modern JS — you’ll write modules that are not only future-proof but also clean, scalable, and professional.
Bonus: Upgrade Status for Easier Transitions
Even if your project includes a large amount of custom code, Drupal’s Upgrade Status module makes version upgrades far more manageable.
It scans your codebase for deprecated APIs, highlights compatibility issues, and provides actionable guidance for bringing your modules up to date.
Using Upgrade Status early and often ensures smoother transitions between major versions—saving time, reducing stress, and giving you confidence that your custom code will keep running well into the future.