Blade: define a variable and output HTML without an XSS hole

Blade: define a variable and output HTML without an XSS hole
Quick answer

In a Blade view, declare a variable with @php $nom = '…'; @endphp, or pass it in from the controller with view('vue', compact('nom')). To display it, {{ $nom }} escapes the HTML and {!! $nom !!} inserts it as is. Only use the second form on trusted content: that is where XSS holes are created.

Two needs come up constantly in a Blade view: declaring a variable there, and deciding whether its content should be escaped or rendered as is. The second one is not a matter of comfort, it is the exact spot where an XSS hole gets introduced.

Published in the Web development track. This guide covers both: getting a variable into the view, declaring one on the spot, and choosing between {{ }} and {!! !!} with your eyes open. The examples were run on Laravel 13.30.1 with PHP 8.4.25.

Passing a variable from the controller

This is the normal route, and the one to favour: the view displays, the controller decides. The second argument to view() is an associative array whose keys each become a variable.

app/Http/Controllers/DashboardController.php
return view('dashboard', ['name' => 'Gekko']);
resources/views/dashboard.blade.php
<p>Bienvenue {{ $name }} !</p>

To display a date coming from a model, see handling dates with Carbon.

For several values that already live in PHP variables, compact() saves repeating the names:

php
$name    = 'Gekko';
$reseau  = 'gekkode';

return view('dashboard', compact('name', 'reseau'));

with() lets you add one along the way, which is handy when the value is computed at the last moment:

php
return view('dashboard', compact('name'))->with('extra', 'valeur ajoutée');

If a variable may be missing, give it a fallback value in the view rather than adding a condition:

php
{{ $inconnu ?? 'valeur par défaut' }}

Declaring a variable directly in the view

When the value only exists for display, the @php directive lets you declare it on the spot:

resources/views/dashboard.blade.php
@php
    $nom = 'Gekko';
    $classe = $task->active ? 'is-active' : 'is-done';
@endphp

<p class="{{ $classe }}">Bonjour {{ $nom }}</p>

Plain PHP tags work too, but @php reads better and stays consistent with the rest of the template.

Two directives cover the neighbouring cases without going through a variable at all. @class builds a class attribute from a set of conditions:

php
<div @class(['actif' => $task->active, 'masque' => $task->hidden])></div>

<!-- rendu quand active vaut true et hidden false -->
<div class="actif"></div>

And @once guarantees that a block is rendered only once, even if the view is included several times on the page:

php
@once
    <script src="/js/carte.js" defer></script>
@endonce
Where reasonable stops

@php is fine for a display value. As soon as a database query, a business calculation or a transformation loop is involved, the code belongs in the controller, in a Blade component or in a model accessor. A view that queries the database generates N+1 queries that are invisible from the controller.

Outputting HTML: {{ }} versus {!! !!}

Blade compiles {{ $variable }} into a call to the e() function, which escapes special characters. You can see it in the compiled view, under storage/framework/views:

php
<?php echo e($nom); ?>

The consequence: if the variable contains tags, they are printed as literal text instead of being interpreted.

php
@php $html = '<strong>gras</strong>'; @endphp

{{ $html }}     {{-- affiche : &lt;strong&gt;gras&lt;/strong&gt; --}}
{!! $html !!}   {{-- affiche : gras, en gras --}}

The {!! !!} syntax inserts the value with no transformation at all. That is what you want for an article body written in HTML, and it is also the way in for a script injection when the value comes from a user.

The rule not to work around

Only use {!! !!} on HTML whose origin you control, or that has been sanitised beforehand. With the value <script>alert(1)</script>, {{ }} prints the text while {!! !!} runs the script in the visitor’s browser. Both behaviours were verified.

For content coming from a form, which you will have taken care to protect against automated submissions, sanitise before you display. A library such as HTML Purifier applies an allowlist of tags, failing that, store Markdown and convert it at render time, which leaves you in control of the tags produced.

The special cases worth knowing

To pass data to JavaScript, @json produces a valid, escaped literal:

php
<script>
    const config = @json(['id' => $task->id, 'titre' => $task->title]);
</script>

To print braces without Blade interpreting them, in a template meant for a JavaScript framework for instance, @verbatim neutralises the whole block:

php
@verbatim
    <div id="app">{{ message }}</div>
@endverbatim

For a single expression, prefixing it with an at sign is enough: @{{ message }}.

Do not call e() inside double braces

{{ e($valeur) }} escapes twice: <b> becomes &amp;lt;b&amp;gt; and shows up literally on screen. {{ }} already does it. The e() function is only useful outside Blade, or inside a {!! !!} on one specific portion.

Common errors

{!! !!} on a user-supplied value The string <script>alert(1)</script> really does run in the visitor’s browser. Sanitise the HTML first, or store Markdown.
{{ e($valeur) }} escapes twice Double braces already call e(). The result shows the entities on screen instead of the text.
A database query inside @php The view fires queries the controller never sees, which produces invisible N+1s. Move the logic into the controller or a component.
Forgetting the fallback on an optional variable {{ $var ?? 'défaut' }} avoids an error when the controller does not pass it in every case.

BladeLaravelPHP

Damien Flandrin Web developer since 2010, creator of Gekkode and Email Impact. Every article is tested on a real project before publication. Contact
Newsletter

New tests, tutorials and projects, by e-mail.

Reproducible tests, versioned code, dated results. Never any spam.