HTML Entities vs URL Encoding in Links (& in href)

Updated

Inside an HTML attribute, write the ampersand as &: href="/search?q=shoes&page=2". The HTML parser turns & back into & before the browser uses the link, so the URL that is requested is /search?q=shoes&page=2. URL encoding is a separate layer: %26 means a literal & inside a parameter value and stays in the URL.

Most broken links in this area come from confusing those two layers, or from applying one of them twice.

& vs %26: HTML escaping and URL encoding are different layers HTML source contains href="/search?q=Tom%26Jerry&page=2". The HTML parser removes the & escaping, giving the URL /search?q=Tom%26Jerry&page=2. The server then splits on the plain ampersand and decodes %26, giving q = Tom&Jerry and page = 2. & vs %26: two layers, removed at different times 1. HTML source href="/search?q=Tom%26Jerry&page=2" HTML parser: & → & 2. URL the browser requests /search?q=Tom%26Jerry&page=2 Server: split on &, then decode %26 → & 3. Parameters the server receives q = "Tom&Jerry" page = "2" & belongs in HTML/XML source only. %26 is an ampersand inside a value.
Two layers: HTML escaping is removed by the parser, URL encoding stays in the request

Two layers, two jobs

HTML escaping URL encoding
Example for & & %26
Defined by The HTML standard (character references) RFC 3986 (percent-encoding)
Removed by The HTML parser, when it reads the page The server or framework, when it reads the parameter
Purpose Stop & from starting a character reference in HTML Stop & from being read as a parameter separator
Appears in the HTTP request No Yes

RFC 3986 lists & among the reserved sub-delimiters, and Google's URL structure guide shows it as the separator between key-value pairs. So in a URL, a raw & separates parameters, and %26 is data. In HTML source, the same URL is written with & in place of each raw &.

Why & matters in href

Browsers are forgiving: href="/p?id=1&lang=en" works, because the HTML standard has a legacy rule for attributes. When a named reference without a semicolon is followed by = or a letter or digit, the parser leaves it alone. We checked the cases below with html5lib, a parser that follows the HTML standard's algorithm:

You write in href URL the browser uses Why
/p?a=1&b=2 /p?a=1&b=2 & decoded to &
/p?a=1&b=2 /p?a=1&b=2 &b is not a reference
/p?a=1&lang=en /p?a=1&lang=en &lang followed by =: left alone
/p?a=1&notify=yes /p?a=1&notify=yes &not followed by a letter: left alone
/p?a=1&not /p?a=1¬ &not at the end: decoded to ¬
/p?x=1&reg /p?x=1® &reg at the end: decoded to ®
/p?a=1⟨=en /p?a=1⟨=en With a semicolon, ⟨ is always decoded

The last three rows are real bugs waiting to happen: a parameter name that matches an entity name, placed at the end of the attribute or followed by a semicolon, is silently changed. Writing & removes the guesswork.

The legacy rule exists only inside attributes. In normal text, ?a=1&notify=yes is displayed as ?a=1¬ify=yes, so URLs shown as visible text need & even more.

Where & does not belong

& is only for HTML (and XML) source. Copying it anywhere else puts the literal characters amp; into the URL.

  • Inside <script>. Script content is raw text; entities are not decoded. var u = "/p?a=1&amp;b=2" sends amp;b to the server.
  • In JSON, CSV, redirect rules and server config. Use the plain &.
  • In emails composed as plain text and in chat messages.
  • In the address bar or Search Console. Paste the decoded URL.

When &amp; leaks into a URL, the server sees a parameter named amp;b. Paste such a URL into the URL parser and it flags the stray amp; for you. In campaign links this turns utm_medium into amp;utm_medium and breaks reporting; see UTM parameter encoding mistakes.

Double escaping: &amp;

If a template escapes a URL that is already escaped, the source contains &amp;amp;. The parser decodes it once, and the browser requests /p?a=1&amp;b=2, again with a parameter named amp;b. It is the HTML version of double URL encoding (%2520), and the fix is the same: escape exactly once, at the point where you write the HTML.

<?php
$url = "/search?q=" . rawurlencode("Tom & Jerry") . "&page=2"; // URL layer
// /search?q=Tom%20%26%20Jerry&page=2
echo '<a href="' . htmlspecialchars($url) . '">Results</a>';    // HTML layer
// <a href="/search?q=Tom%20%26%20Jerry&amp;page=2">Results</a>

Note the order: encode values for the URL first, then escape the whole URL for HTML. Each layer is applied once.

The ampersand inside a value

If the data itself contains &, URL-encode it as %26; HTML escaping does not help here.

Goal Correct in HTML source Wrong
Two parameters q=Tom and page=2 ?q=Tom&amp;page=2 ?q=Tom%26page=2 (one parameter)
One parameter q="Tom & Jerry" ?q=Tom%20%26%20Jerry ?q=Tom &amp; Jerry (two parameters)
const url = "/search?" + new URLSearchParams({ q: "Tom & Jerry", page: 2 });
// "/search?q=Tom+%26+Jerry&page=2"

XML sitemaps and feeds

XML has the same rule as HTML, but stricter: a raw & makes the file invalid. Google's sitemap documentation says the file must be UTF-8 encoded and that, as with all XML files, all tag values must be entity escaped. A URL with two parameters is therefore written like this:

<url>
  <loc>https://example.com/catalog?item=12&amp;desc=vacation</loc>
</url>

Crawlers that read the XML decode &amp; and request ?item=12&desc=vacation.

Other characters in attributes

The ampersand is the most common problem, but two more characters matter when you write URLs into HTML attributes:

Character In an HTML attribute In a URL
& &amp; separator; %26 inside a value
" &quot; (it would end a double-quoted attribute) %22
' &#39; if the attribute uses single quotes allowed; often left as is
< > &lt; &gt; are safest %3C %3E
space allowed, but a raw space is not valid in a URL %20 (or + in a form query)

PHP's htmlspecialchars() handles &, ", < and > by default; JavaScript sets attributes safely through element.setAttribute("href", url) or a.href = url, which need no manual entity escaping because no HTML parsing is involved. Escaping by hand is only needed when you build HTML as a string.

Quick checklist

  1. Build the URL first, percent-encoding each value (%26 for an & inside a value).
  2. When you print it into HTML or XML, escape it once (& → &amp;).
  3. Never copy &amp; from page source into scripts, configs, emails or tools.
  4. Search logs and analytics for amp; to catch leaks.

For converting strings by hand, the universal encoder decoder has both HTML encode/decode and URL encode/decode, so you can see each layer separately. Try it with Tom & Jerry: URL-encode the value first, add it to a URL with a second parameter, then HTML-encode the whole URL. The result shows %26 inside the value and &amp; between the parameters, exactly the two layers described above. Spaces have their own pitfalls, covered in %20 vs + in URLs.

Frequently asked questions

Should I write & or &amp; in an href attribute?

Writing & is the safe choice. The HTML parser decodes & to & before the browser uses the URL, so the link works the same, and you avoid cases where text after & is read as a character reference, such as &not or &reg at the end of an attribute.

What is the difference between &amp; and %26?

& is HTML escaping: it is removed by the HTML parser and the URL still contains a plain & that separates parameters. %26 is URL encoding: it stays in the URL and means a literal & inside a parameter value, for example q=Tom%26Jerry.

Why do I see amp; in my analytics or server logs?

The URL was escaped for HTML twice or copied from HTML source into a place that does not decode entities, such as an email, a redirect rule or a JavaScript string. The server then receives a parameter named amp;utm_medium instead of utm_medium.

Split a URL into scheme, host, path, query and fragment, and decode every parameter.

Open the URL Parser

Sources