Creating a Controller and Routing in Drupal Module Developmentfor Drupal 8 , 9 , 10 , and 11

Last updated :  

In this tutorial, we’ll explore two critical components of Drupal module development: the controller and the routing file. These work together to create custom pages in your Drupal site. Using our "Hello World" module as an example, we’ll dissect the HelloWorldPage controller (HelloWorldPage.php) and the hello_world.routing.yml file, which together display "It works!" at the URL /hello. This guide is part of our Drupal module development series for beginners, focusing on Drupal 10/11 (as of 2025).

By the end, you’ll understand how controllers handle logic, how routing maps URLs to controllers, and how to implement them step-by-step. Let’s dive in!

What Are Controllers and Routing in Drupal?

  • Controller: A PHP class that defines the logic for a page or action. It processes requests and returns content (e.g., HTML, JSON) to display to users. In our case, the HelloWorldPage controller outputs the text "It works!".
  • Routing: The .routing.yml file defines URL paths (e.g., /hello) and maps them to controllers. It tells Drupal what to do when a user visits a specific URL, including permissions and page titles.
  • Why They Matter: Together, they allow you to create custom pages or endpoints, making your module interactive and functional.

This tutorial builds on our previous lessons about the .info.yml file, so ensure your "Hello World" module is set up in /modules/custom/hello_world/.

Step-by-Step Breakdown of the Controller

The controller is a PHP class that handles the logic for your custom page. Let’s analyze the provided HelloWorldPage.php file, located in hello_world/src/Controller/HelloWorldPage.php:

 
<?php

declare(strict_types=1);

namespace Drupal\hello_world\Controller;

use Drupal\Core\Controller\ControllerBase;

/**
 * Returns responses for Hello World routes.
 */
final class HelloWorldPage extends ControllerBase {

  /**
   * Builds the response.
   */
  public function __invoke(): array {

    $build['content'] = [
      '#type' => 'item',
      '#markup' => $this->t('It works!'),
    ];

    return $build;
  }

}
 

Step 1: PHP Declaration and Strict Types

Code: <?php declare(strict_types=1);

  • Purpose: Enables strict type checking in PHP, ensuring variables are used with their declared types (e.g., strings, arrays). This improves code reliability.
  • Explanation: This is a modern PHP practice, recommended for Drupal modules to catch errors early.
  • Best Practice: Always include declare(strict_types=1); at the top of PHP files for better code quality.
  • Common Mistake: Omitting this doesn’t break the module but may lead to type-related bugs.

Step 2: Namespace

Code: namespace Drupal\hello_world\Controller;

  • Purpose: Places the class in the Drupal\hello_world\Controller namespace, following Drupal’s PSR-4 autoloading standard.
  • Explanation: The namespace matches the file’s location (src/Controller/). Drupal uses namespaces to organize and autoload classes efficiently.
  • Best Practice: Ensure the namespace matches the folder structure: Drupal\<module_name>\<subfolder>.
  • Common Mistake: Incorrect namespaces (e.g., Drupal\HelloWorld\Controller) will cause Drupal to fail to load the class.

Step 3: Use Statement

Code: use Drupal\Core\Controller\ControllerBase;

  • Purpose: Imports the ControllerBase class, which provides helper methods like $this->t() for translations.
  • Explanation: Extending ControllerBase gives access to Drupal’s core utilities, making it easier to build responses.
  • Best Practice: Use ControllerBase for most controllers unless you need a custom base class.
  • Common Mistake: Forgetting to import ControllerBase will cause errors when using its methods.

Step 4: Class Definition

Code: final class HelloWorldPage extends ControllerBase {

  • Purpose: Defines the HelloWorldPage class, which extends ControllerBase to inherit its functionality.
  • Explanation: The final keyword prevents other classes from extending this one, which is good for simple controllers. The class name (HelloWorldPage) should be descriptive and follow CamelCase.
  • Best Practice: Use meaningful class names that reflect the controller’s purpose (e.g., HelloWorldPage for a page-specific controller).
  • Common Mistake: Mismatched class names between the file and routing configuration will cause errors.

Step 5: Method and Response

Code:

 
public function __invoke(): array {
  $build['content'] = [
    '#type' => 'item',
    '#markup' => $this->t('It works!'),
  ];
  return $build;
}
 
  • Purpose: The __invoke method is called when the route is accessed, returning a render array that Drupal converts to HTML.
  • Explanation:
    • __invoke is a special PHP method that allows the class to be called as a function, often used in Drupal for single-purpose controllers.
    • The render array ($build) defines the page content. Here, #type => 'item' creates a basic content element, and #markup => $this->t('It works!') outputs translatable text.
    • $this->t() ensures the string is translatable, a Drupal best practice for multilingual sites.
  • Best Practice: Return a render array for flexibility (Drupal can theme it). Use $this->t() for all user-facing strings.
  • Common Mistake: Returning plain text or incorrect render array keys can break the page or cause rendering issues.

Image Example Idea: A screenshot of a code editor (e.g., VS Code) showing HelloWorldPage.php with syntax highlighting, emphasizing the namespace and __invoke method. (Would you like me to generate this image?)

Step-by-Step Breakdown of the Routing File

The hello_world.routing.yml file maps the URL /hello to the controller. It’s located in the module’s root folder (hello_world/):

 

hello_world.hello_world_page:
  path: '/hello'
  defaults:
    _title: 'Hello World Page'
    _controller: '\Drupal\hello_world\Controller\HelloWorldPage'
  requirements:
    _permission: 'access content'

 

Step 1: Route Name

Line: hello_world.hello_world_page:

  • Purpose: A unique identifier for the route, used internally by Drupal.
  • Explanation: The name follows the format <module_name>.<route_id>. Here, hello_world.hello_world_page is descriptive and avoids conflicts.
  • Best Practice: Use clear, unique names. Include the module name to namespace the route.
  • Common Mistake: Duplicate route names across modules can cause conflicts.

Step 2: Path

Line: path: '/hello'

  • Purpose: Defines the URL path users visit (e.g., example.com/hello).
  • Explanation: This is the public-facing URL for your page. It must start with a slash (/).
  • Best Practice: Keep paths simple and meaningful. Avoid reserved paths like /admin.
  • Common Mistake: Missing the leading slash or using spaces will break the route.

Step 3: Defaults

Line:

 
defaults:
  _title: 'Hello World Page'
  _controller: '\Drupal\hello_world\Controller\HelloWorldPage'
 
  • Purpose: Specifies default settings, like the page title and the controller to handle the request.
  • Explanation:
    • _title sets the page title displayed in the browser tab and page header.
    • _controller points to the controller class (\Drupal\hello_world\Controller\HelloWorldPage). Since __invoke is used, no specific method is needed (Drupal calls __invoke automatically).
  • Best Practice: Use full namespace for the controller. Ensure the title is clear and relevant.
  • Common Mistake: Typos in the namespace or class name will cause a "controller not found" error.

Step 4: Requirements

Line:

 
requirements:
  _permission: 'access content'
 
  • Purpose: Defines access rules for the route.
  • Explanation: The access content permission allows any authenticated or anonymous user to view the page. Other permissions (e.g., administer site configuration) can restrict access.
  • Best Practice: Use appropriate permissions to control access. Drupal’s permission system is robust, so leverage it.
  • Common Mistake: Incorrect permissions or omitting this key can make the page inaccessible or too open.

Image Example Idea: A screenshot of the hello_world.routing.yml file in a code editor, highlighting the YAML structure. (Confirm if you’d like me to generate this.)

Creating the Controller and Routing: Step-by-Step Guide

Let’s implement these files for the "Hello World" module, assuming the .info.yml is already set up.

Step 1: Create the Controller File

  1. Navigate to /modules/custom/hello_world/.
  2. Create the folder structure src/Controller/ (so the full path is hello_world/src/Controller/).
  3. Create HelloWorldPage.php inside src/Controller/
  4. Paste the controller code:
 
<?php

declare(strict_types=1);

namespace Drupal\hello_world\Controller;

use Drupal\Core\Controller\ControllerBase;

/**
 * Returns responses for Hello World routes.
 */
final class HelloWorldPage extends ControllerBase {

  /**
   * Builds the response.
   */
  public function __invoke(): array {

    $build['content'] = [
      '#type' => 'item',
      '#markup' => $this->t('It works!'),
    ];

    return $build;
  }

}
 
  1. Save the file, ensuring the name matches the class (HelloWorldPage.php).

Step 2: Create the Routing File

  1. In the module root (hello_world/), create hello_world.routing.yml
  2. Paste the routing code:
 
hello_world.hello_world_page:
  path: '/hello'
  defaults:
    _title: 'Hello World Page'
    _controller: '\Drupal\hello_world\Controller\HelloWorldPage'
  requirements:
    _permission: 'access content'
 
  1. Save with proper YAML formatting (2-space indentation, no tabs).

Step 3: Test the Module

  1. Clear Drupal’s cache: drush cr or go to /admin/config/development/performance and click “Clear all caches”.
  2. Enable the module if not already enabled: drush en hello_world -y or use /admin/modules.
  3. Visit example.com/hello. You should see “It works!” with the title “Hello World Page”.
  4. If the page doesn’t load, check logs at /admin/reports/dblog or verify file paths and names.
hello world drupal module

Common Pitfalls for Beginners

  • Namespace Errors: Ensure the namespace in the controller (Drupal\hello_world\Controller) matches the file path and routing configuration.
  • File Naming: The controller file must match the class name (HelloWorldPage.php).
  • YAML Syntax: Use 2-space indentation in .routing.yml. Validate with a YAML linter if errors occur.
  • Cache Issues: Always clear the cache after adding or modifying routes (drush cr).
  • Permission Problems: If the page is inaccessible, verify the _permission value exists and is assigned to users.

Next Steps

You’ve now created a custom page using a controller and routing! This is a core skill for Drupal module development. In the next tutorial, we could cover adding a form to the "Hello World" module, creating a custom block, or exploring hooks in a .module file and many more topics , Stay connected.