skip to content

In a Laravel Blade view, why does a value stored as Tom & Jerry show a literal & to visitors, and what does Blade::withoutDoubleEncoding() change?

level: middleimportance: nice to knowfreq 22%

answer

  1. the value was escaped twice
  2. e() second argument, doubleEncode
  3. default true: & becomes &
  4. echo format becomes e(%s, false)
  5. fix the data before the flag

basics

~20 s

Blade's e() double-encodes by default, so an entity already in the data becomes &amp; and shows as &. Blade::withoutDoubleEncoding() compiles {{ }} to e($x, false), leaving existing entities intact while still escaping < and >.

solid answer

~40 s

`{{ }}` calls `e($value)`, whose second parameter `$doubleEncode` defaults to `true`, so `htmlspecialchars` encodes the `&` of an existing entity: `&amp;` becomes `&amp;amp;` and the visitor sees `&amp;`. The real cause is usually that the value was escaped once already - by an input-sanitizing step before saving, by an import that stored entities, or by an explicit `e()` inside the echo. `Blade::withoutDoubleEncoding()`, called in a service provider's `boot()`, switches Blade's echo format to `e(%s, false)`: existing entities are left as they are, while `<`, `>` and quotes are still encoded. It affects only `{{ }}` echoes compiled after the call, not direct `e()` calls elsewhere, and it makes text that literally contains `&lt;` render as `<`.

code

php · 15 lines
php
<?php

namespace App\Providers;

use Illuminate\Support\Facades\Blade;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        // {{ }} now compiles to e($value, false)
        Blade::withoutDoubleEncoding();
    }
}

go deeper

for a junior

Recall that e() double-encodes by default, so data already containing entities shows them literally on the page.

for a middle

Explain the doubleEncode argument, the echo format e(%s, false) the switch installs, and its scope: only {{ }} and only newly compiled views.

for a senior

Trace the symptom to where data was escaped early, fix and migrate it, and weigh the global switch against its fidelity loss.

for a principal

Hold the line on escape-once-at-output as a team rule, so storage stays raw text and display quirks never push people toward raw echoes.

## The symptom A forum thread title is stored as `Tom &amp; Jerry`. The template prints `{{ $thread->title }}` and the visitor sees `Tom &amp; Jerry` instead of `Tom & Jerry`. Viewing the page source shows `Tom &amp;amp; Jerry`. ## What double encoding means An **HTML entity** is an escape sequence such as `&amp;` (for `&`) or `&lt;` (for `<`). **Double encoding** is escaping text that already contains entities, so the `&` that starts each entity is itself escaped. Laravel's `e($value, $doubleEncode = true)` helper passes that second argument straight to PHP's `htmlspecialchars`. With the default `true`: | Stored value | Output of e() | Visitor sees | |---|---|---| | `Tom & Jerry` | `Tom &amp; Jerry` | `Tom & Jerry` | | `Tom &amp; Jerry` | `Tom &amp;amp; Jerry` | `Tom &amp; Jerry` | | `<b>` | `&lt;b&gt;` | `<b>` | Double encoding is the **faithful** behaviour: whatever characters are in the data, the visitor sees exactly those characters. The page looks wrong only because the data is wrong. ## Where the extra escaping usually comes from 1. **Escaping on input.** A middleware or form hook runs `htmlspecialchars` on request data before saving - the "sanitize on arrival" habit. The database now holds HTML, and Blade escapes it again on output. 2. **Imported content.** A feed, a CMS export or a scraped source stores entities in plain-text fields. 3. **Manual escaping in the view.** `{{ e($title) }}` or `{{ htmlspecialchars($title) }}` escapes twice in one line. 4. **Translation or config strings** written with entities by hand. The durable fix is to stop the first escape: store raw text and escape once, at output. A data migration can unescape already-stored rows. ## What Blade::withoutDoubleEncoding() changes The Blade compiler keeps an **echo format** - by default `e(%s)` - that it wraps around every `{{ }}` expression when compiling a template. `Blade::withoutDoubleEncoding()` sets it to `e(%s, false)`; `Blade::withDoubleEncoding()` sets it to `e(%s, true)`. The docs place the call in `AppServiceProvider::boot()`. After the switch: - Existing entities in the data are left alone, so `Tom &amp; Jerry` renders as `Tom & Jerry`. - `<`, `>`, `"` and `'` are **still encoded**; this is not a way to print HTML, and it does not weaken XSS protection for element text. - Only `{{ }}` echoes use the echo format. `{!! !!}` is unaffected, and direct `e()` calls in PHP classes keep double encoding on. - The setting is baked into compiled views. Templates already compiled with the old format keep it until they are recompiled - when the template file changes or compiled views are cleared. ## The cost of turning it off Disabling double encoding is lossy. A member who writes a post about HTML and types `&lt;` literally now sees `<`, because the entity is passed through and the browser decodes it. On a developer forum that is a visible bug. Because the switch is global, it trades one class of display bug for another. Options, from most to least preferred: - Fix the source of the second escape and migrate existing data. - For a single legacy field, decode once where the data enters the view model, then escape normally. - Use the global switch only when the whole app legitimately stores entity-encoded text and no content needs literal entities. ## Diagnosing it quickly 1. View the page source, not the rendered page: `&amp;amp;` confirms double encoding. 2. Inspect the stored value: an `&amp;` already in the database means escaping happened before output. 3. Search the write path for `htmlspecialchars`, `e(` or a sanitizing middleware applied to request input. 4. Search the view for an `e()` inside `{{ }}`. 5. Only if the data legitimately contains entities, consider the switch. ## Reading it in an interview A good answer names the default (`$doubleEncode = true`), the mechanism (echo format `e(%s, false)`), the scope (only `{{ }}`, only newly compiled views), and above all that the symptom points at data escaped too early, not at Blade misbehaving.

  • Does withoutDoubleEncoding make {{ }} unsafe for user input?
    No. `htmlspecialchars` with `double_encode` off still encodes `<`, `>`, `&` that do not start an entity, and quotes; it only skips re-encoding existing entities. A stored `&lt;script&gt;` renders as visible text `<script>`, not as a tag. The cost is fidelity, not safety: literal entities in user text are displayed decoded.
  • You add the call and some pages still show &amp;amp;. Why?
    The echo format is applied when a template is compiled, and compiled views are cached. Templates compiled before the change keep `e($x)` until the source file changes or the compiled views are cleared. Another cause is an explicit `e()` or `htmlspecialchars` inside the echo, which the switch does not touch.

saying these in an interview costs you the question

  • Double encoding is a Blade bug that the switch fixes
  • withoutDoubleEncoding lets {{ }} print HTML tags as markup
  • The switch also changes e() calls made in PHP classes
  • Sanitizing input with htmlspecialchars before saving is good practice
  • Switching to {!! !!} is the right fix for a stray &amp;amp;