All rules

Sheath rule

blade-prefer-json-in-script

Echoes in JavaScript value positions should use @json(...) or Js::from(...); {{ }} HTML-entity encodes the data and corrupts it for JS.
Package
Core
Category
Blade
Default severity
warning by default
Auto-fix
Manual fix

#Why

{{ $data }} runs through e(), and e() speaks HTML: it turns " into &quot;, & into &amp;, < into &lt;. Inside a <script> those entities are never decoded. JavaScript reads them as literal characters:

$name = 'O"Brien & Sons';
<script>var name = {{ $name }};</script>
-> var name = O&quot;Brien &amp; Sons; <- not JS, not the data

Laravel ships the right tools: @json($data) and {{ Js::from($data) }} emit properly JS-escaped output (Js::from returns an Htmlable, which e() passes through untouched).

JSON-only script types need JSON text rather than a JavaScript expression. Use @json($data) there. Js::from() may emit a JSON.parse(...) expression, which is valid in executable JavaScript but is not itself JSON. Although Js::encode() produces JSON text, it returns a plain string, so wrapping it in {{ }} makes Blade entity-escape the quotes and corrupt the JSON.

The rule reports escaped Blade echoes anywhere inside executable script data. In a JavaScript value position it recommends direct JSON serialization. Inside a string literal, template literal, or comment it instead tells you to move the expression to a value position first: HTML escaping can corrupt a string and characters such as */ can terminate a comment. Script blocks whose type marks them as HTML templates (text/x-template, text/html) are exempt because entity encoding is correct in HTML content. JSON script blocks (type="application/json") are checked: entities corrupt them the same way. Raw echoes can expose untrusted content without escaping. See security-no-raw-echo.

#Examples

#Bad

<script>
var data = {{ $data }};
</script>
<script>
const config = {
items: {{ $items }},
};
</script>
<script type="application/json">
{"items": {{ $items }}}
</script>
<script type="application/ld+json">
{{ Js::from($schema) }}
</script>
<script>
var name = '{{ $name }}';
</script>
<script>
/* {{ $note }} */
</script>

#Good

<script>
var data = @json($data);
</script>
<script>
const config = {
items: {{ Js::from($items) }},
};
</script>
<script type="application/ld+json">
@json($schema)
</script>
{{-- HTML template blocks are HTML context, not JS --}}
<script type="text/x-template">
<p>{{ $name }}</p>
</script>

#Notes

  • The strict preset enables this check. Numeric output such as var count = {{ $count }} may happen to work, so projects using that pattern can otherwise receive many new findings.
  • Prefer @json($data) for a value dropped straight into JS, and {{ Js::from($data) }} when you want an expression form. Both produce output that is safe against </script> breakout as well.
  • In application/json, application/ld+json, importmap, and speculationrules blocks, use @json(...); those blocks are parsed as JSON and cannot contain JSON.parse(...) or HTML-escaped JSON text.
  • Echoes in ambiguous positions are not reported.
  • Standard JavaScript MIME essence strings, including legacy values, are recognized case-insensitively after trimming ASCII whitespace, as required by HTML's MIME-type processing.

#Related Rules