Creating a custom controller

In Drupal, a controller is responsible for receiving a request and returning a response usually in the form of a Render Array. In this tutorial, we will build a module that displays a simple message and a data table at a custom URL.

calendar_today
folder_openControllers

Step 1: Define the Module

This step can be skipped if you are defining a controller in an existing module. We need to tell Drupal that our module exists. Create a folder named mint_controller (this is an example, use the the machine name suitable for your moudule) and add the info.yml file. This file provides the metadata for the Drupal extension.

descriptionmint_controller.info.yml
1name: 'Mint Controller Example'
2type: module
3description: 'Example module with custom controller'
4package: 'Mint'
5core_version_requirement: ^10 || ^11

Step 2: Register the Route

Routes map a URL path to a specific PHP function or class method. We define our route in a routing file.

File: mint_controller.routing.yml(or whatever your module name was but the file name should end with .routing.yml

  • Path: This is the URL (/mint/example) where your controller will live.
  • _controller: This points to the PHP class that handles the logic. Note that we are using the full namespace.
  • _permission: We use access content to ensure the page is viewable by general visitors.
descriptionmint_controller.routing.yml
1mint_controller.example:
2  path: '/mint/example'
3  defaults:
4    _title: 'Example Controller'
5    _controller: '\Drupal\mint_controller\Controller\ExampleController'
6  requirements:
7    _permission: 'access content'
info
Since Drupal 11.4.x, routes can be declared using PHP attributes directly inside PHP files. See this tutorial for examples.

Step 3: Build the Controller Logic

Now we create the logic. Drupal uses PSR-4 autoloading, so the file must be placed in the src/Controller directory.

descriptionsrc/Controller/ExampleController.php
1<?php
2
3declare(strict_types=1);
4
5namespace Drupal\mint_controller\Controller;
6
7use Drupal\Core\Controller\ControllerBase;
8
9/**
10 * Returns responses for Mint Controller Example routes.
11 */
12class ExampleController extends ControllerBase {
13  /**
14   * Builds the response.
15   */
16  public function __invoke(): array {
17    $header = [
18      'id' => $this->t('ID'),
19      'name' => $this->t('Name'),
20      'email' => $this->t('Email'),
21      'status' => $this->t('Status'),
22    ];
23
24    $table = [
25      '#type' => 'table',
26      '#header' => $header,
27      '#rows' => $this->getRows(),
28      '#empty' => $this->t('No data available'),
29    ];
30
31    $build['content'] = [
32      '#type' => 'item',
33      '#markup' => $this->t('It works!'),
34    ];
35
36    $build['table'] = $table;
37
38    return $build;
39  }
40
41  public function getRows(): array {
42    return [
43      [1, 'Alice Smith', '[email protected]', 'Active'],
44      [2, 'Bob Jones', '[email protected]', 'Inactive'],
45      [3, 'Carol Lee', '[email protected]', 'Active'],
46      [4, 'David Kim', '[email protected]', 'Pending'],
47      [5, 'Emma Brown', '[email protected]', 'Active'],
48      [6, 'Frank White', '[email protected]', 'Inactive'],
49      [7, 'Grace Hall', '[email protected]', 'Active'],
50      [8, 'Henry King', '[email protected]', 'Pending'],
51      [9, 'Ivy Scott', '[email protected]', 'Active'],
52      [10, 'Jack Turner', '[email protected]', 'Inactive'],
53    ];
54  }
55}

In this example, we use the __invoke() magic method. This allows the routing system to call the class directly as if it were a function.

Key Components of the Code:

  1. Inheritance: We extend ControllerBase, which gives us access to helpful tools like the translation service ($this->t()).
  2. The Header: We define an associative array for the table columns.
  3. The Render Array: Instead of returning raw HTML, we return a structured array.
    • #type => 'table': Tells Drupal to pass this data through its table theme template.
    • #rows: The actual data fetched from the getRows() helper method.
  4. Translation: All strings are wrapped in $this->t() to ensure the interface can be translated into other languages.

Step 4: Enable and Test

Once your files are in place, follow these steps to see the result:

  1. Enable the module: Run drush en mint_controller or enable it via the /admin/modules UI.
  2. Clear Caches: Since we added a routing file, you must clear the cache for Drupal to "see" the new URL. Run drush cr.
  3. Visit the Page: Navigate to your-site.com/mint/example.

Summary of Folder Structure

Your module structure should look like this:

mint_controller/
├── mint_controller.info.yml
├── mint_controller.routing.yml
└── src/
   └── Controller/
       └── ExampleController.php

Tags

ControllersModulesRouting