HTML Guides for url encoding
Learn how to identify and fix common HTML validation errors flagged by the W3C Validator — so your pages are standards-compliant and render correctly across every browser. Also check our Accessibility Guides.
Percent-encoding (also called URL encoding) is the mechanism defined by RFC 3986 for representing special or reserved characters in URLs. It works by replacing a character with a % sign followed by two hexadecimal digits that correspond to the character’s byte value. For example, a space becomes %20, an ampersand becomes %26, and a literal percent sign becomes %25. When a browser or parser encounters % in a URL, it expects the next two characters to be valid hexadecimal digits so it can decode them back to the original character.
If the parser finds a % that is not followed by two hexadecimal digits, it cannot decode the sequence, and the URL is considered invalid. The W3C validator flags this as a bad value for the href attribute.
Common causes
- A literal % that wasn’t encoded. This is the most frequent cause. For instance, a URL containing a percentage value like 50% in a query string — the % must be written as %25.
- Truncated percent-encoded sequences. A sequence like %2 is incomplete because it only has one hex digit instead of two.
- Invalid hex characters after %. A sequence like %GZ is invalid because G and Z are not hexadecimal digits (valid hex digits are 0–9 and A–F).
- Copy-pasting URLs from non-web sources. URLs pasted from documents, emails, or spreadsheets may contain unencoded special characters.
Why this matters
- Broken links. Browsers may interpret the malformed URL differently than intended, leading users to the wrong page or a 404 error.
- Standards compliance. The WHATWG URL Standard and the HTML specification require that href values contain valid URLs. Malformed URLs violate these standards.
- Accessibility. Screen readers and assistive technologies rely on well-formed URLs to provide accurate navigation information to users.
- Interoperability. While some browsers attempt error recovery on malformed URLs, behavior is not guaranteed to be consistent across all browsers and tools.
How to fix it
- Encode literal % as %25. If the % is meant to appear as part of the URL’s content (e.g., in a query parameter value), replace it with %25.
- Complete any truncated sequences. If you intended a percent-encoded character like %20, make sure both hex digits are present.
- Use proper URL encoding functions. In JavaScript, use encodeURIComponent() for query parameter values. In server-side languages, use the equivalent encoding function (e.g., urllib.parse.quote() in Python, urlencode() in PHP).
Examples
Literal % not encoded
<!-- ❌ Bad: the % in "48%" is not followed by two hex digits -->
<a href="https://example.com?width=48%">48% width</a>
<!-- ✅ Good: the % is encoded as %25 -->
<a href="https://example.com?width=48%25">48% width</a>
Truncated percent-encoded sequence
<!-- ❌ Bad: %2 is incomplete — it needs two hex digits -->
<a href="https://example.com/path%2file">Download</a>
<!-- ✅ Good: %2F is a complete encoding for "/" -->
<a href="https://example.com/path%2Ffile">Download</a>
Invalid hexadecimal characters
<!-- ❌ Bad: %XY contains non-hexadecimal characters -->
<a href="https://example.com/search?q=%XYdata">Search</a>
<!-- ✅ Good: if the intent was a literal "%XY", encode the % -->
<a href="https://example.com/search?q=%25XYdata">Search</a>
Multiple unencoded % in a URL
<!-- ❌ Bad: both % signs are unencoded -->
<a href="https://example.com?min=10%&max=90%">Range</a>
<!-- ✅ Good: both % signs are properly encoded -->
<a href="https://example.com?min=10%25&max=90%25">Range</a>
In URLs, special characters that aren’t part of the standard allowed set must be represented using percent-encoding (also called URL encoding). This works by replacing the character with a % followed by two hexadecimal digits representing its byte value — for example, a space becomes %20, and an ampersand becomes %26. Because % itself serves as the escape character in this scheme, any bare % that isn’t followed by two hexadecimal digits creates an ambiguous, invalid URL.
When the W3C validator encounters a src attribute containing a % not followed by two valid hex digits, it cannot determine the intended character and reports the error. This issue typically arises in two scenarios:
- A literal percent sign in the URL — for instance, a query parameter like ?width=48% where the % is meant as an actual percent symbol. The % must be encoded as %25.
- Incomplete or corrupted percent-encoding — such as %2 instead of %20, or %GZ where the characters after % aren’t valid hexadecimal digits (only 0–9 and A–F/a–f are valid).
Why this matters
- Browser inconsistency: While many browsers try to handle malformed URLs gracefully, behavior varies. Some browsers may misinterpret the intended resource path, leading to broken images or unexpected requests.
- Standards compliance: The URL Living Standard and RFC 3986 define strict rules for percent-encoding. Invalid URLs violate these standards.
- Reliability: Proxies, CDNs, and server-side software may reject or misroute requests with malformed URLs, causing images to fail to load in certain environments even if they work in your local browser.
How to fix it
- Find every bare % in the URL that isn’t followed by two hexadecimal digits.
- If the % is meant as a literal percent sign, replace it with %25.
- If the % is part of a broken encoding sequence (e.g., %2 or %GH), correct it to the intended two-digit hex code (e.g., %20 for a space).
- Use proper URL encoding functions in your language or framework (e.g., encodeURIComponent() in JavaScript, urlencode() in PHP) when building URLs dynamically, rather than constructing them by hand.
Examples
Literal percent sign not encoded
<!-- ❌ Bad: bare "%" is not followed by two hex digits -->
<img alt="Chart" src="https://example.com/chart.png?scale=50%">
<!-- ✅ Fixed: "%" encoded as "%25" -->
<img alt="Chart" src="https://example.com/chart.png?scale=50%25">
Incomplete percent-encoding sequence
<!-- ❌ Bad: "%2" is incomplete — missing the second hex digit -->
<img alt="Photo" src="https://example.com/my%2photo.jpg">
<!-- ✅ Fixed: use "%20" for a space character -->
<img alt="Photo" src="https://example.com/my%20photo.jpg">
Non-hexadecimal characters after percent sign
<!-- ❌ Bad: "%zz" uses non-hex characters -->
<img alt="Logo" src="https://example.com/logo%zz.png">
<!-- ✅ Fixed: remove the erroneous sequence if it was unintentional -->
<img alt="Logo" src="https://example.com/logo.png">
Multiple encoding issues in one URL
<!-- ❌ Bad: bare "%" in query value and unencoded space -->
<img alt="Report" src="https://example.com/img?label=100%&name=my file.png">
<!-- ✅ Fixed: "%" → "%25", space → "%20" -->
<img alt="Report" src="https://example.com/img?label=100%25&name=my%20file.png">
Building URLs safely with JavaScript
When generating src values in code, use encodeURIComponent() to handle special characters automatically:
const label = "100%";
const name = "my file.png";
const src = `https://example.com/img?label=${encodeURIComponent(label)}&name=${encodeURIComponent(name)}`;
// Result: "https://example.com/img?label=100%25&name=my%20file.png"
This ensures every special character — including % — is correctly percent-encoded without manual effort.
In URLs, percent-encoding is used to represent special or reserved characters. The format is a % sign followed by exactly two hexadecimal digits (0–9, A–F), such as %20 for a space or %3F for a question mark. When the browser encounters a % in a URL, it expects the next two characters to be valid hex digits. If they aren’t — for example, % followed by a letter like G, a non-hex character, or nothing at all — the URL is considered malformed.
This issue specifically targets the srcset attribute on <source> elements (commonly used inside <picture> elements), where one or more image candidate URLs contain an invalid percent sequence. The most common causes are:
- A literal % sign in a query parameter — e.g., ?quality=80% where % is meant literally but isn’t encoded.
- Truncated percent-encoding — e.g., %2 instead of %2F, possibly from a copy-paste error or a broken URL-encoding function.
- Double-encoding gone wrong — a URL that was partially encoded, leaving some % characters in an ambiguous state.
This matters because browsers may handle malformed URLs inconsistently. Some browsers might try to recover gracefully, while others may fail to load the image entirely. Invalid URLs also break standards compliance, can cause issues with CDNs and caching layers, and make your markup unreliable across different environments.
How to fix it
- Find the offending % character in your srcset URL.
- If the % is meant literally (e.g., as part of a percentage value like 80%), encode it as %25.
- If the % is part of an incomplete percent-encoding (e.g., %2 instead of %2F), correct it to the full three-character sequence.
- Review your URL-generation logic — if URLs are built dynamically by a CMS, template engine, or server-side code, ensure proper encoding is applied before output.
Examples
Invalid: literal % in srcset URL
The % in quality=80% is not followed by two hex digits, so it’s treated as a broken percent-encoding sequence.
<picture>
<source
srcset="https://example.com/photo.webp?quality=80%"
type="image/webp">
<img src="https://example.com/photo.jpg" alt="A scenic landscape">
</picture>
Valid: % encoded as %25
Replacing the bare % with %25 produces a valid URL.
<picture>
<source
srcset="https://example.com/photo.webp?quality=80%25"
type="image/webp">
<img src="https://example.com/photo.jpg" alt="A scenic landscape">
</picture>
Invalid: truncated percent-encoding in a file path
Here, %2 is incomplete — it should be %2F (which decodes to /) or some other valid sequence.
<picture>
<source
srcset="https://example.com/images%2photo.webp 1x"
type="image/webp">
<img src="https://example.com/photo.jpg" alt="Product photo">
</picture>
Valid: corrected percent-encoding
The sequence is now %2F, a properly formed encoding.
<picture>
<source
srcset="https://example.com/images%2Fphoto.webp 1x"
type="image/webp">
<img src="https://example.com/photo.jpg" alt="Product photo">
</picture>
Invalid: unencoded % in srcset with multiple candidates
Every URL in the srcset list must be valid. Here, both candidates contain a bare %.
<picture>
<source
srcset="https://example.com/img.webp?w=480&q=75% 480w,
https://example.com/img.webp?w=800&q=75% 800w"
type="image/webp">
<img src="https://example.com/img.jpg" alt="Blog header image">
</picture>
Valid: all candidates properly encoded
<picture>
<source
srcset="https://example.com/img.webp?w=480&q=75%25 480w,
https://example.com/img.webp?w=800&q=75%25 800w"
type="image/webp">
<img src="https://example.com/img.jpg" alt="Blog header image">
</picture>
Note that in the corrected example, & is also encoded as & within the HTML attribute, which is required for valid markup when using ampersands in attribute values.
Ready to validate your sites?
Start your free trial today.