URL Decode JavaScript — decodeURIComponent()
Usa el Decodificador de URL Online gratuito directamente en tu navegador — sin instalación.
Probar Decodificador de URL Online online →Las cadenas codificadas en porcentaje aparecen constantemente en el código JavaScript — una consulta de búsqueda llega como q=standing+desk%26price%3A200, una redirección OAuth como next=https%3A%2F%2Fdashboard.internal%2F, una ruta de almacenamiento como reports%2F2025%2Fq1.pdf. Cómo decodificar una URL en JavaScript se reduce a elegir la función integrada correcta entre tres opciones: decodeURIComponent(), decodeURI() y URLSearchParams — y la elección entre ellas es la causa principal de la mayoría de las corrupciones silenciosas de datos que he visto en bases de código de producción, especialmente el caso límite del + como espacio y la doble decodificación. Para una decodificación rápida sin escribir código, el Decodificador de URL de ToolDeck lo gestiona al instante en el navegador. Este tutorial de decodificación de URL en JavaScript cubre las tres funciones en profundidad (ES2015+ / Node.js 10+): cuándo usar cada una, cómo difieren para los espacios y caracteres reservados, decodificación desde archivos y solicitudes HTTP, manejo seguro de errores y los cuatro errores que causan los bugs de producción más sutiles.
- ✓decodeURIComponent() decodifica todas las secuencias codificadas en porcentaje — es la opción correcta para valores de parámetros de consulta individuales y segmentos de ruta
- ✓decodeURI() preserva los caracteres estructurales de la URI como / ? & = # : — úsalo solo al decodificar una cadena de URL completa, nunca para valores individuales
- ✓URLSearchParams.get() devuelve valores ya decodificados — llamar a decodeURIComponent() sobre su resultado causa doble decodificación
- ✓decodeURIComponent() NO decodifica + como espacio — para datos codificados como formulario (application/x-www-form-urlencoded), usa URLSearchParams o reemplaza + antes de decodificar
- ✓Envuelve siempre decodeURIComponent() en try/catch cuando la entrada proviene de datos del usuario — un % suelto o una secuencia incompleta lanza un URIError
¿Qué es la decodificación de URL?
La codificación porcentual (formalmente definida en RFC 3986) reemplaza los caracteres que son inseguros o estructuralmente significativos en una URL con un % seguido de dos dígitos hexadecimales — el valor en bytes UTF-8 del carácter. La decodificación de URL invierte esta transformación: cada secuencia %XX se convierte de vuelta a su byte original, y la secuencia de bytes resultante se interpreta como texto UTF-8. Un espacio decodificado de %20, una barra de %2F, el carácter no ASCII ü de %C3%BC (su representación UTF-8 de dos bytes).
Los caracteres que nunca se codifican — llamados caracteres no reservados — son las letras A–Z y a–z, los dígitos 0–9 y - _ . ~. Todo lo demás tiene un papel estructural en la URL (como / separando segmentos de ruta o & separando parámetros de consulta) o debe codificarse cuando se usa como dato. El resultado práctico: un filtro de búsqueda como status=active&tier=premium codificado como valor de un único parámetro de consulta llega sin parecerse en nada al original.
// Codificado en porcentaje — tal como se recibe en una solicitud HTTP o payload de webhook "q=price%3A%5B200+TO+800%5D%20AND%20brand%3ANorthwood%20%26%20status%3Ain-stock"
// Tras la decodificación de URL — el filtro original de Elasticsearch "q=price:[200 TO 800] AND brand:Northwood & status:in-stock"
decodeURIComponent() — La función estándar para decodificar valores
decodeURIComponent() es el motor de la decodificación de URL en JavaScript. Decodifica cada secuencia %XX en la cadena de entrada — incluidos los caracteres que tienen significado estructural en una URL, como %2F (barra), %3F (interrogación), %26 (ampersand) y %3D (signo igual). Esto la convierte en la opción correcta para decodificar valores de parámetros de consulta individuales y segmentos de ruta, pero en la opción incorrecta para decodificar una URL completa — donde esos caracteres estructurales deben permanecer codificados. Es una función global: no se necesita ninguna importación en ningún entorno JavaScript.
Ejemplo mínimo funcional
// Decodificar valores de parámetros de consulta individuales — el caso más común
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]'
// Nota: + NO se decodifica como espacio — ver la sección + en la tabla de comparación
console.log(city) // São Paulo
console.log(district) // Itáim Bibi
console.log(category) // office furniture
console.log(filter) // price:[200+TO+800]Decodificación de una URL de redirección extraída de un parámetro de consulta
// La URL de redirección fue codificada en porcentaje al insertarse como valor de parámetro
// lado receptor: extraerla y usarla — no se necesita decodificación manual con 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') // Decodificado automáticamente por URLSearchParams
const sessionId = url.searchParams.get('session_id') // 'sid_7x9p2k'
// rawNext ya está decodificado: 'https://dashboard.internal/reports?view=weekly&team=platform'
// NO llamar a decodeURIComponent(rawNext) de nuevo — eso sería doble decodificación
const nextUrl = new URL(rawNext!)
console.log(nextUrl.hostname) // dashboard.internal
console.log(nextUrl.searchParams.get('view')) // weekly
console.log(nextUrl.searchParams.get('team')) // platformDecodificación de segmentos de ruta no ASCII y Unicode
// API REST con segmentos de ruta internacionalizados
// Cada byte UTF-8 del carácter original fue codificado en porcentaje por separado
const encodedSegments = [
'%E6%9D%B1%E4%BA%AC', // 東京 (Tokyo) — 3 bytes por carácter
'M%C3%BCnchen', // München — ü codificada como 2 bytes
'caf%C3%A9', // café — é como NFC precompuesto
'S%C3%A3o%20Paulo', // São Paulo
]
encodedSegments.forEach(seg => {
console.log(decodeURIComponent(seg))
})
// 東京
// München
// café
// São Paulo
// Extrayendo una clave de archivo de una URL de API de almacenamiento
// La clave del objeto contenía / por lo que fue codificada como %2F dentro del segmento de ruta
const storageUrl = 'https://storage.api.example.com/v1/objects/reports%2F2025%2Fq1-financials.pdf'
const rawKey = new URL(storageUrl).pathname.replace('/v1/objects/', '')
// .pathname decodifica la codificación a nivel URL pero %2F (como %252F a nivel URL) permanece
// Usar decodeURIComponent para el paso final:
const fileKey = decodeURIComponent(rawKey) // 'reports/2025/q1-financials.pdf'
console.log(fileKey)decodeURIComponent() lanza un URIError cuando la entrada contiene un % no seguido de dos dígitos hexadecimales válidos — por ejemplo un % suelto al final de una cadena o una secuencia como %GH. Envuélvelo siempre en try/catch al decodificar entrada suministrada por el usuario. Los patrones de decodificación segura se tratan en la sección de manejo de errores más abajo.Funciones de decodificación de URL en JavaScript — Referencia de caracteres
Las tres funciones de decodificación integradas difieren exactamente en qué secuencias codificadas decodifican. La tabla muestra el comportamiento para los caracteres que más importan en la práctica:
| Codificado | Carácter | decodeURIComponent() | decodeURI() | URLSearchParams |
|---|---|---|---|---|
| %20 | espacio | espacio ✅ | espacio ✅ | espacio ✅ |
| + | más (formulario) | + (conservado) | + (conservado) | espacio ✅ |
| %2B | + literal | + ✅ | + ✅ | + ✅ |
| %26 | & | & ✅ | & (conservado) ❌ | & ✅ |
| %3D | = | = ✅ | = (conservado) ❌ | = ✅ |
| %3F | ? | ? ✅ | ? (conservado) ❌ | ? ✅ |
| %23 | # | # ✅ | # (conservado) ❌ | # ✅ |
| %2F | / | / ✅ | / (conservado) ❌ | / ✅ |
| %3A | : | : ✅ | : (conservado) ❌ | : ✅ |
| %40 | @ | @ ✅ | @ (conservado) ❌ | @ ✅ |
| %25 | % | % ✅ | % ✅ | % ✅ |
| %C3%BC | ü | ü ✅ | ü ✅ | ü ✅ |
Las dos filas críticas son + y los caracteres estructurales (%26, %3D, %3F). URLSearchParams decodifica + como espacio porque sigue la especificación application/x-www-form-urlencoded — correcto para los envíos de formularios HTML, pero diferente de lo que hace decodeURIComponent() con el mismo carácter. Y decodeURI() omite silenciosamente %26, %3D y %3F — lo que parece correcto hasta que un valor contiene realmente un ampersand o signo igual codificado.
decodeURI() — Decodificar una URL completa sin romper su estructura
decodeURI() es el complemento de encodeURI(). Decodifica una cadena de URL completa preservando los caracteres que tienen significado estructural en una URI: ; , / ? : @ & = + $ #. Estos se dejan en su forma codificada en porcentaje (o como caracteres literales si aparecieron sin codificar en la entrada). Esto hace que decodeURI() sea seguro para sanear URLs completas que pueden contener caracteres no ASCII en la ruta o el nombre de host, sin colapsar accidentalmente la estructura de la cadena de consulta.
Saneamiento de una URL con segmentos de ruta no ASCII
// Una URL de CDN con segmentos de ruta internacionalizados y una cadena de consulta estructurada // decodeURI() decodifica la ruta no ASCII pero preserva ? & = intactos 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 // ↑ No ASCII decodificado; ? & = preservados — la URL sigue siendo estructuralmente válida // decodeURIComponent destruiría la URL — : / ? & = todos decodificados a la vez const broken = decodeURIComponent(encodedUrl) // 'https://cdn.example.com/assets/東京/2025/q1-report.pdf?token=eyJ0eXAiOiJKV1QiLCJhbGci&expires=1735689600' // Parece igual aquí, pero una URL como 'https%3A%2F%2F...' quedaría destruida
URL sobre decodeURI() cuando también necesites acceder a los componentes individuales de la URL. new URL(str) normaliza la entrada, valida su estructura y expone .pathname, .searchParams y .hostname como propiedades ya decodificadas. Reserva decodeURI() para los casos en que solo necesites un resultado en cadena y no puedas usar el constructor URL (por ejemplo, en entornos muy antiguos de Node.js 6 sin la clase global URL).URLSearchParams — Decodificación automática para cadenas de consulta
URLSearchParams es la forma idiomática de analizar cadenas de consulta en JavaScript moderno. Cada valor devuelto por .get(), .getAll() o iteración está automáticamente decodificado — incluyendo + como espacio para datos codificados como formulario. Está disponible globalmente en todos los navegadores modernos y Node.js 10+, no requiere importación y maneja los casos límite que el dividido manual de cadenas hace mal.
Análisis de una cadena de consulta entrante
// Analizando una cadena de consulta de callback de webhook o redirección 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' ← + decodificado como espacio
console.log(params.get('filter')) // 'price:[200+TO+800]' ← %3A y %5B decodificados
console.log(params.getAll('tag')) // ['ergonomic', 'adjustable']
console.log(params.get('redirect')) // 'https://dashboard.internal/orders?view=pending'
// Iterando todos los parámetros
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=pendingAnálisis de parámetros de consulta desde la URL actual del navegador
// URL actual: 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 (+ decodificado automáticamente)
console.log(filters.category) // office furnitureDecodificación de datos codificados en URL desde archivos y respuestas de API
Dos escenarios aparecen constantemente en proyectos reales: procesar un archivo en disco que contiene datos codificados en porcentaje (registros de acceso, exportaciones de datos, archivos de captura de webhooks) y analizar la URL de una solicitud HTTP entrante en un servidor Node.js. Ambos siguen el mismo principio — usa el constructor URL o URLSearchParams en lugar del dividido manual de cadenas — pero los detalles difieren.
Lectura y decodificación de registros codificados en URL desde un archivo
import { createReadStream } from 'fs'
import { createInterface } from 'readline'
// Archivo: orders-export.txt — un registro codificado en URL por línea
// customer_name=Carlos+Mendoza&order_id=ord_9c2f4a&product=Standing+Desk+Pro&total=149.99%20EUR
// customer_name=Ana+Torres&order_id=ord_7b3a1c&product=Ergonomic+Chair&total=89.00%20EUR
// customer_name=Pedro+Garc%C3%ADa&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 decodifica + como espacio y secuencias %XX automáticamente
const params = new URLSearchParams(line)
orders.push({
customerName: params.get('customer_name'), // 'Carlos Mendoza'
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: 'Carlos Mendoza', orderId: 'ord_9c2f4a', product: 'Standing Desk Pro', total: '149.99 EUR' }Análisis de un log de acceso Nginx para decodificar consultas de búsqueda
import { createReadStream } from 'fs'
import { createInterface } from 'readline'
// Las líneas del log de acceso de Nginx tienen este aspecto:
// 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) {
// Extraer la ruta de la solicitud de la línea del 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 decodifica automáticamente
} catch {
// Omitir líneas malformadas — los logs de acceso pueden contener entradas truncadas
}
}
return queries
}
const queries = await extractSearchQueries('/var/log/nginx/access.log')
console.log(queries)
// ['standing desk&brand:Northwood', 'ergonomic chair', 'monitor arm 27 inch']Análisis de parámetros de consulta en un servidor HTTP de 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) => {
// Construir una URL completa — el segundo argumento es la base requerida por el constructor 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)Cuando necesites inspeccionar una URL codificada durante el desarrollo — para entender qué envía un webhook antes de escribir el código de análisis — pégala directamente en el Decodificador de URL de ToolDeck para ver la forma decodificada al instante sin ejecutar un script.
Decodificación de URL desde la línea de comandos
Para scripts de shell, pipelines de CI o inspección rápida de cadenas codificadas, varios enfoques funcionan sin escribir un script completo. Los one-liners de Node.js son multiplataforma; en macOS y Linux, python3 también está siempre disponible.
# ── One-liners de Node.js ──────────────────────────────────────────────────
# Decodificar un único valor codificado en porcentaje
node -e "console.log(decodeURIComponent(process.argv[1]))" "S%C3%A3o%20Paulo%20%26%20Rio"
# São Paulo & Rio
# Analizar una cadena de consulta e imprimir cada par clave=valor (decodificado)
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
# Decodificar e imprimir con formato un cuerpo JSON codificado en URL (común en depuración 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 de Python (disponible en la mayoría de sistemas macOS/Linux) ─────────────
# unquote_plus también decodifica + como espacio — correcto para datos codificados como formulario
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 — registrar y decodificar una URL de redirección desde una cabecera de respuesta ──────────
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))
"Decodificación tolerante a fallos con decode-uri-component
El decodeURIComponent() integrado lanza un URIError en cualquier secuencia malformada — incluido un % final sin dos dígitos hexadecimales siguientes. En código de producción que procesa URLs suministradas por el usuario o de terceros — logs de consultas de búsqueda, URLs de clics de campañas de email, datos web scrapeados — las secuencias malformadas son suficientemente comunes como para causar errores. El paquete decode-uri-component (~30M de descargas semanales en npm) maneja las secuencias malformadas de forma tolerante devolviendo la secuencia original sin cambios en lugar de lanzar un error.
npm install decode-uri-component # o pnpm add decode-uri-component
import decodeUriComponent from 'decode-uri-component'
// La función nativa lanza un error con entrada malformada — puede hacer caer un manejador de solicitudes
try {
decodeURIComponent('product%name%') // ❌ URIError: URI malformed
} catch (e) {
console.error('Nativa lanzó:', (e as Error).message)
}
// decode-uri-component devuelve la secuencia raw en lugar de lanzar
console.log(decodeUriComponent('product%name%'))
// product%name% ← las secuencias malformadas se dejan tal cual, sin lanzar error
// Las secuencias válidas se decodifican correctamente
console.log(decodeUriComponent('S%C3%A3o%20Paulo%20%26%20Rio'))
// São Paulo & Rio
// Mixto: las secuencias válidas se decodifican, las inválidas se preservan
console.log(decodeUriComponent('Berlin%20Office%20%ZZ%20HQ'))
// Berlin Office %ZZ HQ ← %ZZ no es hexadecimal válido — se conserva tal cual
// Uso práctico en un pipeline de procesamiento de logs
const rawSearchQueries = [
'standing%20desk%20ergonomic',
'price%3A%5B200+TO+800%5D',
'50%25+off', // ← lanzaría un error con decodeURIComponent (% suelto)
'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']Usa decodeURIComponent() con try/catch en código de aplicación donde quieres saber de inmediato si llega entrada inesperada. Recurre a decode-uri-component en pipelines de datos, procesadores de logs y manejadores de webhooks donde quieras seguir procesando incluso cuando algunas entradas contienen codificación inválida.
Manejo de cadenas codificadas en porcentaje malformadas
Un % suelto, una secuencia incompleta como %A o un par inválido como %GH hacen que decodeURIComponent() lance URIError: URI malformed. Cualquier entrada controlada por el usuario — consultas de búsqueda, fragmentos de URL, campos de formulario que contienen URLs, parámetros de enlaces de campañas de email — puede contener estas secuencias. Un wrapper seguro es esencial para cualquier código expuesto externamente.
Wrappers de decodificación segura para escenarios comunes
// ── 1. Decodificación segura básica — devuelve la cadena original si hay error ─────────────
function safeDecode(encoded: string): string {
try {
return decodeURIComponent(encoded)
} catch {
return encoded // devolver la entrada raw si la decodificación falla — nunca hacer crash
}
}
// ── 2. Decodificación segura + manejo de + como espacio (para valores codificados como formulario) ─────────
function safeFormDecode(formEncoded: string): string {
try {
return decodeURIComponent(formEncoded.replace(/+/g, ' '))
} catch {
return formEncoded.replace(/+/g, ' ') // al menos reemplazar + aunque el resto falle
}
}
// ── 3. Parser seguro de cadena de consulta completa → objeto plano ──────────────────────
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 maneja toda la decodificación internamente
}
} catch {
// Devolver vacío silenciosamente ante entrada completamente malformada
}
return result
}
// Uso
console.log(safeDecode('S%C3%A3o%20Paulo')) // São Paulo
console.log(safeDecode('search%20for%2050%25+off')) // search for 50% off (% suelto)
// → en realidad está bien; % aquí es %25
console.log(safeDecode('malformed%string%')) // malformed%string% (sin lanzar error)
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' }Errores comunes
He visto estos cuatro patrones causar corrupción silenciosa de datos o errores inesperados en producción — normalmente solo cuando un valor contiene un carácter especial, lo que significa que se cuelan en las pruebas unitarias y afloran con datos reales de usuarios.
Error 1 — Doble decodificación de un valor de URLSearchParams
Problema: URLSearchParams.get() devuelve una cadena ya decodificada. Llamar a decodeURIComponent() sobre su resultado decodifica el valor dos veces — convirtiendo cualquier % restante en la salida decodificada en %25, lo que corrompe los datos. Solución: usa el valor de URLSearchParams.get() directamente — no se necesita decodificación adicional.
// ❌ URLSearchParams ya decodificó — decodificar de nuevo corrompe valores con %
const qs = new URLSearchParams('rate=50%25&redirect=https%3A%2F%2Fdashboard.internal%2F')
const rawRate = qs.get('rate') // '50%' ← ya decodificado
const wrongRate = decodeURIComponent(rawRate)
// '50%25' ← el % se convirtió en %25 en el segundo paso — ahora está mal otra vez// ✅ Usar URLSearchParams.get() directamente — la decodificación es automática
const qs = new URLSearchParams('rate=50%25&redirect=https%3A%2F%2Fdashboard.internal%2F')
const rate = qs.get('rate') // '50%' ← correcto, no se necesita paso extra
const redirect = qs.get('redirect') // 'https://dashboard.internal/'
const parsed = new URL(redirect!) // Seguro para construir — ya decodificado
console.log(parsed.hostname) // dashboard.internalError 2 — No decodificar + como espacio para datos codificados como formulario
Problema: Los envíos de formularios y algunas librerías OAuth codifican los espacios como + (application/x-www-form-urlencoded). Llamar a decodeURIComponent() sobre estos datos deja + como un signo más literal. Solución: usa URLSearchParams para analizar datos codificados como formulario, o reemplaza + con un espacio antes de llamar a decodeURIComponent().
// ❌ decodeURIComponent trata + como un signo más literal, no como espacio
// El endpoint de token OAuth envía: grant_type=authorization_code&code=SplxlOBeZQQYb...
// &redirect_uri=https%3A%2F%2Fapp.example.com%2Fcallback
// Algunas implementaciones OAuth heredadas también usan + para espacios en códigos
const formBody = 'customer_name=Carlos+Mendoza&product=Standing+Desk+Pro&qty=2'
const [, rawVal] = formBody.split('&')[0].split('=')
const name = decodeURIComponent(rawVal)
console.log(name) // 'Carlos+Mendoza' ← + no convertido a espacio// ✅ Usar URLSearchParams — sigue la especificación application/x-www-form-urlencoded
const formBody = 'customer_name=Carlos+Mendoza&product=Standing+Desk+Pro&qty=2'
const params = new URLSearchParams(formBody)
console.log(params.get('customer_name')) // 'Carlos Mendoza' ← + correctamente decodificado como espacio
console.log(params.get('product')) // 'Standing Desk Pro'Error 3 — Usar decodeURI() para valores de parámetros de consulta individuales
Problema: decodeURI() no decodifica %26 (&), %3D (=) ni %3F (?) — caracteres comúnmente codificados dentro de los valores de parámetros. Usarlo para decodificar un único valor deja esas secuencias intactas, produciendo silenciosamente una salida incorrecta. Solución: usa decodeURIComponent() para valores individuales; reserva decodeURI() para cadenas de URL completas.
// ❌ decodeURI no decodifica & y = — el valor sigue roto const encodedFilter = 'status%3Dactive%26tier%3Dpremium%26region%3Deu-west' const filter = decodeURI(encodedFilter) console.log(filter) // 'status%3Dactive%26tier%3Dpremium%26region%3Deu-west' ← %3D y %26 no decodificados
// ✅ decodeURIComponent decodifica todas las secuencias incluidos & y = const encodedFilter = 'status%3Dactive%26tier%3Dpremium%26region%3Deu-west' const filter = decodeURIComponent(encodedFilter) console.log(filter) // 'status=active&tier=premium®ion=eu-west' ← correctamente decodificado
Error 4 — No envolver decodeURIComponent en try/catch para entrada del usuario
Problema: Un %suelto no seguido de dos dígitos hexadecimales — común en consultas de búsqueda escritas por usuarios (“50% de descuento”, “100% algodón”) — hace que decodeURIComponent() lance URIError: URI malformed, haciendo caer el manejador de solicitudes si no se captura. Solución: envuelve siempre en try/catch cuando la entrada proviene de datos del usuario, fragmentos de URL o sistemas externos.
// ❌ El usuario escribió '100% algodón' en un cuadro de búsqueda — el % suelto hace caer el servidor
// GET /api/search?q=100%25+cotton ← Este caso específico está bien (%25 = %)
// GET /api/search?q=100%+cotton ← Este hace crash (% sin seguir de 2 dígitos hex)
app.get('/api/search', (req, res) => {
const query = decodeURIComponent(req.query.q as string)
// ↑ lanza URIError: URI malformed para '100% cotton' → error 500 no manejado
})// ✅ Envolver en try/catch — recurrir a la entrada raw si la decodificación falla
app.get('/api/search', (req, res) => {
let query: string
try {
query = decodeURIComponent(req.query.q as string)
} catch {
query = req.query.q as string // usar el valor raw en lugar de hacer crash
}
// continuar procesando de forma segura — query está decodificada o es raw
})decodeURIComponent vs decodeURI vs URLSearchParams — Comparación rápida
| Método | Decodifica %XX | Decodifica + como espacio | Lanza con malformado | Decodifica & = ? # | Caso de uso | Requiere instalación |
|---|---|---|---|---|---|---|
| decodeURIComponent() | ✅ todos | ❌ no | ✅ URIError | ✅ sí | Valores individuales y segmentos de ruta | No |
| decodeURI() | ✅ mayoría | ❌ no | ✅ URIError | ❌ no | Cadenas de URL completas | No |
| URLSearchParams | ✅ todos | ✅ sí | ❌ silencioso | ✅ sí | Análisis de cadenas de consulta con decodificación automática | No |
| Constructor URL | ✅ todos | ✅ sí | ✅ TypeError | ✅ sí | Análisis y normalización de URLs completas | No |
| decode-uri-component | ✅ todos | ❌ no | ❌ silencioso | ✅ sí | Decodificación por lotes con tolerancia a entrada malformada | npm install |
| querystring.unescape() | ✅ todos | ❌ no | ❌ silencioso | ✅ sí | Node.js heredado (obsoleto desde v16) | No (integrado) |
Para la gran mayoría de casos, la elección se reduce a tres escenarios. Usa URLSearchParams para analizar cadenas de consulta — maneja la decodificación, la regla del + como espacio y las claves repetidas automáticamente. Usa decodeURIComponent() (envuelto en try/catch) para un único valor o segmento de ruta, especialmente cuando esperas barras codificadas como %2F dentro de un segmento. Usa decodeURI() solo cuando tienes una cadena de URL completa con caracteres no ASCII en su ruta y necesitas que los caracteres estructurales (/ ? & =) permanezcan codificados en la salida.
Preguntas frecuentes
Herramientas relacionadas
Para decodificar con un solo clic sin escribir ningún código, pega tu cadena codificada en porcentaje directamente en el Decodificador de URL de ToolDeck — decodifica al instante en el navegador, con el resultado listo para copiar en tu código o 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.