Skip to content

Drupal Twig Templating — Complete Guide to Twig in Drupal

DodaTech Updated 2026-06-27 10 min read

In this tutorial, you'll learn how to use Twig in Drupal theming — from basic syntax and variables to filters, functions, template inheritance, and debugging — so you can build custom templates with confidence.

What You'll Learn

  • Twig syntax fundamentals: variables, blocks, comments, and control structures
  • How Drupal exposes content, page, node, and user variables to templates
  • Using Twig filters and functions for text formatting and URL generation
  • Extending and including templates with inheritance
  • Enabling Twig debugging and performance optimization

Why It Matters

Drupal 8 replaced PHP-based templates with Twig, a modern template engine from the Symfony framework. Twig is faster, more secure, and easier to read than raw PHP. It automatically escapes output, prevents XSS Attacks, and enforces a clean separation between logic and presentation. For anyone building Drupal themes, Twig is the language you will use every day. Mastering it means you can build any layout, override any template, and customize every aspect of your site's appearance.

Real-World Use

A development team builds a custom publishing platform on Drupal. The designers create mockups in Figma, and the frontend developers translate them into Twig templates. They use template inheritance to define a base page layout, then extend it for articles, landing pages, and search results. Twig filters format dates and truncate text. The autoescape feature ensures user-generated content never breaks the layout or executes malicious scripts.

Learning Path

flowchart LR
  A[Installing Themes] --> B[Twig Templating]
  B --> C[Sub-themes]
  C --> D[Template Suggestions]
  D --> E[Asset Libraries]
  E --> F[Module Management]
  F --> G[Essential Modules]

Twig Overview

Twig is a template engine written in PHP and used by the Symfony framework. Drupal adopted Twig starting with Drupal 8, replacing the old PHPtemplate system. Twig compiles templates to PHP classes for performance.

Key principles of Twig:

Twig separates template logic from PHP business logic. You cannot run arbitrary PHP code inside a Twig template. This makes templates safer and easier to maintain.

Twig automatically escapes all output unless explicitly marked as safe. This prevents XSS vulnerabilities by default.

Twig template files use the .html.twig extension and live in the theme's templates/ directory.

Basic Syntax

Twig has three main delimiters:

{{ variable }}         {# Outputs the value of a variable #}
{% block name %}       {# Executes a control structure or block #}
{# This is a comment #} {# Comments are stripped from output #}

Variables

Variables are accessed using dot notation:

<h1>{{ node.title.value }}</h1>
<p>{{ node.body.value }}</p>
<span>Author: {{ node.getOwner().getDisplayName() }}</span>

Control Structures

{# If statement #}
{% if node.isPublished() %}
  <p>This content is published.</p>
{% else %}
  <p>This content is not published.</p>
{% endif %}

{# For loop #}
<ul>
  {% for item in items %}
    <li>{{ item }}</li>
  {% else %}
    <li>No items found.</li>
  {% endfor %}
</ul>

{# Set variable #}
{% set custom_class = 'highlight' %}
<div class="{{ custom_class }}">Content</div>

Variables in Drupal

Drupal provides a rich set of variables to Twig templates.

Global Variables

  • site — site information (name, slogan, url)
  • user — current user object (uid, roles, display name)
  • front_page — the front page URL
  • language — current language object
  • logged_in — boolean, true if user is authenticated
  • is_admin — boolean, true if user has admin role
  • base_path — Drupal root path (usually /)
  • directory — theme directory path

Page Variables

Available in page.html.twig:

  • page.content — main content area
  • page.sidebar — sidebar region content
  • page.header — header region content
  • page.footer — footer region content
  • page.primary_menu — main navigation
  • page.breadcrumb — breadcrumb trail
  • page.title — page title
  • page.tabs — local task tabs (edit, view, etc.)

Node Variables

Available in node.html.twig:

  • node — the full node object
  • label — the node title (string)
  • content — rendered content array
  • date — node creation date
  • author_name — author display name
  • node_type — content type machine name
  • status — published status
  • in_preview — whether in preview mode
  • view_mode — current view mode (full, teaser, etc.)

Filters

Filters transform variable values. They are applied with the pipe | character.

{{ 'hello'|upper }}                     {# HELLO #}
{{ 'HELLO'|lower }}                     {# hello #}
{{ 'hello world'|capitalize }}           {# Hello world #}
{{ 'Hello World'|replace({'World': 'Drupal'}) }}  {# Hello Drupal #}

{{ node.body.value|striptags }}          {# Remove HTML tags #}
{{ node.body.value|striptags|slice(0, 150) ~ '...' }} {# Truncate to 150 chars #}

Drupal-Specific Filters

{# Translate string #}
{{ 'Submit'|t }}

{# Clean ID for HTML attributes #}
{{ 'My Custom ID'|clean_id }}
{# Output: my-custom-id #}

{# Remove specific keys from array #}
{{ content|without('comment', 'links') }}

{# Safe join of array values #}
{{ items|safe_join(', ') }}

{# Format date #}
{{ node.created.value|format_date('medium') }}
{{ node.created.value|date('Y-m-d') }}

{# Placeholder replacement #}
{{ 'Hello @name'|t({'@name': user.displayname}) }}

Chaining Filters

{{ node.title.value|striptags|upper|slice(0, 50) }}

Functions

Functions generate dynamic values in templates.

{# Generate internal path #}
<a href="{{ path('entity.node.canonical', {'node': node.id}) }}">{{ label }}</a>

{# Generate full URL #}
<a href="{{ url('entity.node.canonical', {'node': node.id}, {'absolute': true}) }}">{{ label }}</a>

{# Get active theme path #}
<img src="{{ active_theme_path() }}/images/logo.png" alt="Logo">

{# File URL from URI #}
<img src="{{ file_url(node.field_image.entity.uri.value) }}" alt="{{ node.field_image.alt }}">

{# Attach a library #}
{{ attach_library('mytheme/custom') }}

{# Link generation #}
{{ link('Read more', 'entity.node.canonical', {'node': node.id}, {'class': ['read-more']}) }}

Extends and Includes

Template inheritance is one of Twig's most powerful features.

Extends

{# templates/page.html.twig - base layout #}
<!DOCTYPE html>
<html>
<head>
  <title>{{ head_title }}</title>
  {{ stylesheets }}
</head>
<body>
  <header>{{ page.header }}</header>
  <main>{% block content %}{% endblock %}</main>
  <footer>{{ page.footer }}</footer>
  {{ scripts }}
</body>
</html>
{# templates/page--article.html.twig - extends the base #}
{% extends 'page.html.twig' %}

{% block content %}
  <article class="article">
    <h1>{{ node.title.value }}</h1>
    <div class="meta">
      {{ node.created.value|format_date('long') }}
    </div>
    <div class="body">
      {{ node.body.value }}
    </div>
  </article>
{% endblock %}

Includes

{# Include a reusable component #}
{{ include('@mytheme/templates/header.html.twig') }}

{# Include with variables #}
{{ include('@mytheme/templates/card.html.twig', {
  title: 'Card Title',
  body: 'Card body content'
}) }}

Blocks

Blocks define sections of content that child templates can override.

{# Base template with blocks #}
{% block page_title %}
  <h1>{{ title }}</h1>
{% endblock %}

{% block page_content %}
  {{ content }}
{% endblock %}

{% block page_sidebar %}
  {{ sidebar }}
{% endblock %}

A child template can override any block:

{% extends 'base.html.twig' %}

{% block page_title %}
  <h1 class="custom-title">{{ title }}</h1>
{% endblock %}

{# Omit page_sidebar — it uses the parent's default #}
{% block page_content %}
  {{ parent() }}  {# Include parent block content #}
  <section class="additional">Extra content</section>
{% endblock %}

Debug Output

Twig debugging shows which template is being used and available variables.

Enabling Twig Debug

# sites/default/services.yml
parameters:
  twig.config:
    debug: true
    cache: false  # Disable Twig cache during development

After enabling, reload the page and view the HTML source. You will see HTML comments showing which templates were used:

<!-- THEME HOOK: 'node' -->
<!-- FILE NAME SUGGESTIONS:
   * node--article--full.html.twig
   * node--article.html.twig
   * node.html.twig
-->
<!-- BEGIN OUTPUT from 'themes/custom/mytheme/templates/node.html.twig' -->

Dumping Variables

{# Dump all available variables (requires Devel module) #}
{{ kint() }}
{{ dpm() }}

{# Dump a specific variable #}
{{ kint(node) }}
{{ dpm(node.title.value) }}

Autoescape

Twig automatically escapes output as HTML by default. Use the raw filter to output unescaped HTML:

{# Autoescaped - safe but HTML is escaped #}
{{ node.body.value }}
{# Output: &lt;p&gt;Hello&lt;/p&gt; #}

{# Raw - outputs HTML as-is #}
{{ node.body.value|raw }}
{# Output: <p>Hello</p> #}

Always use |raw carefully. Only apply it to trusted content from administrators. User-generated content should never use |raw.

Drupal Twig Extensions

The twig_tweak module provides additional Twig functions and filters:

composer require drupal/twig_tweak
{# Load a view #}
{{ drupal_view('my_view', 'block_1', arg1, arg2) }}

{# Load entity content #}
{{ drupal_entity('node', 123, 'teaser') }}

{# Load a block #}
{{ drupal_block('system_branding_block') }}

{# Get a field from a specific entity #}
{{ drupal_field('body', 'node', 123) }}

{# Get a region #}
{{ drupal_region('sidebar') }}

{# Get a token value #}
{{ drupal_token('site:name') }}

Template Caching

Twig caches compiled templates to PHP classes. During development, disable the cache:

parameters:
  twig.config:
    cache: false

In production, Twig cache is enabled and improves performance. Clear the cache after template changes:

drush cr

The compiled templates are stored at sites/default/files/php/twig/.

Common Mistakes

  1. Not using autoescape: Disabling autoescape or applying |raw to untrusted content exposes the site to XSS attacks. Only use raw on content authored by trusted administrators.

  2. Forgetting |raw for rendered content: Some Drupal render arrays need |raw to render properly. Check whether the variable is already a string or a render array.

  3. Hardcoding URLs: Using absolute URLs like /node/123 instead of the path() function breaks when the URL alias changes. Always use Twig functions for URLs.

  4. Not clearing Twig cache: After modifying templates, the old cached version may still be served. Run drush cr to clear all caches.

  5. Putting business logic in templates: Twig is for presentation only. Complex logic belongs in preprocess functions or custom modules, not in {% if %} blocks in templates.

Practice Questions

  1. How does Twig's autoescape feature improve security compared to raw PHP templates?

  2. What is the difference between {% extends %} and {% include %} in Twig?

  3. How would you truncate a long text field to 200 characters and append an ellipsis using Twig filters?

  4. Challenge: Create a Twig template structure for a blog site. Write a base page.html.twig with header, content, and footer blocks. Create a node--article.html.twig that extends the base, overrides the content block, and displays the article title, author, date, body, and tags. Add a sidebar include that shows recent posts using the drupal_view() function from twig_tweak.

FAQ

What is Twig?

Twig is a template engine from the Symfony PHP framework. Drupal 8+ uses Twig for all theme templates. It features automatic escaping, template inheritance, filters, functions, and a clean syntax that separates logic from presentation.

How do I enable Twig debugging?

Edit sites/default/services.yml and set twig.config.debug: true and twig.config.cache: false. Clear the cache and reload the page. View HTML source to see template suggestions as HTML comments.

How do I truncate text in Twig?

Use the slice filter: {{ text|striptags|slice(0, 150) ~ '...' }}. This removes HTML tags, takes the first 150 characters, and appends an ellipsis.

What is the difference between {{ }} and {{ }}|raw?

By default, Twig escapes HTML characters in variables. The raw filter outputs the value without escaping. Use raw only for trusted content, never for user-generated input.

How do I clear Twig cache?

Run drush cr to clear all caches, including compiled Twig templates. During development, disable Twig cache in services.yml by setting twig.config.cache: false.

Mini Project

Goal: Create a custom Twig template for an article content type.

  1. Create templates/node--article.html.twig in your theme
  2. Display the article title wrapped in an <h1> with a link
  3. Show the author name and publication date using the format_date filter
  4. Render the body field with |raw to output HTML
  5. Display all taxonomy tags as a comma-separated list
  6. Add an "Estimated reading time" calculated from word count
  7. Enable Twig debugging and verify your template is being used
  8. Disable Twig cache in services.yml during development

What's Next

Now that you understand Twig templating, proceed to creating sub-themes to customize base themes. After that, explore template suggestions for fine-grained template control.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro