Create custom twig templates for custom module

Last updated on
11 January 2024

The general idea in Drupal 8 is that you want to avoid creating html directly in the PHP code of your custom module. You want this to go into twig templates. To create new twig templates in your module, go through the following steps.

Step #1: Define hook_theme() in .module file

Create a [module].module file if it doesn't already exist in your module, and add code that defines each of your twig templates. The key of each item in the array is what you will need to call the template later. Do not use dashes in the file name.

/**
 * Implements hook_theme().
 */
function [module]_theme($existing, $type, $theme, $path) {
  return [
    'my_template' => [
      'variables' => ['test_var' => NULL],
    ],
  ];
}

See the documentation for hook_theme().

Step #2: Create Twig Template

In your module, inside of the templates folder, create your twig template. The name of the file has to match what you put into hook_theme() (make sure replace underscores with dashes). In this case, the file name would be my-template.html.twig.
Here is the file I used to test:

{# [module]/templates/my-template.html.twig #}
<p>Test twig template!</p>
 
<p>test_var: {{ test_var }}</p>

The beauty of this is that the template file defined in your module will be used if such a file does not already exist in your theme. Just dump a file inside of the templates folder of your theme, clear cache (drush cache-rebuild/ drush cr), and it will read that file instead.

You can put the file in any nested sub-folder of the site theme to keep things organized.

Step #3: Call the Template

The usage examples below with 3 different scenarios. Use the example that suits your use case. 

Step #3.1: Call from controller

In the place where you are returning your render array (whether from a controller method that is called from your router yml file, or wherever), make a call to your twig template. Below is an example from a testing module that is called from the routing yml file in the module. (need more info on this part)

<?php
/**
 * @file
 * Contains \Drupal\test_twig\Controller\TestTwigController.
 */
 
namespace Drupal\[module_name]\Controller;
 
use Drupal\Core\Controller\ControllerBase;
 
class TestTwigController extends ControllerBase {
  public function content() {
 
    return [
      '#theme' => 'my_template',
      '#test_var' => $this->t('Test Value'),
    ];
 
  }
}

Step #3.2: Render as HTML

You can also use render service method to build the output if you need to use this as part of a different workflow in your code: -

$renderable = [
  '#theme' => 'my_template',
  '#test_var' => 'test variable',
];
$rendered = \Drupal::service('renderer')->renderPlain($renderable);

Or if using in a not standard place such as email template you can:

 $path = drupal_get_path('module', 'your_module') . '/templates/email-table.html.twig';

 $rendered = \Drupal::service('twig')->load($path)->render([
   'test_var' => $value
 ]);

Keep in mind that this is a basic implementation, and doesn't do any kind of caching. The Render API Overview docs contain more information about how you can add caching to this. Speaking of caching - variable names will be cached and if you change them (say, "test_var" to "my_var") you'll have to refresh the cache.

Step #3.3: Render as part of another plugin (such as block).

You can also use render array as output of custom plugin such as block: 

<?php

namespace Drupal\[module_name]\Plugin\Block;

use Drupal\Core\Block\BlockBase;

/**
 * Provides a 'My Template' block.
 *
 * @Block(
 *   id = "my_template_block",
 *   admin_label = @Translation("My Template")
 * )
 */
class MyTemplateBlock extends BlockBase {

  /**
   * {@inheritdoc}
   */
  public function defaultConfiguration() {
    return ['label_display' => FALSE];
  }

  /**
   * {@inheritdoc}
   */
  public function build() {
    $renderable = [
      '#theme' => 'my_template',
      '#test_var' => 'test variable',
    ];

    return $renderable;
  }

}

For more information about above example see Block API overview.

Help improve this page

Page status: No known problems

You can: