Twig `|raw` and a Broken Escaped Link
A template outputs a field value with {{ content.field_cta }}. The field is a link field rendered as a render array. A developer, seeing HTML tags printed as text in a teaser view mode, changes it to {{ content.field_cta|raw }}. The link now renders but a & in the URL query string breaks the markup, and a content editor reports that a user-entered onclick attribute now executes. The field is also used in a card component included via {% include %}. The team wants links to work in all view modes without reintroducing XSS or broken escaping.
1. Diagnose why `|raw` was the wrong fix and what actually caused the escaped output.
Read the worked answer
{{ content.field_cta }} prints a render array, and Twig's autoescape will escape it as a string if it is not rendered through Drupal's render pipeline. The correct behavior is that content variables are already render arrays; printing them should render them. If tags appear as text, the likely cause is that the variable was converted to a string too early (e.g., |render then |escape, or the field was accessed via a raw value like node.field_cta.value instead of content.field_cta).
|raw disables autoescaping for the whole output:
- It lets user-supplied attributes (
onclick) through, creating an XSS vector. - It bypasses Drupal's render/attribute sanitization, so
&in URLs is not properly encoded.
The fix is not to disable escaping but to render the render array correctly and let Drupal's renderer and Twig autoescape handle attributes.
2. Give the corrected approach for both the direct field and the included component.
Read the worked answer
Direct field:
- Use
{{ content.field_cta }}and ensure it is a render array. If you need a string, use{{ content.field_cta|render }}and never wrap it in|raw. - Do not access raw
.valuefor link fields; use the render array so URL and attributes are processed by the link formatter.
Included component:
- Pass the render array, not a string:
{% include 'card.html.twig' with { cta: content.field_cta } only %}and inside use{{ cta }}. - If the component needs a URL string, extract via the processed value (e.g.,
content.field_cta[0]['#url']) and let Twig escape it in thehrefattribute.
Reasoning: keep autoescape on, render render arrays through Drupal, and let the link formatter handle encoding and attribute filtering. This fixes the & encoding and closes the XSS hole.