Question

Render HTML Strings Safely in Laravel Blade

phphtmllaravellaravel-blade

Question

A Laravel view receives an HTML string such as:

$text = '<p><strong>Lorem</strong> ipsum dolor <img src="images/test.jpg"></p>';

When I display it in a Blade template with:

{{ $text }}

Blade shows the HTML tags as plain text instead of rendering them in the browser. How can I render this HTML correctly in Blade, while handling it safely? PHP's echo() renders the HTML as expected.

Short Answer

Blade escapes output inside {{ }} to protect your application from cross-site scripting (XSS). You can render a trusted or sanitized HTML string with {!! !!}, but you should never use unescaped output directly with content supplied by users.

Concept

Blade has two main output styles:

{{ $value }}

and:

{!! $value !!}

{{ $value }} is escaped output. Blade converts HTML-sensitive characters into safe text representations. For example, <strong> becomes &lt;strong&gt;, so the browser displays the tags rather than interpreting them as markup.

This default is intentional. If an attacker can control a value and it contains a script tag or a malicious event handler, rendering it directly could let the attacker run JavaScript in another user's browser. This is called cross-site scripting (XSS).

{!! $value !!} is unescaped output. Blade sends the string to the browser as HTML, so valid tags can be rendered.

Use unescaped output only when the HTML is one of the following:

  • Written and controlled by your application.
  • Generated by a trusted server-side library.
  • Sanitized with an allowlist before it reaches the view.

In most views, keep using {{ }}. It is the safe default.

Mental Model

Think of {{ }} as showing the contents of a note behind glass. You can read every character, but nothing on the note can affect the page.

Think of {!! !!} as taking the note out from behind the glass and following its instructions. If the note contains harmless HTML, that is useful. If an untrusted person wrote the note, it could contain harmful instructions.

So the rule is simple: escape by default; render raw HTML only after establishing trust or sanitizing it.

Take Quiz

Syntax and Examples

Use double curly braces for normal text:

<p>{{ $username }}</p>

If $username is <strong>Ada</strong>, the page displays the literal text <strong>Ada</strong>. This is usually what you want for names, comments, search terms, and other user input.

Use Blade's unescaped syntax for trusted HTML:

{!! $text !!}

Given this controller data:

$text = '<p><strong>Lorem</strong> ipsum dolor <img src="/images/test.jpg" alt="Test image"></p>';

return view('article', compact('text'));

this Blade template renders the paragraph, bold text, and image:

<article>
    {!! $text !!}
</article>

A safer pattern is to use a clear variable name that communicates why raw rendering is permitted:

$trustedHtml = '<p><strong>Release notes</strong></p>';

return view('release-notes', compact('trustedHtml'));
{!! $trustedHtml !!}

Do not assume a value is trusted merely because it came from your database. A database can store user-provided content too.

Step by Step Execution

Consider this value:

$text = '<p><strong>Lorem</strong> ipsum</p>';

Escaped output

{{ $text }}
  1. Blade receives the value containing <p> and <strong> tags.
  2. Blade escapes HTML-sensitive characters.
  3. The generated response effectively contains text such as:
&lt;p&gt;&lt;strong&gt;Lorem&lt;/strong&gt; ipsum&lt;/p&gt;
  1. The browser treats that content as text and displays the HTML tags visibly.

Unescaped output

{!! $text !!}
  1. Blade receives the same value.
  2. Blade does not escape its HTML characters.
  3. The generated response contains:
<p><strong>Lorem</strong> ipsum</p>
  1. The browser parses the tags and renders a paragraph with Lorem in bold.

The second version is correct only if the string is safe to render.

Real World Use Cases

Rendering HTML intentionally is useful when an application stores or generates controlled markup, for example:

  • CMS articles: An editor writes formatted article content that has been sanitized before storage.
  • Email or notification previews: The application generates a trusted HTML template.
  • Documentation pages: Markdown is converted to HTML by a trusted renderer, then sanitized if contributors can edit it.
  • Rich text fields: A WYSIWYG editor produces HTML that is filtered to permit only supported tags and attributes.
  • Reusable content blocks: Administrators manage approved banners, help text, or legal notices with limited formatting.

For plain values such as product names, profile names, error messages, query parameters, and comments, use {{ }} instead.

Take Quiz

Real Codebase Usage

In production Laravel applications, developers usually make the trust boundary explicit.

Keep normal output escaped

<h1>{{ $post->title }}</h1>
<p>Posted by {{ $post->author->name }}</p>

Even if these values are stored in the database, they may have originated from a form or an API.

Sanitize rich text before rendering

A common flow for a rich-text body field is:

  1. Accept HTML from an editor.
  2. Sanitize it using an allowlist of permitted elements and attributes.
  3. Store the sanitized HTML.
  4. Render the sanitized value with {!! !!}.
<article class="prose">
    {!! $post->body_html !!}
</article>

Prefer structured data where possible

If the only requirement is bold text or a link, consider storing structured fields rather than arbitrary HTML. For example, store a title, description, and url, then let the Blade template create the markup. This reduces security risk and makes layouts easier to change.

Use reusable components for known markup

For application-controlled messages, components often avoid raw HTML entirely:

<x-alert type="success">
    Your profile was updated.
</x-alert>

The component owns the HTML structure, while content remains escaped by default.

Common Mistakes

Using {!! !!} with request input

This is unsafe:

{!! request('comment') !!}

An attacker could submit HTML containing scripts or dangerous attributes. Keep it escaped instead:

{{ request('comment') }}

Or sanitize rich text before rendering it.

Trusting database content automatically

This can also be unsafe:

{!! $post->body !!}

The database is storage, not a guarantee of safety. Check where body came from. If users, imports, webhooks, or external APIs can populate it, sanitize it first.

Expecting {{ }} to render HTML

This is not a Blade error:

{{ '<em>Hello</em>' }}

It is intentional escaping. Use {!! !!} only for safe HTML.

Sanitizing by removing only <script> tags

Removing script tags alone is not a complete XSS defense. Dangerous markup can appear in event attributes, URLs, SVG, and other places. Use a maintained HTML sanitizer with an allowlist rather than writing a few string replacements.

Forgetting asset paths

For an application image, prefer a generated URL:

$text = '<img src="' . asset('images/test.jpg') . '" alt="Test image">';

Or, better yet, keep the image outside the HTML string and render it normally in Blade when the markup is known.

Comparisons

ApproachWhat happensBest useSecurity note
{{ $value }}Escapes HTML and shows tags as textNearly all dynamic textSafe default for untrusted content
{!! $value !!}Outputs HTML without escapingTrusted or sanitized rich HTMLUnsafe for raw user input
PHP echo $valueOutputs the string directlyLegacy/plain PHP templatesDoes not provide Blade's automatic escaping
strip_tags($value)Removes tags rather than rendering themDisplaying plain-text excerptsNot a full HTML sanitization strategy
Sanitizer + {!! $value !!}Allows only approved HTML, then renders itUser-authored rich textAppropriate when configured carefully

The key difference is not whether the value came from PHP or a database. The key question is whether the value is safe HTML at the moment it is rendered.

Take Quiz

Cheat Sheet

{{-- Safe default: HTML is escaped --}}
{{ $value }}

{{-- Render HTML only when it is trusted or sanitized --}}
{!! $trustedHtml !!}
  • Use {{ }} for names, titles, form values, URLs displayed as text, comments, and API data.
  • Use {!! !!} only for application-controlled or sanitized HTML.
  • Treat database values as untrusted unless their source and sanitization process are known.
  • Do not render request() data, user comments, or external API HTML with {!! !!}.
  • Use a maintained allowlist-based HTML sanitizer for rich text from users.
  • Add alt text to images and use reliable URLs such as asset('images/test.jpg') where appropriate.

FAQ

Why does Blade show my HTML tags as text?

Because {{ $value }} escapes HTML by design. It prevents a dynamic value from being interpreted as browser markup.

How do I render an HTML string in Laravel Blade?

Use {!! $html !!} only when $html is trusted or has been sanitized.

Is {!! !!} safe in Laravel?

It is safe only when the content is controlled by your application or cleaned by a suitable HTML sanitizer. It is unsafe for raw user input.

Why does PHP echo render HTML while Blade does not?

echo directly outputs the string. Blade's {{ }} adds automatic HTML escaping as a security feature.

Can I use {{ }} for HTML stored in the database?

Yes, but it will display as text. To render database HTML, first ensure it is trusted or sanitized, then use {!! !!}.

Does strip_tags() make user HTML safe to render?

No. strip_tags() removes tags and is useful for plain-text output, but it is not a complete solution for safely allowing selected HTML.

Should I store HTML in the database?

It can be appropriate for rich-text content. Store sanitized HTML or store a source format such as Markdown and convert and sanitize it before rendering.

Take Quiz

Mini Project

Description

Build a small announcement view that displays a normal user-entered title safely and displays an application-controlled formatted announcement as HTML. This demonstrates when escaped and unescaped Blade output should be used.

Goal

Render trusted announcement markup while ensuring a user-provided title cannot inject HTML.

Requirements

Requirement 1

Take Quiz

Keep learning

Related questions

View more