> ## Content Index
> Fetch the complete content index at: https://securinglaravel.com/llms.txt
> Use this file to discover other available public pages before exploring further.

# Security Tip: Don't Use nl2br()!
- URL: https://securinglaravel.com/security-tip-dont-use-nl2br/
- Published: 2024-01-06T00:00:39.000Z
- Updated: 2025-08-25T00:19:05.000Z
- Description: [Tip#67] As useful as it sounds, nl2br() can potentially leave you open to Cross-Site Scripting (XSS) vulnerabilities... you should reach for CSS instead!
- Author: Stephen Rees-Carter
- Tags: Security Tips, CSS, XSS

PHP is full of interesting and useful functions for a variety of use cases, and one such function which gets used **a lot** is `nl2br()`! 

If you’re not familiar, `nl2br()` adds `<br>` characters into strings where there are newline characters (`\n`, `\n\r`, and `\r`).

For example:

```
> nl2br("One\nTwo\nThree");
= """
  One<br />\n
  Two<br />\n
  Three
  """
```

The most common use case I see for `nl2br()` is to display user-submitted inputs from `<textarea>` fields. It translates the newline characters the user inputted into actual newlines (via `<br>` tags), which are displayed on the page.

However, the risk is that by using `nl2br()`, you’re needing to output the user input unescaped on the page, which can introduce [Cross-Site Scripting (XSS) vulnerabilities](https://securinglaravel.com/t/xss).

For example, if we use the following user input in next few examples:

```
One
Two
<img src=x onerror="alert('Boom!')">
Three
```

You can’t use use `nl2br()` inside blade escaping tags:

```
{{ nl2br($input) }}
```

As it’ll escape the `<br>` tags too:

![](https://storage.ghost.io/c/d9/0d/d90de76f-6031-4e2c-85b8-3447a38c4992/content/images/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https-3a-2f-2fsubstack-post-media.s3.amazonaws.com-2fpublic-2fimages-2f3d477572-3886-4af3-8d7f-509dfb8e6064_508x55.png)

All of the content on a single line, with no line breaks and the HTML escaped and visible.

But when you unescape the output:

```
{!! nl2br($input) !!}
```

You get this:

![](https://storage.ghost.io/c/d9/0d/d90de76f-6031-4e2c-85b8-3447a38c4992/content/images/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https-3a-2f-2fsubstack-post-media.s3.amazonaws.com-2fpublic-2fimages-2f0bba6d41-640e-4dcc-bff6-abc96f187abf_660x201.png)

The text on new lines, but the XSS payload has been triggered… 😔

One way to work around this is to escape inside the `nl2br()`:

```
{!! nl2br(e($input)) !!}
```

![](https://storage.ghost.io/c/d9/0d/d90de76f-6031-4e2c-85b8-3447a38c4992/content/images/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https-3a-2f-2fsubstack-post-media.s3.amazonaws.com-2fpublic-2fimages-2fd3a5ce50-5d9a-4efd-bed0-a672ece3615f_324x170.png)

Escaped output (XSS payload is visible) with newlines.

It works, but it looks pretty ugly and is very easy to forget!

A much better way to solve it is to use the CSS rule `white-space: pre-line;` on the escaped input, without making any modifications to the input:

```
<div style="white-space: pre-line;">{{ $input }}</div>
```

Or if you use [Tailwind CSS](https://tailwindcss.com/docs/whitespace?ref=securinglaravel.com):

```
<div class="whitespace-pre-line">{{ $input }}</div>
```

![](https://storage.ghost.io/c/d9/0d/d90de76f-6031-4e2c-85b8-3447a38c4992/content/images/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https-3a-2f-2fsubstack-post-media.s3.amazonaws.com-2fpublic-2fimages-2f5b84f797-1423-48ab-b284-e17b44d78829_333x161.png)

Escaped output (XSS payload is visible) with newlines.

This approach provides an incredibly clean solution that takes advantage of [standard output escaping](https://securinglaravel.com/security-tip-escape-output-with-e/) to prevent XSS from sneaking in, while still preserving the inputted newlines in the way the user intended. Also, you’re unlikely to forget to escape the output as `{{ ... }}` should be your default already.

## Stay One Step Ahead of Security Threats!

Join **Securing Laravel* and arm yourself with the knowledge to protect your applications. Get [weekly security tips](https://securinglaravel.com/tag/tips/) delivered straight to your inbox, plus upgrade for access to [monthly In Depth articles](https://securinglaravel.com/tag/in-depth/) that dive deep into crucial security topics. By signing up, you're not just securing your code – you're supporting ongoing security research in the Laravel and PHP community!

Subscribe 

Email sent! Check your inbox to complete your signup. 

No spam. Unsubscribe anytime.

---

***If you found this security tip useful?* 👍**  
[*Subscribe now*](#/portal/signup) *to get weekly* [***Security Tips***](https://securinglaravel.com/tag/tips/) *straight to your inbox, filled with practical, actionable advice to help you build safer apps.*

***Want to learn more?* 🤓**  
*Upgrade to a* [*Premium Subscription*](#/portal/signup) *for exclusive monthly* [**In Depth* articles*](https://securinglaravel.com/tag/in-depth/)*, or support my work with a* [*one-off tip*](#/portal/support) *or* [*recurring Sponsorship*](https://securinglaravel.com/sponsor/)*! Your support directly funds my security work in the Laravel community.* 🥰

**Need a second set of eyes on your code?** 
*Book in a* [*Laravel Security Audit and Penetration Test*](https://stephenreescarter.net/laravel-security-audits-and-pentesting/?utm%5Fsource=securinglaravel.com) *today! I also offer budget-friendly* [*Security Reviews*](https://stephenreescarter.net/laravel-security-reviews/?utm%5Fsource=securinglaravel.com) *too.*

*Finally, connect with me on* [*Bluesky*](https://bsky.app/profile/valorin.bsky.social?ref=securinglaravel.com)*, or* [*other socials*](https://pinkary.com/@valorin?ref=securinglaravel.com)*, and check out* [*Practical Laravel Security*](https://practicallaravelsecurity.com/?utm%5Fsource=securinglaravel.com)*, my interactive course designed to boost your Laravel security skills.*