The Exact Error
URIError: URI malformed
URIError: malformed URI sequence
URIError: The URI to be decoded is not a valid encoding
Quick summary:
decodeURIComponent()ordecodeURI()received a string containing a%not followed by two valid hex digits. The most common source is user-supplied URLs or improperly encoded query strings.
Why This Error Happens
Percent-encoding requires % followed by exactly two hexadecimal characters (0-9, A-F). Any other sequence throws:
1. Bare % in the string ā hello%world ā %wo is not valid hex
2. Truncated encoding ā value%2 ā incomplete sequence
3. Decoding twice ā calling decodeURIComponent on an already-decoded string that contains % as a literal character
4. User input ā a user pastes a URL with % not meant as an escape character
Step-by-Step Diagnosis
Step 1 ā Identify where the malformed input comes from
const raw = req.query.redirect; // User-supplied
console.log('Raw value:', raw);
// Check for invalid % sequences
console.log('Has bare %:', /%(?![0-9A-Fa-f]{2})/.test(raw));
Step 2 ā Check for double decoding
// If the string was already decoded, it may contain literal % signs
// that are not valid percent-encoding
const alreadyDecoded = 'hello 50% done';
decodeURIComponent(alreadyDecoded); // URIError: % is followed by ' d'
Step 3 ā Verify the encoding at the source
// Always encode before sending
const param = encodeURIComponent('value with % and spaces');
// Use this in the URL, then decode on the receiving end
Solutions
Solution 1 ā Wrap in try-catch
function safeDecode(str) {
try {
return decodeURIComponent(str);
} catch (e) {
return str;
}
}
Solution 2 ā Sanitize % signs before decoding
function decodeSafe(str) {
// Replace bare % (not followed by two hex digits) with %25
const sanitized = str.replace(/%(?![0-9A-Fa-f]{2})/g, '%25');
return decodeURIComponent(sanitized);
}
Solution 3 ā Use the URL API instead of manual decoding
try {
const url = new URL(input);
// url.searchParams automatically handles decoding
const value = url.searchParams.get('redirect');
} catch (e) {
console.error('Invalid URL:', e.message);
}
Solution 4 ā Encode properly at the source
// Building a URL with query parameters
const base = 'https://example.com/search';
const params = new URLSearchParams({ q: 'hello world & more', page: '2' });
const fullUrl = `${base}?${params.toString()}`;
// All values are properly encoded automatically
Real-World Examples
Express.js route handling:
app.get('/redirect', (req, res) => {
let target;
try {
target = decodeURIComponent(req.query.to || '');
} catch {
return res.status(400).json({ error: 'Invalid redirect URL' });
}
if (!target.startsWith('/')) {
return res.status(400).json({ error: 'Invalid redirect target' });
}
res.redirect(target);
});
Quick Reference ā URL Encoding Functions
| Function | Encodes | Leaves unencoded |
|---|---|---|
encodeURIComponent(s) | All except A-Z a-z 0-9 - _ . ! ~ * ' ( ) | Unreserved chars |
encodeURI(s) | Special chars except ;,/?:@&=+$# | URI structure chars |
decodeURIComponent(s) | Decodes all %XX sequences | ā |
decodeURI(s) | Decodes all except ;,/?:@&=+$# | ā |
Prevent This Error in the Future
1. Always use encodeURIComponent for individual query parameter values.
2. Use URLSearchParams to build query strings ā it handles encoding automatically.
3. Wrap decodeURIComponent in try-catch when handling any user-supplied input.
Use ToolNinja to Debug Faster
The URL Encoder lets you encode and decode URL components interactively ā paste any string to see the correct percent-encoded form or decode a malformed URL fragment.