skip to content

In a Blade view, what is the difference between {{ $value }} and {!! $value !!}, and when is the raw form acceptable?

level: juniorimportance: must knowfreq 82%

answer

  1. two echo shapes, one compiled file
  2. curly form wraps the e() helper
  3. htmlspecialchars, ENT_QUOTES, UTF-8
  4. raw form: plain echo, no escaping
  5. raw only for HTML your code built

basics

~20 s

Blade compiles {{ $value }} to an echo through the e() helper, which runs htmlspecialchars so markup prints as text; {!! $value !!} echoes the value untouched, so it is safe only for HTML your own code built or sanitized.

solid answer

~40 s

Blade compiles `{{ $value }}` into `<?php echo e($value); ?>`. The `e()` helper calls `htmlspecialchars` with `ENT_QUOTES | ENT_SUBSTITUTE` and UTF-8, so `<`, `>`, `&`, `"` and `'` become entities and a user's `<script>` tag shows up as harmless text. `{!! $value !!}` compiles to a bare `<?php echo $value; ?>` with no escaping at all. The raw form is acceptable only for HTML that your own code generated or that passed through a real sanitizer, such as a Markdown field rendered with safe options; never for request input or database columns users can write. One nuance matters in review: `e()` returns an `Htmlable` object's `toHtml()` unescaped, so `{{ }}` is only as safe as the objects you hand it.

code

html · 9 lines
html
{{-- resources/views/members/show.blade.php --}}
<h2>{{ $member->display_name }}</h2>

{{-- HTML built by our own sanitizing renderer --}}
<div class="bio">{!! $renderedBio !!}</div>

{{-- compiled output, storage/framework/views --}}
{{-- <h2><?php echo e($member->display_name); ?></h2> --}}
{{-- <div class="bio"><?php echo $renderedBio; ?></div> --}}

go deeper

for a junior

Recall that {{ }} escapes through e() and {!! !!} does not, and default to the curly form for anything a user typed.

for a middle

Explain what e() does: htmlspecialchars with ENT_QUOTES and UTF-8, double encoding on by default, and Htmlable objects returned unescaped.

for a senior

Show how you audit a codebase for raw output, including HtmlString and toHtmlString() hiding inside the curly form, and where each exception is vouched for.

for a principal

Frame raw output as a team rule: one sanitizing path per rich-text field, reviewed exceptions, and grep-able markers so escaping stays the default as the app grows.

## What each form compiles into A **Blade view** is a template file (`resources/views/*.blade.php`) that Laravel compiles into plain PHP and caches under `storage/framework/views`. Blade has two echo forms, and the difference is visible in that compiled file: | Blade source | Compiled PHP | Escaping | |---|---|---| | `{{ $bio }}` | `<?php echo e($bio); ?>` | HTML-escaped | | `{!! $bio !!}` | `<?php echo $bio; ?>` | none | The curly form is the default you reach for; the raw form is an explicit opt-out. **Cross-site scripting (XSS)** is the attack both forms are about: if text a user controls is written into the page as markup, the browser runs whatever script that markup carries in every visitor's session. ## What the e() helper actually does `e()` is a global helper defined in `Illuminate/Support/helpers.php`. Its behaviour, in order: - A `DeferringDisplayableValue` is first resolved to its displayable value. - An object implementing `Illuminate\Contracts\Support\Htmlable` returns `toHtml()` **without escaping** - `HtmlString`, `Js`, rendered views and component slots are all `Htmlable`. - A backed enum is replaced by its `->value`. - Everything else goes to `htmlspecialchars($value ?? '', ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8', $doubleEncode)`; `null` becomes an empty string. `ENT_QUOTES` means both double and single quotes are encoded (`&quot;` and `&#039;`), which is what keeps a value from breaking out of a quoted HTML attribute. `ENT_SUBSTITUTE` replaces invalid UTF-8 sequences with a replacement character instead of returning an empty string. The last argument, **double encoding**, defaults to `true`, so an existing `&amp;` becomes `&amp;amp;`. The result: `{{ '<b>hi</b>' }}` sends `&lt;b&gt;hi&lt;/b&gt;` to the browser, and the visitor sees the literal text `<b>hi</b>`. Nothing is stripped; the tags are **encoded**, so they display instead of executing. ## When the raw form is legitimate `{!! !!}` exists because some values really are HTML. The acceptable sources share one property: **your code decided what markup they contain**. 1. HTML your application generated from trusted inputs, for example a helper that builds a small badge from constants. 2. User-authored rich text that went through a real sanitizer or a Markdown converter configured to drop raw HTML, at a single, reviewed point in the code. 3. Content from a trusted internal source, such as a CMS field only editors can write, when the team has explicitly accepted that trust. Everything else - request input, profile fields, comment bodies, anything a user can reach through a form, an import or an API - belongs in `{{ }}`. Input validation rules such as `string` or `max:500` do not change that: they constrain shape and length, not which characters are dangerous in HTML. ## The objects that bypass escaping inside {{ }} Because `e()` trusts `Htmlable`, a raw-output decision can hide inside the curly form. `{{ new HtmlString($request->input('bio')) }}` renders live markup even though the template looks safe. Treat constructing an `HtmlString` from user data with the same suspicion as `{!! !!}`. Blade's `Blade::stringable()` echo handlers are the opposite case: they format an object you do not own before echoing, and in the curly form their result still passes through `e()`. ## Echoing values of different types The curly form is not only for strings. Its behaviour by input type: | Value passed to `{{ }}` | What is printed | |---|---| | `null` | an empty string | | `int`, `float` | the number, converted to a string | | a backed enum case | the case's `value`, escaped | | an object with `__toString` | the string, escaped | | an `Htmlable` object | its `toHtml()`, not escaped | A literal `true` prints `1` and `false` prints nothing, exactly as PHP's own string conversion does, which is why boolean flags belong in a directive rather than an echo. An array cannot be echoed at all; convert it to a string or pass it to a script through `Js::from`. ## Reviewing a template A practical review routine for escaping: 1. Search for `{!!` and ask, for each hit, which code produced the markup. 2. Search for `new HtmlString(` and `toHtmlString()` and ask the same question. 3. Check values that land in attributes, URLs, event handlers and `<script>` blocks separately; HTML-escaping is correct for element text and quoted attributes, not for every position in a page. 4. Confirm raw output is not used just to render an `&` or an apostrophe correctly - that is a double-encoding problem with a different fix. The rule that survives all of this is short: **escape by default with `{{ }}`, and make every exception name the code that vouches for its HTML.**

  • Does {{ }} protect a value echoed inside a <script> block?
    No. A script element's content is not entity-decoded by the browser, so e() output such as `&quot;` stays literally in the JavaScript and breaks it, while JavaScript-significant characters get no special treatment. Script data should go through `Js::from()` or the `@js` directive, which build a JavaScript expression escaped for that position.
  • What happens when you echo an object in {{ }}?
    `e()` checks the type first. An `Htmlable` returns its `toHtml()` unescaped, a backed enum is replaced by its value, and an object with `__toString` is converted to a string and escaped. For a class you do not control, `Blade::stringable()` registers a formatter; in the curly form its return value still passes through `e()`.
  • If you sanitize a column when it is saved, is {!! !!} on that column safe?
    Only while every write path runs the same sanitizer and its rules never loosen. Seeders, imports, admin tools and API endpoints can all bypass a save-time hook. Many teams keep the source text, render and sanitize at output, and cache the result, so the raw echo always sees HTML from one reviewed path.

saying these in an interview costs you the question

  • {!! !!} is just a faster way to print strings
  • {{ }} strips HTML tags out of the value
  • Escaping input once on save makes {!! !!} safe everywhere
  • {{ }} makes a value safe in any position, even inside script blocks
  • Validation rules like string and max:500 prevent XSS, so raw output is fine