Twig Integration
When to Use
Render arrays are designed to work seamlessly with Twig -- pass them as variables and Twig renders them automatically. This is the standard pattern for theming in Drupal.
How Render Arrays Work in Twig
Automatic rendering: Any render array passed to a Twig template is automatically rendered when printed.
// In a controller or preprocess function
$variables['sidebar'] = [
'#type' => 'container',
'#attributes' => ['class' => ['sidebar']],
'content' => ['#markup' => '<p>Sidebar content</p>'],
];
{# In template.html.twig #}
<div class="layout">
{{ sidebar }}
{# Drupal automatically calls renderer on sidebar render array #}
</div>
Reference: core/lib/Drupal/Core/Template/TwigExtension.php -- Twig filters/functions for Drupal
Pattern: Passing Render Arrays to Templates
In hook_theme() implementation:
function mymodule_theme($existing, $type, $theme, $path) {
return [
'mymodule_card' => [
'variables' => [
'title' => NULL,
'body' => NULL, // Can be string OR render array
'footer' => NULL,
],
'template' => 'mymodule-card',
],
];
}
In preprocess function:
function mymodule_preprocess_mymodule_card(&$variables) {
// Body can be a render array
$variables['body'] = [
'#theme' => 'item_list',
'#items' => ['Item 1', 'Item 2'],
];
// Footer can be a render array
$variables['footer'] = [
'#type' => 'link',
'#title' => t('Read more'),
'#url' => Url::fromRoute('mymodule.page'),
];
}
In templates/mymodule-card.html.twig:
<div class="card">
<h2>{{ title }}</h2>
<div class="card-body">
{{ body }}
{# Automatically renders item_list #}
</div>
<div class="card-footer">
{{ footer }}
{# Automatically renders link #}
</div>
</div>
Pattern: Conditional Rendering in Twig
{# Check if render array has visible children #}
{% if content.field_image|render|striptags|trim %}
<div class="image-wrapper">
{{ content.field_image }}
</div>
{% endif %}
{# Better: Use render element's #access #}
{# In preprocess: #}
{# $variables['content']['field_image']['#access'] = !$node->get('field_image')->isEmpty(); #}
{% if content.field_image %}
{{ content.field_image }}
{% endif %}
Pattern: Altering Render Arrays in Twig (Anti-Pattern)
DON'T DO THIS:
{# WRONG: Cannot modify render arrays in Twig #}
{% set content.field_name['#prefix'] = '<div class="wrapper">' %}
{{ content.field_name }}
DO THIS INSTEAD:
// In preprocess function
function mymodule_preprocess_node(&$variables) {
$variables['content']['field_name']['#prefix'] = '<div class="wrapper">';
}
Why: Twig wraps all arrays in ProtectedRenderArray -- they're read-only in templates. Modification must happen in preprocess.
Reference: Render arrays can no longer be created or modified in Twig
Useful Twig Filters for Render Arrays
| Filter | Purpose | Example |
|---|---|---|
render |
Force rendering (usually automatic) | {{ content.field_name\|render }} |
without |
Render array excluding certain keys | {{ content\|without('field_image', 'links') }} |
striptags |
Remove HTML tags from rendered output | {{ content.body\|render\|striptags }} |
Common Mistakes
- Calling
render()in preprocess functions -- Pass render arrays to Twig, let Twig render them automatically - Trying to modify render arrays in Twig -- Arrays are read-only in templates; use preprocess instead
- Using
{{ content|render }}unnecessarily --{{ content }}auto-renders; explicit|renderonly needed in specific edge cases - Not understanding
|withoutcreates a copy -- Original variable unchanged; use result in new variable if needed - Rendering fields individually when
contentalready has them --{{ content }}renders all fields; cherry-pick only if needed
See Also
- Preprocess Functions for modifying variables before Twig
- Reference: Twig best practices - preprocess functions and templates
- Reference: Drupal at your Fingertips: Twig