Escaping means converting special characters so they are treated as plain text in the place where the data will be used. The right escaping depends on where the data goes. There is no single “make it safe” function.
| Where the data goes | What to use |
|---|---|
| HTML text and quoted attributes | htmlspecialchars() |
| A URL query value | urlencode() or http_build_query() |
| A URL path part | rawurlencode() |
JavaScript (inside a <script>) | json_encode() with JSON_HEX_* flags |
| JSON output | json_encode() |
| An SQL query | Prepared statements |
| A shell command | escapeshellarg(), or better, avoid shell commands |
| CSV | Quote and guard against formulas |
| An email header | Remove CR and LF characters |
HTML
<?php
$name = "O'Reilly & <Sons>";
echo "<p>Hello, " . htmlspecialchars($name, ENT_QUOTES, "UTF-8") . "</p>";
<p>Hello, O'Reilly & <Sons></p>
URL parts
<?php
$search = "php & mysql";
$user = "ana/ben";
echo "/search?q=" . urlencode($search) . "\n";
echo "/users/" . rawurlencode($user) . "/profile\n";
echo "/list?" . http_build_query(["tag" => "a b", "page" => 2]);
/search?q=php+%26+mysql
/users/ana%2Fben/profile
/list?tag=a+b&page=2
HTML and URL together
A link needs both: URL-encode the value, then HTML-escape the whole attribute.
<?php
$term = "salt & pepper";
$url = "/search?q=" . urlencode($term);
echo '<a href="' . htmlspecialchars($url, ENT_QUOTES, "UTF-8") . '">Search</a>';
<a href="/search?q=salt+%26+pepper">Search</a>
JavaScript
<?php
$user = ["name" => "Ana", "bio" => "</script><b>"];
echo "<script>const user = " . json_encode($user, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT) . ";</script>";
<script>const user = {"name":"Ana","bio":"\u003C\/script\u003E\u003Cb\u003E"};</script>
Never build JavaScript by joining strings into inline event handlers (onclick="..."). Pass data through data- attributes (escaped for HTML) and read it in a script file.
Email headers
<?php
function headerSafe(string $value): string
{
return str_replace(["\r", "\n"], "", $value);
}
echo headerSafe("Hello\r\nBcc: attacker@example.com");
HelloBcc: attacker@example.com
Without removing the line breaks, an attacker can inject extra headers (such as Bcc:) into the email. Better still, use a mail library that handles it.
Where to escape
Escape at output, as late as possible. Store the original text. If you escape before saving, you end up with double-escaped text (&amp;), and the data is wrong for every other use (an email, a PDF, JSON).
Template engines do it for you
- Twig and Blade escape
{{ variable }}by default. You must opt out to print raw HTML. - Plain PHP does not. You have to remember
htmlspecialchars()every time, so use a helper likeh(), and make it a habit.
Test a template for missing escapes
Search your code for echo $ and <?= $ that is not wrapped in an escape function. Each one is a question: where did that value come from?
Escaping once, in the wrong place
htmlspecialchars() protects HTML only. An escaped value inside a <script> or an SQL query is still dangerous. Match the escaping to the destination.