When building complex Drupal themes, you often find yourself repeating the same HTML structures across different template files. Whether it is a global header, a complex footer, or a reusable card component, following the DRY (Don't Repeat Yourself) principle is essential for maintainable code. In Drupal, the primary way to achieve this modularity is by including partial Twig templates.

However, developers often run into a common stumbling block where the {% include %} tag simply prints the path string instead of rendering the template content. This guide will walk you through the correct ways to include partial templates in Drupal 8, 9, and 10, ensuring your theme remains clean and efficient.

Understanding Twig Includes in the Drupal Ecosystem

In a standard Symfony project, you might be used to simple relative paths for template includes. In Drupal, the process is slightly different because of how the system discovers and registers templates. Drupal uses a sophisticated theme registry that tracks where every .html.twig file lives.

If you attempt to use a relative path like {% include 'parts/footer.html.twig' %}, Drupal might not recognize the path correctly, resulting in the template engine treating the path as a literal string. To fix this, you need to use specific naming conventions and namespaces that Drupal understands.

The most robust and widely accepted method for including partials is using Twig namespaces. Every theme and module in Drupal automatically gets a namespace based on its machine name, prefixed with the @ symbol.

For example, if your theme's machine name is mytheme, you can access any file within its /templates directory using the @mytheme namespace. This is the preferred approach because it remains consistent even if your theme's directory structure moves within the Drupal installation.

{# Including a footer from your theme's /templates/parts folder #}
{% include '@mytheme/parts/footer.html.twig' %}

This method is highly portable. If you ever need to move your theme from /themes/custom to /themes/contrib, the namespace will still point to the correct location without any code changes.

Method 2: Using the Directory Variable

Another effective way to include templates is by using the global directory variable. This variable contains the path to your active theme. While this is a bit more manual than namespaces, it is explicit and clear for developers who prefer full paths.

{# Using the directory variable to target a template #}
{% include directory ~ '/templates/parts/footer.html.twig' %}

Keep in mind that if you use this method and later rename your theme or move the code to a different theme, you will need to update these paths. This makes it slightly less flexible than the namespace approach, but it is a reliable fallback if you encounter namespace resolution issues.

Method 3: Simplified Includes via Theme Registry

Thanks to updates in Drupal core's Twig handling, the system has become smarter about finding templates that are already registered. If your partial template follows standard naming conventions and is located within your theme’s /templates folder, you can often include it directly by its filename.

{# Simple include if the file is in the template registry #}
{% include 'footer.html.twig' %}

For this to work, Drupal must already be aware of footer.html.twig. If you create a new file, you may need to clear the Drupal cache so the theme registry can index the new template.

Passing Data to Included Templates

One of the most powerful features of including partials is the ability to pass variables into them. This allows you to create dynamic components that change based on where they are called. You use the with keyword to pass an array of variables.

{# Passing a boolean and a string to a partial template #}
{% include '@mytheme/includes/form-block.html.twig' with {
  'is_active': true,
  'label': 'Submit Inquiry'
} %}

You can also pass variables that have already been defined in your current Twig context:

{% set custom_vars = {'foo': 'bar'} %}
{% include '@mytheme/parts/sidebar.html.twig' with custom_vars %}

By default, the included template has access to all variables in the current context. If you want to limit the partial's access only to the variables you explicitly pass, you can add only at the end of the tag:

{% include 'template.html.twig' with {'foo': 'bar'} only %}

Advanced Logic: Fallbacks and Module Includes

Creating Template Fallbacks

Sometimes you might want to include a specific template if it exists, but fall back to a default version if it doesn't. Twig allows you to pass an array to the include function. Drupal will attempt to load the first template in the list and move to the next if it isn't found.

{{ include([
  '@mytheme/parts/custom-footer.html.twig',
  '@mytheme/parts/default-footer.html.twig'
]) }}

Including Templates from a Module

If you are a module developer and want to include a Twig file located within your module, you use the same namespace logic. If your module is named custom_search, your namespace would be @custom_search.

{# Including a template from a custom module #}
{% include '@custom_search/results-header.html.twig' %}

If you are defining the template yourself in a module, remember to implement hook_theme() in your .module file to register it:

/**
 * Implements hook_theme().
 */
function my_module_theme($existing, $type, $theme, $path) {
  return [
    'my_partial_template' => [
      'variables' => ['test_var' => NULL],
      'template' => 'my-partial-template',
    ],
  ];
}

Frequently Asked Questions

Why does my include tag just print the filename as a string?

This usually happens when Twig cannot find the file at the path provided. Instead of throwing a hard error in some configurations, it treats the input as a string. Ensure you are using the @theme_name namespace or the full path via the directory variable, and always clear your cache after adding new template files.

Can I include a template from a parent theme in a sub-theme?

Yes! This is one of the best uses for namespaces. If your sub-theme is active, you can still reach back to the base theme using its namespace, such as {% include '@stable9/layout/region.html.twig' %}.

Is there a performance hit for using many includes?

While every include adds a tiny bit of overhead, the impact is negligible compared to the benefits of maintainable code. Drupal's Twig engine compiles these templates into PHP classes, so the performance cost is only significant during the initial compilation or when the cache is cleared.

Wrapping Up

Mastering the use of partial templates is a milestone in becoming a proficient Drupal front-end developer. By utilizing namespaces like @mytheme, passing contextual data with with, and implementing fallbacks, you create a theme that is both modular and robust.

Remember to: 1. Use namespaces for maximum portability. 2. Leverage the with keyword to keep components dynamic. 3. Clear your Drupal cache whenever you add new .html.twig files to your directory structure.

With these tools in your belt, you are well on your way to building clean, DRY, and professional Drupal themes.