DrupalRX Field Guide
Enterprise Drupal diagnosis, architecture, and implementation notes.

Building a Custom Drupal 11 Module: The Architecture Decisions That Matter

One of the most common Drupal questions is:

How do I build a custom module?

The internet has thousands of answers.

Most look something like this:

  • function mymodule_form_alter() {}

Or:

  • function mymodule_node_insert() {}

Those tutorials aren’t wrong.

They’re just incomplete.

Because the difficult part of building a Drupal module isn’t creating the module.

The difficult part is building a module that remains maintainable three years later.

Most custom modules start simple.

Then requirements grow.

Features accumulate.

Business rules multiply.

Eventually the module becomes difficult to understand, difficult to test, and difficult to modify.

The difference between a maintainable module and a future headache usually comes down to architecture.

This article focuses on those decisions.

The First Question

Before writing code, ask:

Should this be a custom module at all?

This is surprisingly important.

I’ve inherited projects where developers built:

  • Custom redirects
  • Custom metadata systems
  • Custom workflow systems
  • Custom media solutions

even though mature solutions already existed.

Custom code creates responsibility.

Every line of custom code becomes something your organization must:

  • Maintain
  • Upgrade
  • Test
  • Support

The best custom module is often the one you never had to write.

Think In Terms Of Business Capabilities

One mistake developers make is organizing modules around technical features.

Example:

  • custom_formscustom_apicustom_utilscustom_helpers

These names tell us almost nothing.

Instead, think about business capabilities.

Examples:

  • event_registrationcourse_managementmember_directoryartist_portal

The module should represent a business concern.

Not a technical implementation detail.

This creates cleaner boundaries and makes systems easier to understand.

Keep Business Logic Out Of Hooks

Hooks are powerful.

They’re also one of the fastest ways to create technical debt.

Example:

  • function mymodule_node_insert() { // 200 lines of business logic}

This works.

Until it doesn’t.

Over time:

  • More conditions appear
  • More dependencies appear
  • More exceptions appear

The hook becomes difficult to maintain.

Better Approach

Use hooks to trigger behavior.

Use services to implement behavior.

Example:

  • Hook ↓Service ↓Business Logic

The hook remains simple.

The business logic remains reusable.

The architecture scales more effectively.

Embrace Dependency Injection

This is one of the most important architectural shifts in modern Drupal.

Many legacy examples still use:

  • \Drupal::service()

or

\Drupal::entityTypeManager()

inside business logic.

While convenient, this creates tight coupling.

Better Approach

Inject dependencies.

Benefits include:

  • Testability
  • Maintainability
  • Clarity
  • Reduced coupling

A class should clearly communicate what it depends on.

Dependency injection makes those relationships visible.

Services Should Own Business Logic

When I review Drupal projects, I often look for service architecture first.

Questions include:

Where does the business logic live?

Is it reusable?

Is it testable?

Is it isolated?

The strongest modules generally have service layers.

Example:

  • ArtistManagerMembershipServiceNotificationServiceReportingService

These names communicate intent.

Intent is valuable.

Event Subscribers Are Often Better Than Hooks

Developers frequently reach for hooks first.

Modern Drupal increasingly benefits from event-driven architecture.

Examples:

  • Instead of:

hook_entity_insert()

Consider:

  • Event Subscriber

Benefits:

  • Better organization
  • Clearer responsibility
  • Easier testing
  • Improved maintainability

Not every situation requires events.

But they are often worth evaluating.

Design Configuration Carefully

Configuration mistakes become painful over time.

Questions include:

What should be configurable?

What should be code?

What belongs in configuration management?

The wrong answer creates operational problems later.

Example

Good configuration candidates:

  • API endpoints
  • Feature toggles
  • Business settings

Poor configuration candidates:

  • Application logic
  • Architectural decisions

Configuration should influence behavior.

Not define architecture.

Create Clear Module Boundaries

One of the easiest ways to create technical debt is allowing modules to do too much.

Example:

  • event_manager

begins handling:

  • Registration
  • Payments
  • Notifications
  • Reporting
  • Analytics

Suddenly the module owns everything.

Complexity increases rapidly.

Better Approach

Establish boundaries.

Example:

  • event_managementevent_notificationsevent_reporting

Responsibilities remain clear.

Change becomes easier.

Think About APIs Early

Even if your project isn’t currently headless, ask:

Could this functionality eventually be exposed through an API?

The answer is increasingly yes.

Designing services properly creates future flexibility.

The API becomes another consumer of business logic rather than a separate implementation.

Logging Matters More Than Most Developers Think

Many custom modules provide almost no visibility.

When problems occur:

Nobody knows why.

Good modules provide useful logging.

Examples:

  • Failed API calls
  • Validation failures
  • Business rule violations

Logs should help answer:

What happened?

without requiring code analysis.

Avoid The Utility Module Trap

I see this repeatedly.

Developers create:

  • custom_utilshelper_modulecommon_functions

Over time these become dumping grounds.

Eventually nobody knows what belongs there.

If a module’s purpose cannot be explained clearly, that’s usually a warning sign.

Testability Is An Architectural Concern

Testing is often treated as a later activity.

It shouldn’t be.

Questions include:

Can this service be tested independently?

Are dependencies isolated?

Is business logic centralized?

Strong architecture naturally supports testing.

Weak architecture makes testing difficult.

Common Mistakes

Mistake #1

Putting Business Logic In Hooks

Hooks should trigger behavior.

Not contain it.

Mistake #2

Overusing Static Service Calls

Dependency injection creates healthier systems.

Mistake #3

Building Generic Utility Modules

Business capabilities create better boundaries.

Mistake #4

Ignoring Configuration Strategy

Configuration mistakes accumulate over time.

Mistake #5

Thinking Only About Today

Modules often live far longer than expected.

Design accordingly.

Module Architecture Checklist

Structure

  • Clear module purpose
  • Business capability defined

Services

  • Business logic isolated
  • Dependencies injected

Events

  • Event subscribers evaluated
  • Hooks minimized where appropriate

Configuration

  • Settings externalized
  • Logic kept in code

Testing

  • Services testable
  • Dependencies isolated

Operations

  • Logging implemented
  • Error handling reviewed

Final Thoughts

Building a Drupal module is easy.

Building a maintainable Drupal module is harder.

The difference rarely comes down to syntax.

It comes down to architecture.

Strong modules:

  • Have clear responsibilities
  • Isolate business logic
  • Use services effectively
  • Establish healthy boundaries
  • Remain understandable over time

Those qualities matter far more than any individual API or hook.

Because eventually every module becomes legacy.

Good architecture simply makes that future easier to manage.

Need Help Reviewing Custom Drupal Architecture?

DrupalRX helps organizations evaluate custom modules, technical debt, service architecture, modernization strategies, and long-term maintainability.

If your Drupal codebase is becoming difficult to evolve, the problem may not be the feature set.

It may be the architecture.

standard