Escape Tokens with Markup in Drupal 8

If you’ve ever tried to use markup in custom tokens, you’ve probably run into the situation where you needed to display raw HTML instead of escaped HTML which isn’t easy to do out of the box and doesn’t look like it’s going to get any better any time soon.

Instead of the expected output of

<p>some text in a paragraph</p>

You’ll get this lovely piece of HTML

&lt;p&gt;some text in a paragraph&lt;/p&gt;

Which is fine unless you were expecting raw HTML.

So, how can we fix this?

How Tokens Work

To figure out how to fix this problem, we first need to figure out what the problem is. On line 204 of core/lib/Drupal/Core/Utility/Token.php we see this

$replacements[$token] = $value instanceof MarkupInterface ? $value : new HtmlEscapedText($value);

Generally you’re sending a string to Drupal::token()->replace() which will send your replacement through HtmlEscapedText and, as the name implies, will escape the value resulting in some ugly escaped HTML instead of our pretty raw HTML.

This same line also tells us how we might get around this though.


According to line 204 of Token.php, if we send an instance of MarkupInterface as a replacement value for a token, it won’t be escaped.

The simplest way I could find to get an instance of MarkupInterface was to use Markup::create which takes the value you pass it, and sets that to the string value of the markup, and will return your raw string.

So all we need to do in our tokens hook is something like this

function mymodule_tokens($type, $tokens, array $data, array $options, \Drupal\Core\Render\BubbleableMetadata $bubbleable_metadata) {
    foreach ($tokens as $token => $original) {
        $replacements[$original] = Markup::create($replacementValue);
    return $replacements;

Doing this will ensure that your token markup is not escaped and you can stop banging your head against the desk like I have been.

Leave a Reply

Your email address will not be published. Required fields are marked *