URL Decode JavaScript — decodeURIComponent()
Utilisez le Décodeur d'URL en ligne gratuit directement dans votre navigateur — sans installation.
Essayer Décodeur d'URL en ligne en ligne →Les chaînes encodées en pourcent apparaissent constamment dans le code JavaScript — une requête de recherche arrive sous la forme q=standing+desk%26price%3A200, une redirection OAuth comme next=https%3A%2F%2Fdashboard.internal%2F, un chemin de stockage comme reports%2F2025%2Fq1.pdf. Savoir comment décoder une URL en JavaScript revient à choisir la bonne parmi trois fonctions natives : decodeURIComponent(), decodeURI() et URLSearchParams— et ce choix est à l'origine de la plupart des corruptions silencieuses de données que j'ai vues dans des bases de code en production, notamment le cas limite du + comme espace et le double décodage. Pour un décodage rapide sans écrire de code, le Décodeur d'URL de ToolDeck le fait instantanément dans le navigateur. Ce tutoriel de décodage d'URL en JavaScript couvre les trois fonctions en profondeur (ES2015+ / Node.js 10+) : quand utiliser chacune, comment elles diffèrent pour les espaces et les caractères réservés, le décodage depuis des fichiers et des requêtes HTTP, la gestion sécurisée des erreurs et les quatre erreurs qui causent les bugs de production les plus subtils.
- ✓decodeURIComponent() décode toutes les séquences encodées en pourcent — c'est le bon choix pour les valeurs de paramètres de query individuels et les segments de chemin
- ✓decodeURI() préserve les caractères structurels de l'URI comme / ? & = # : — utilisez-le uniquement pour décoder une chaîne d'URL complète, jamais pour des valeurs individuelles
- ✓URLSearchParams.get() retourne des valeurs déjà décodées — appeler decodeURIComponent() sur le résultat cause un double décodage
- ✓decodeURIComponent() ne décode PAS + comme un espace — pour les données encodées comme formulaire (application/x-www-form-urlencoded), utilisez URLSearchParams ou remplacez + avant de décoder
- ✓Enveloppez toujours decodeURIComponent() dans try/catch quand l'entrée vient de données utilisateur — un % isolé ou une séquence incomplète lève URIError
Qu'est-ce que le décodage d'URL ?
L'encodage pourcent (formellement défini dans la RFC 3986) remplace les caractères non sûrs ou structurellement significatifs dans une URL par un signe %suivi de deux chiffres hexadécimaux — la valeur en octet UTF-8 du caractère. Le décodage d'URL inverse cette transformation : chaque séquence %XXest reconvertie en son octet d'origine, et la séquence d'octets résultante est interprétée comme du texte UTF-8. Un espace décodé depuis %20, une barre oblique depuis %2F, le caractère non ASCII ü depuis %C3%BC (sa représentation UTF-8 sur deux octets).
Les caractères qui ne sont jamais encodés — appelés caractères non réservés — sont les lettres A–Z et a–z, les chiffres 0–9 et - _ . ~. Tout le reste a un rôle structurel dans l'URL (comme / séparant les segments de chemin ou & séparant les paramètres de query) ou doit être encodé quand il est utilisé comme donnée. Le résultat pratique : un filtre de recherche comme status=active&tier=premiumencodé comme valeur d'un seul paramètre de query arrive sans aucune ressemblance avec l'original.
// Encodé en pourcent — tel que reçu dans une requête HTTP ou un payload webhook "q=price%3A%5B200+TO+800%5D%20AND%20brand%3ANorthwood%20%26%20status%3Ain-stock"
// Après le décodage d'URL — le filtre Elasticsearch d'origine "q=price:[200 TO 800] AND brand:Northwood & status:in-stock"
decodeURIComponent() — La fonction standard pour décoder des valeurs
decodeURIComponent()est le moteur du décodage d'URL en JavaScript. Elle décode chaque séquence %XXdans la chaîne d'entrée — y compris les caractères qui ont une signification structurelle dans une URL, comme %2F (barre oblique), %3F (point d'interrogation), %26 (esperluette) et %3D(signe égal). Cela en fait le bon choix pour décoder les valeurs de paramètres de query individuels et les segments de chemin, mais le mauvais choix pour décoder une URL complète — où ces caractères structurels doivent rester encodés. C'est une fonction globale : aucune importation n'est nécessaire dans aucun environnement JavaScript.
Exemple minimal fonctionnel
// Décoder des valeurs de paramètres de query individuels — le cas courant
const city = decodeURIComponent('S%C3%A3o%20Paulo') // 'São Paulo'
const district = decodeURIComponent('It%C3%A1im%20Bibi') // 'Itáim Bibi'
const category = decodeURIComponent('office%20furniture') // 'office furniture'
const filter = decodeURIComponent('price%3A%5B200+TO+800%5D') // 'price:[200+TO+800]'
// Remarque : + n'est PAS décodé comme espace — voir la section + dans le tableau de comparaison
console.log(city) // São Paulo
console.log(district) // Itáim Bibi
console.log(category) // office furniture
console.log(filter) // price:[200+TO+800]Décodage d'une URL de redirection extraite d'un paramètre de query
// L'URL de redirection a été encodée en pourcent lors de son insertion comme valeur de paramètre
// côté récepteur : l'extraire et l'utiliser — sans décodage manuel avec URLSearchParams
const incomingUrl = 'https://auth.company.com/callback' +
'?next=https%3A%2F%2Fdashboard.internal%2Freports%3Fview%3Dweekly%26team%3Dplatform' +
'&session_id=sid_7x9p2k'
const url = new URL(incomingUrl)
const rawNext = url.searchParams.get('next') // Décodé automatiquement par URLSearchParams
const sessionId = url.searchParams.get('session_id') // 'sid_7x9p2k'
// rawNext est déjà décodé : 'https://dashboard.internal/reports?view=weekly&team=platform'
// Ne PAS appeler decodeURIComponent(rawNext) à nouveau — ce serait un double décodage
const nextUrl = new URL(rawNext!)
console.log(nextUrl.hostname) // dashboard.internal
console.log(nextUrl.searchParams.get('view')) // weekly
console.log(nextUrl.searchParams.get('team')) // platformDécodage de segments de chemin non ASCII et Unicode
// API REST avec des segments de chemin internationalisés
// Chaque octet UTF-8 du caractère original a été encodé en pourcent séparément
const encodedSegments = [
'%E6%9D%B1%E4%BA%AC', // 東京 (Tokyo) — 3 octets par caractère
'M%C3%BCnchen', // München — ü encodé sur 2 octets
'caf%C3%A9', // café — é en NFC précomposé
'S%C3%A3o%20Paulo', // São Paulo
]
encodedSegments.forEach(seg => {
console.log(decodeURIComponent(seg))
})
// 東京
// München
// café
// São Paulo
// Extraction d'une clé de fichier depuis une URL d'API de stockage
// La clé d'objet contenait / donc elle a été encodée comme %2F dans le segment de chemin
const storageUrl = 'https://storage.api.example.com/v1/objects/reports%2F2025%2Fq1-financials.pdf'
const rawKey = new URL(storageUrl).pathname.replace('/v1/objects/', '')
// .pathname décode l'encodage au niveau URL mais %2F (comme %252F au niveau URL) reste
// Utiliser decodeURIComponent pour l'étape finale :
const fileKey = decodeURIComponent(rawKey) // 'reports/2025/q1-financials.pdf'
console.log(fileKey)decodeURIComponent() lève une URIError quand l'entrée contient un % non suivi de deux chiffres hexadécimaux valides — par exemple un %isolé à la fin d'une chaîne ou une séquence comme %GH. Enveloppez-le toujours dans try/catchlors du décodage d'une entrée fournie par l'utilisateur. Les patterns de décodage sécurisé sont couverts dans la section Gestion des erreurs ci-dessous.Fonctions de décodage d'URL en JavaScript — Référence des caractères
Les trois fonctions de décodage natives diffèrent précisément dans les séquences encodées qu'elles décodent. Le tableau montre le comportement pour les caractères les plus importants en pratique :
| Encodé | Caractère | decodeURIComponent() | decodeURI() | URLSearchParams |
|---|---|---|---|---|
| %20 | espace | espace ✅ | espace ✅ | espace ✅ |
| + | plus (formulaire) | + (conservé) | + (conservé) | espace ✅ |
| %2B | + littéral | + ✅ | + ✅ | + ✅ |
| %26 | & | & ✅ | & (conservé) ❌ | & ✅ |
| %3D | = | = ✅ | = (conservé) ❌ | = ✅ |
| %3F | ? | ? ✅ | ? (conservé) ❌ | ? ✅ |
| %23 | # | # ✅ | # (conservé) ❌ | # ✅ |
| %2F | / | / ✅ | / (conservé) ❌ | / ✅ |
| %3A | : | : ✅ | : (conservé) ❌ | : ✅ |
| %40 | @ | @ ✅ | @ (conservé) ❌ | @ ✅ |
| %25 | % | % ✅ | % ✅ | % ✅ |
| %C3%BC | ü | ü ✅ | ü ✅ | ü ✅ |
Les deux lignes critiques sont + et les caractères structurels (%26, %3D, %3F). URLSearchParams décode +comme un espace parce qu'il suit la spécification application/x-www-form-urlencoded — correct pour les soumissions de formulaires HTML, mais différent de ce que fait decodeURIComponent() avec le même caractère. Et decodeURI() ignore silencieusement %26, %3D et %3F— ce qui semble correct jusqu'à ce qu'une valeur contienne réellement une esperluette ou un signe égal encodé.
decodeURI() — Décoder une URL complète sans casser sa structure
decodeURI() est le pendant de encodeURI(). Elle décode une chaîne d'URL complète tout en préservant les caractères qui ont une signification structurelle dans une URI : ; , / ? : @ & = + $ #. Ceux-ci sont laissés sous leur forme encodée en pourcent (ou comme caractères littéraux s'ils apparaissaient non encodés dans l'entrée). Cela rend decodeURI()sûr pour nettoyer des URL complètes qui peuvent contenir des caractères non ASCII dans le chemin ou le nom d'hôte, sans effondrer accidentellement la structure de la query string.
Nettoyage d'une URL avec des segments de chemin non ASCII
// Une URL CDN avec des segments de chemin internationalisés et une query string structurée // decodeURI() décode le chemin non ASCII mais préserve ? & = intacts const encodedUrl = 'https://cdn.example.com/assets/%E6%9D%B1%E4%BA%AC%2F2025%2Fq1-report.pdf' + '?token=eyJ0eXAiOiJKV1QiLCJhbGci&expires=1735689600' const readable = decodeURI(encodedUrl) console.log(readable) // https://cdn.example.com/assets/東京/2025/q1-report.pdf?token=eyJ0eXAiOiJKV1QiLCJhbGci&expires=1735689600 // ↑ Non ASCII décodé ; ? & = préservés — l'URL reste structurellement valide // decodeURIComponent détruirait l'URL — : / ? & = tous décodés d'un coup const broken = decodeURIComponent(encodedUrl) // 'https://cdn.example.com/assets/東京/2025/q1-report.pdf?token=eyJ0eXAiOiJKV1QiLCJhbGci&expires=1735689600' // Semble identique ici, mais une URL comme 'https%3A%2F%2F...' serait détruite
URL à decodeURI()quand vous avez également besoin d'accéder aux composants individuels de l'URL. new URL(str)normalise l'entrée, valide sa structure et expose .pathname, .searchParams et .hostname comme propriétés déjà décodées. Réservez decodeURI()aux cas où vous avez seulement besoin d'un résultat sous forme de chaîne et ne pouvez pas utiliser le constructeur URL (par exemple, dans de très anciens environnements Node.js 6 sans la classe globale URL).URLSearchParams — Décodage automatique pour les query strings
URLSearchParamsest la façon idiomatique d'analyser les query strings en JavaScript moderne. Chaque valeur retournée par .get(), .getAll()ou l'itération est automatiquement décodée — y compris + comme espace pour les données encodées comme formulaire. Il est disponible globalement dans tous les navigateurs modernes et Node.js 10+, ne nécessite aucune importation et gère les cas limites que la division manuelle de chaînes rate.
Analyse d'une query string entrante
// Analyse d'une query string de callback webhook ou de redirection OAuth
const rawSearch =
'?event_id=evt_9c2f4a1b' +
'&product_name=Standing+Desk+Pro' +
'&filter=price%3A%5B200+TO+800%5D' +
'&tag=ergonomic&tag=adjustable' +
'&redirect=https%3A%2F%2Fdashboard.internal%2Forders%3Fview%3Dpending'
const params = new URLSearchParams(rawSearch)
console.log(params.get('event_id')) // 'evt_9c2f4a1b'
console.log(params.get('product_name')) // 'Standing Desk Pro' ← + décodé comme espace
console.log(params.get('filter')) // 'price:[200+TO+800]' ← %3A et %5B décodés
console.log(params.getAll('tag')) // ['ergonomic', 'adjustable']
console.log(params.get('redirect')) // 'https://dashboard.internal/orders?view=pending'
// Itération de tous les paramètres
for (const [key, value] of params) {
console.log(`${key}: ${value}`)
}
// event_id: evt_9c2f4a1b
// product_name: Standing Desk Pro
// filter: price:[200+TO+800]
// tag: ergonomic
// tag: adjustable
// redirect: https://dashboard.internal/orders?view=pendingAnalyse des paramètres de query depuis l'URL courante du navigateur
// URL courante : https://app.example.com/search
// ?q=standing+desk
// &category=office+furniture
// &sort=price_asc
// &page=2
interface SearchFilters {
query: string | null
category: string | null
sort: string
page: number
}
function getSearchFilters(): SearchFilters {
const params = new URLSearchParams(window.location.search)
return {
query: params.get('q'), // 'standing desk'
category: params.get('category'), // 'office furniture'
sort: params.get('sort') ?? 'relevance',
page: Number(params.get('page') ?? '1'),
}
}
const filters = getSearchFilters()
console.log(filters.query) // standing desk (+ décodé automatiquement)
console.log(filters.category) // office furnitureDécodage de données encodées en URL depuis des fichiers et des réponses API
Deux scénarios reviennent constamment dans les projets réels : traiter un fichier sur disque contenant des données encodées en pourcent (logs d'accès, exports de données, fichiers de capture de webhooks) et analyser l'URL d'une requête HTTP entrante dans un serveur Node.js. Les deux suivent le même principe — utiliser le constructeur URL ou URLSearchParams plutôt que la division manuelle de chaînes — mais les détails diffèrent.
Lecture et décodage d'enregistrements encodés en URL depuis un fichier
import { createReadStream } from 'fs'
import { createInterface } from 'readline'
// Fichier : orders-export.txt — un enregistrement encodé en URL par ligne
// customer_name=Thomas+Dupont&order_id=ord_9c2f4a&product=Standing+Desk+Pro&total=149.99%20EUR
// customer_name=Claire+Martin&order_id=ord_7b3a1c&product=Ergonomic+Chair&total=89.00%20EUR
// customer_name=Julie+Dupont&order_id=ord_2e8d5f&product=Monitor+Arm&total=59.00%20EUR
interface Order {
customerName: string | null
orderId: string | null
product: string | null
total: string | null
}
async function parseOrdersFile(filePath: string): Promise<Order[]> {
const fileStream = createReadStream(filePath, { encoding: 'utf-8' })
const rl = createInterface({ input: fileStream, crlfDelay: Infinity })
const orders: Order[] = []
for await (const line of rl) {
if (!line.trim()) continue
// URLSearchParams décode + comme espace et les séquences %XX automatiquement
const params = new URLSearchParams(line)
orders.push({
customerName: params.get('customer_name'), // 'Thomas Dupont'
orderId: params.get('order_id'), // 'ord_9c2f4a'
product: params.get('product'), // 'Standing Desk Pro'
total: params.get('total'), // '149.99 EUR'
})
}
return orders
}
const orders = await parseOrdersFile('./orders-export.txt')
console.log(orders[0])
// { customerName: 'Thomas Dupont', orderId: 'ord_9c2f4a', product: 'Standing Desk Pro', total: '149.99 EUR' }Analyse d'un log d'accès Nginx pour décoder les requêtes de recherche
import { createReadStream } from 'fs'
import { createInterface } from 'readline'
// Les lignes du log d'accès Nginx ressemblent à :
// 192.168.1.42 - - [11/Mar/2026:10:23:01 +0000] "GET /api/search?q=standing%20desk%26brand%3ANorthwood&sort=price_asc HTTP/1.1" 200 1842
async function extractSearchQueries(logFile: string): Promise<string[]> {
const rl = createInterface({ input: createReadStream(logFile), crlfDelay: Infinity })
const queries: string[] = []
for await (const line of rl) {
// Extraire le chemin de la requête depuis la ligne du log
const match = line.match(/"GET ([^ ]+) HTTP/)
if (!match) continue
try {
const requestUrl = new URL(match[1], 'http://localhost')
const query = requestUrl.searchParams.get('q')
if (query) queries.push(query) // URLSearchParams décode automatiquement
} catch {
// Ignorer les lignes malformées — les logs d'accès peuvent contenir des entrées tronquées
}
}
return queries
}
const queries = await extractSearchQueries('/var/log/nginx/access.log')
console.log(queries)
// ['standing desk&brand:Northwood', 'ergonomic chair', 'monitor arm 27 inch']Analyse des paramètres de query dans un serveur HTTP Node.js
import http from 'http'
// URL entrante : /api/products?q=standing+desk&warehouse=eu%2Dwest%2D1&minStock=10&cursor=eyJpZCI6MTIzfQ%3D%3D
const server = http.createServer((req, res) => {
// Construire une URL complète — le second argument est la base requise par le constructeur URL
const requestUrl = new URL(req.url!, 'http://localhost')
const searchQuery = requestUrl.searchParams.get('q') // 'standing desk'
const warehouseId = requestUrl.searchParams.get('warehouse') // 'eu-west-1'
const minStock = Number(requestUrl.searchParams.get('minStock') ?? '0')
const cursor = requestUrl.searchParams.get('cursor') // 'eyJpZCI6MTIzfQ=='
if (!warehouseId) {
res.writeHead(400, { 'Content-Type': 'application/json' })
res.end(JSON.stringify({ error: 'warehouse parameter is required' }))
return
}
const cursorData = cursor ? JSON.parse(Buffer.from(cursor, 'base64').toString()) : null
res.writeHead(200, { 'Content-Type': 'application/json' })
res.end(JSON.stringify({ searchQuery, warehouseId, minStock, cursorData }))
})
server.listen(3000)Quand vous avez besoin d'inspecter une URL encodée pendant le développement — pour comprendre ce qu'un webhook envoie avant d'écrire le code d'analyse — collez-la directement dans le Décodeur d'URL de ToolDeck pour voir la forme décodée instantanément sans exécuter un script.
Décodage d'URL en ligne de commande
Pour les scripts shell, les pipelines CI ou l'inspection rapide de chaînes encodées, plusieurs approches fonctionnent sans écrire un script complet. Les one-liners Node.js sont multiplateformes ; sur macOS et Linux, python3 est également toujours disponible.
# ── One-liners Node.js ──────────────────────────────────────────────────
# Décoder une seule valeur encodée en pourcent
node -e "console.log(decodeURIComponent(process.argv[1]))" "S%C3%A3o%20Paulo%20%26%20Rio"
# São Paulo & Rio
# Analyser une query string et afficher chaque paire clé=valeur (décodée)
node -e "
const params = new URLSearchParams(process.argv[1])
for (const [k, v] of params) console.log(`${k} = ${v}`)
" "q=standing+desk&warehouse=eu%2Dwest%2D1&minStock=10"
# q = standing desk
# warehouse = eu-west-1
# minStock = 10
# Décoder et afficher formaté un corps JSON encodé en URL (courant en débogage de webhooks)
node -e "
const raw = decodeURIComponent(process.argv[1])
console.log(JSON.stringify(JSON.parse(raw), null, 2))
" '%7B%22event%22%3A%22purchase%22%2C%22amount%22%3A149.99%2C%22currency%22%3A%22EUR%22%7D'
# {
# "event": "purchase",
# "amount": 149.99,
# "currency": "EUR"
# }
# ── One-liner Python (disponible sur la plupart des systèmes macOS/Linux) ─────────────
# unquote_plus décode aussi + comme espace — correct pour les données encodées comme formulaire
python3 -c "
from urllib.parse import unquote_plus
import sys
print(unquote_plus(sys.argv[1]))
" "Standing+Desk+%26+Ergonomic+Chair"
# Standing Desk & Ergonomic Chair
# ── curl — enregistrer et décoder une URL de redirection depuis un en-tête de réponse ──────────
curl -sI "https://api.example.com/short/abc123" | grep -i location | node -e "
const line = require('fs').readFileSync('/dev/stdin', 'utf8')
const url = line.replace(/^location:s*/i, '').trim()
console.log(decodeURIComponent(url))
"Décodage tolérant avec decode-uri-component
Le decodeURIComponent() natif lève une URIError sur toute séquence malformée — y compris un %final sans deux chiffres hexadécimaux suivants. Dans le code de production qui traite des URL fournies par l'utilisateur ou des tiers — logs de requêtes de recherche, URL de clics de campagnes e-mail, données web scrappées — les séquences malformées sont assez courantes pour provoquer des plantages. Le paquet decode-uri-component (~30 millions de téléchargements hebdomadaires sur npm) gère les séquences malformées de manière tolérante en retournant la séquence originale inchangée au lieu de lever une erreur.
npm install decode-uri-component # ou pnpm add decode-uri-component
import decodeUriComponent from 'decode-uri-component'
// La fonction native lève une erreur sur une entrée malformée — peut planter un handler de requête
try {
decodeURIComponent('product%name%') // ❌ URIError: URI malformed
} catch (e) {
console.error('Native a levé :', (e as Error).message)
}
// decode-uri-component retourne la séquence raw au lieu de lever
console.log(decodeUriComponent('product%name%'))
// product%name% ← les séquences malformées laissées telles quelles, sans erreur
// Les séquences valides sont décodées correctement
console.log(decodeUriComponent('S%C3%A3o%20Paulo%20%26%20Rio'))
// São Paulo & Rio
// Mixte : séquences valides décodées, invalides préservées
console.log(decodeUriComponent('Berlin%20Office%20%ZZ%20HQ'))
// Berlin Office %ZZ HQ ← %ZZ n'est pas hex valide — conservé tel quel
// Usage pratique dans un pipeline de traitement de logs
const rawSearchQueries = [
'standing%20desk%20ergonomic',
'price%3A%5B200+TO+800%5D',
'50%25+off', // ← lèverait une erreur avec decodeURIComponent (% isolé)
'wireless%20keyboard%20%26%20mouse',
]
const decoded = rawSearchQueries.map(q => decodeUriComponent(q))
console.log(decoded)
// ['standing desk ergonomic', 'price:[200+TO+800]', '50%25+off', 'wireless keyboard & mouse']Utilisez decodeURIComponent() avec try/catch pour le code applicatif où vous voulez savoir immédiatement si une entrée inattendue arrive. Faites appel à decode-uri-component dans les pipelines de données, les processeurs de logs et les handlers de webhooks où vous voulez continuer le traitement même quand certaines entrées contiennent un encodage invalide.
Gestion des chaînes encodées en pourcent malformées
Un % isolé, une séquence incomplète comme %A ou une paire invalide comme %GH font que decodeURIComponent() lève URIError: URI malformed. Toute entrée contrôlée par l'utilisateur — requêtes de recherche, fragments d'URL, champs de formulaire contenant des URL, paramètres de liens de campagnes e-mail — peut contenir ces séquences. Un wrapper sécurisé est indispensable pour tout code exposé à l'extérieur.
Wrappers de décodage sécurisé pour les scénarios courants
// ── 1. Décodage sécurisé basique — retourne la chaîne originale en cas d'erreur ─────────────
function safeDecode(encoded: string): string {
try {
return decodeURIComponent(encoded)
} catch {
return encoded // retourner l'entrée raw si le décodage échoue — ne jamais planter
}
}
// ── 2. Décodage sécurisé + traitement de + comme espace (pour les valeurs encodées comme formulaire) ─────────
function safeFormDecode(formEncoded: string): string {
try {
return decodeURIComponent(formEncoded.replace(/+/g, ' '))
} catch {
return formEncoded.replace(/+/g, ' ') // au moins remplacer + même si le reste échoue
}
}
// ── 3. Parser sécurisé de query string complète → objet plat ──────────────────────
function parseQueryString(queryString: string): Record<string, string> {
const result: Record<string, string> = {}
try {
const params = new URLSearchParams(queryString)
for (const [key, value] of params) {
result[key] = value // URLSearchParams gère tout le décodage en interne
}
} catch {
// Retourner silencieusement vide pour une entrée complètement malformée
}
return result
}
// Utilisation
console.log(safeDecode('S%C3%A3o%20Paulo')) // São Paulo
console.log(safeDecode('search%20for%2050%25+off')) // search for 50% off (% isolé)
// → en fait correct ; % ici est %25
console.log(safeDecode('malformed%string%')) // malformed%string% (sans erreur)
console.log(safeFormDecode('standing+desk+pro')) // standing desk pro
console.log(parseQueryString('q=hello+world&tag=node%20js&page=2'))
// { q: 'hello world', tag: 'node js', page: '2' }Erreurs courantes
J'ai vu ces quatre patterns provoquer des corruptions silencieuses de données ou des plantages inattendus en production — généralement seulement quand une valeur contient un caractère spécial, ce qui signifie qu'ils passent au travers des tests unitaires et surgissent avec de vraies données utilisateur.
Erreur 1 — Double décodage d'une valeur URLSearchParams
Problème : URLSearchParams.get() retourne une chaîne déjà décodée. Appeler decodeURIComponent() sur le résultat décode la valeur deux fois — transformant tout % restant dans la sortie décodée en %25, ce qui corrompt les données. Solution : utilisez la valeur de URLSearchParams.get()directement — aucun décodage supplémentaire n'est nécessaire.
// ❌ URLSearchParams a déjà décodé — décoder à nouveau corrompt les valeurs avec %
const qs = new URLSearchParams('rate=50%25&redirect=https%3A%2F%2Fdashboard.internal%2F')
const rawRate = qs.get('rate') // '50%' ← déjà décodé
const wrongRate = decodeURIComponent(rawRate)
// '50%25' ← % a été décodé en %25 lors du second passage — maintenant erroné à nouveau// ✅ Utiliser URLSearchParams.get() directement — le décodage est automatique
const qs = new URLSearchParams('rate=50%25&redirect=https%3A%2F%2Fdashboard.internal%2F')
const rate = qs.get('rate') // '50%' ← correct, aucune étape supplémentaire nécessaire
const redirect = qs.get('redirect') // 'https://dashboard.internal/'
const parsed = new URL(redirect!) // Sûr à construire — déjà décodé
console.log(parsed.hostname) // dashboard.internalErreur 2 — Ne pas décoder + comme espace pour les données encodées comme formulaire
Problème : Les soumissions de formulaires et certaines bibliothèques OAuth encodent les espaces en + (application/x-www-form-urlencoded). Appeler decodeURIComponent() sur ces données laisse + comme un signe plus littéral. Solution : utilisez URLSearchParams pour analyser les données encodées comme formulaire, ou remplacez + par un espace avant d'appeler decodeURIComponent().
// ❌ decodeURIComponent traite + comme un signe plus littéral, pas comme un espace
// L'endpoint de token OAuth envoie : grant_type=authorization_code&code=SplxlOBeZQQYb...
// &redirect_uri=https%3A%2F%2Fapp.example.com%2Fcallback
// Certaines implémentations OAuth legacy utilisent aussi + pour les espaces dans les codes
const formBody = 'customer_name=Thomas+Dupont&product=Standing+Desk+Pro&qty=2'
const [, rawVal] = formBody.split('&')[0].split('=')
const name = decodeURIComponent(rawVal)
console.log(name) // 'Thomas+Dupont' ← + non converti en espace// ✅ Utiliser URLSearchParams — suit la spécification application/x-www-form-urlencoded
const formBody = 'customer_name=Thomas+Dupont&product=Standing+Desk+Pro&qty=2'
const params = new URLSearchParams(formBody)
console.log(params.get('customer_name')) // 'Thomas Dupont' ← + correctement décodé comme espace
console.log(params.get('product')) // 'Standing Desk Pro'Erreur 3 — Utiliser decodeURI() pour des valeurs de paramètres de query individuels
Problème : decodeURI() ne décode pas %26 (&), %3D (=) ni %3F(?) — des caractères couramment encodés dans les valeurs de paramètres. L'utiliser pour décoder une seule valeur laisse ces séquences intactes, produisant silencieusement une sortie incorrecte. Solution : utilisez decodeURIComponent() pour les valeurs individuelles ; réservez decodeURI()pour les chaînes d'URL complètes.
// ❌ decodeURI ne décode pas & et = — la valeur reste brisée const encodedFilter = 'status%3Dactive%26tier%3Dpremium%26region%3Deu-west' const filter = decodeURI(encodedFilter) console.log(filter) // 'status%3Dactive%26tier%3Dpremium%26region%3Deu-west' ← %3D et %26 non décodés
// ✅ decodeURIComponent décode toutes les séquences y compris & et = const encodedFilter = 'status%3Dactive%26tier%3Dpremium%26region%3Deu-west' const filter = decodeURIComponent(encodedFilter) console.log(filter) // 'status=active&tier=premium®ion=eu-west' ← correctement décodé
Erreur 4 — Ne pas envelopper decodeURIComponent dans try/catch pour l'entrée utilisateur
Problème : Un %isolé non suivi de deux chiffres hexadécimaux — courant dans les requêtes de recherche tapées par les utilisateurs (“50% de réduction”, “100% coton”) — fait que decodeURIComponent() lève URIError: URI malformed, plantant le handler de requête si non capturé. Solution : enveloppez toujours dans try/catchquand l'entrée provient de données utilisateur, de fragments d'URL ou de systèmes externes.
// ❌ L'utilisateur a tapé '100% cotton' dans une boîte de recherche — le % isolé plante le serveur
// GET /api/search?q=100%25+cotton ← Ce cas précis est correct (%25 = %)
// GET /api/search?q=100%+cotton ← Celui-ci plante (% sans 2 chiffres hex suivants)
app.get('/api/search', (req, res) => {
const query = decodeURIComponent(req.query.q as string)
// ↑ lève URIError: URI malformed pour '100% cotton' → erreur 500 non gérée
})// ✅ Envelopper dans try/catch — revenir à l'entrée raw si le décodage échoue
app.get('/api/search', (req, res) => {
let query: string
try {
query = decodeURIComponent(req.query.q as string)
} catch {
query = req.query.q as string // utiliser la valeur raw plutôt que de planter
}
// continuer le traitement en sécurité — query est décodée ou raw
})decodeURIComponent vs decodeURI vs URLSearchParams — Comparaison rapide
| Méthode | Décode %XX | Décode + comme espace | Lève sur malformé | Décode & = ? # | Cas d'usage | Nécessite installation |
|---|---|---|---|---|---|---|
| decodeURIComponent() | ✅ tous | ❌ non | ✅ URIError | ✅ oui | Valeurs individuelles et segments de chemin | Non |
| decodeURI() | ✅ plupart | ❌ non | ✅ URIError | ❌ non | Chaînes d'URL complètes | Non |
| URLSearchParams | ✅ tous | ✅ oui | ❌ silencieux | ✅ oui | Analyse de query strings avec décodage automatique | Non |
| Constructeur URL | ✅ tous | ✅ oui | ✅ TypeError | ✅ oui | Analyse et normalisation d'URL complètes | Non |
| decode-uri-component | ✅ tous | ❌ non | ❌ silencieux | ✅ oui | Décodage par lot avec tolérance aux entrées malformées | npm install |
| querystring.unescape() | ✅ tous | ❌ non | ❌ silencieux | ✅ oui | Node.js legacy (obsolète depuis v16) | Non (intégré) |
Pour la grande majorité des cas, le choix se réduit à trois scénarios. Utilisez URLSearchParams pour analyser les query strings — il gère le décodage, la règle du + comme espace et les clés répétées automatiquement. Utilisez decodeURIComponent() (enveloppé dans try/catch) pour une seule valeur ou un segment de chemin, surtout quand vous attendez des barres obliques encodées en %2F dans un segment. Utilisez decodeURI()uniquement quand vous avez une chaîne d'URL complète avec des caractères non ASCII dans son chemin et que vous avez besoin que les caractères structurels (/ ? & =) restent encodés dans la sortie.
Questions fréquentes
Outils associés
Pour un décodage en un clic sans écrire de code, collez votre chaîne encodée en pourcent directement dans le Décodeur d'URL de ToolDeck — il décode instantanément dans le navigateur, avec le résultat prêt à copier dans votre code ou terminal.
Alex is a front-end and Node.js developer with extensive experience building web applications and developer tooling. He is passionate about web standards, browser APIs, and the JavaScript ecosystem. In his spare time he contributes to open-source projects and writes about modern JavaScript patterns, performance optimisation, and everything related to the web platform.
Marcus specialises in JavaScript performance, build tooling, and the inner workings of the V8 engine. He has spent years profiling and optimising React applications, working on bundler configurations, and squeezing every millisecond out of critical rendering paths. He writes about Core Web Vitals, JavaScript memory management, and the tools developers reach for when performance really matters.