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 <strong>, 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.
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 }}
- Blade receives the value containing
<p>and<strong>tags. - Blade escapes HTML-sensitive characters.
- The generated response effectively contains text such as:
<p><strong>Lorem</strong> ipsum</p>
- The browser treats that content as text and displays the HTML tags visibly.
Unescaped output
{!! $text !!}
- Blade receives the same value.
- Blade does not escape its HTML characters.
- The generated response contains:
<p><strong>Lorem</strong> ipsum</p>
- 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.
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:
- Accept HTML from an editor.
- Sanitize it using an allowlist of permitted elements and attributes.
- Store the sanitized HTML.
- 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
| Approach | What happens | Best use | Security note |
|---|---|---|---|
{{ $value }} | Escapes HTML and shows tags as text | Nearly all dynamic text | Safe default for untrusted content |
{!! $value !!} | Outputs HTML without escaping | Trusted or sanitized rich HTML | Unsafe for raw user input |
PHP echo $value | Outputs the string directly | Legacy/plain PHP templates | Does not provide Blade's automatic escaping |
strip_tags($value) | Removes tags rather than rendering them | Displaying plain-text excerpts | Not a full HTML sanitization strategy |
Sanitizer + {!! $value !!} | Allows only approved HTML, then renders it | User-authored rich text | Appropriate 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.
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
alttext to images and use reliable URLs such asasset('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.
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
Keep learning
Related questions
Are PDO Prepared Statements Enough to Prevent SQL Injection in PHP?
Learn how PDO prepared statements prevent SQL injection in PHP, what they protect, and the mistakes that still leave MySQL apps vulnerable.
Can You Bind an Array to an IN Clause in PHP PDO?
Learn how PDO handles placeholders in IN() clauses, why arrays cannot be bound directly, and the safe PHP pattern to build dynamic queries.
Choosing the Right MySQL Collation for PHP and UTF-8
Learn how MySQL character sets and collations work with PHP, and how to choose a practical UTF-8 setup for web applications.