URL Decode JavaScript — decodeURIComponent()

·Front-end & Node.js Developer·Revisado porMarcus Webb·Publicado

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.

Before · text
After · text
// 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

JavaScript (browser / Node.js)
// 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

JavaScript
// 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'))    // platform

Decodificación de segmentos de ruta no ASCII y Unicode

JavaScript
// 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)
Nota: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:

CodificadoCarácterdecodeURIComponent()decodeURI()URLSearchParams
%20espacioespacio ✅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

JavaScript
// 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
Nota:Prefiere el constructor 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

JavaScript (browser / Node.js 10+)
// 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=pending

Análisis de parámetros de consulta desde la URL actual del navegador

JavaScript (browser)
// 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 furniture

Decodificació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

JavaScript (Node.js 10+)
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

JavaScript (Node.js)
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

JavaScript (Node.js 10+)
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.

bash
# ── 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.

bash
npm install decode-uri-component
# o
pnpm add decode-uri-component
JavaScript (Node.js)
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

JavaScript
// ── 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.

Before · JavaScript
After · JavaScript
// ❌ 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.internal

Error 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().

Before · JavaScript
After · JavaScript
// ❌ 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.

Before · JavaScript
After · JavaScript
// ❌ 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&region=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.

Before · JavaScript
After · JavaScript
// ❌ 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étodoDecodifica %XXDecodifica + como espacioLanza con malformadoDecodifica & = ? #Caso de usoRequiere instalación
decodeURIComponent()✅ todos❌ no✅ URIError✅ síValores individuales y segmentos de rutaNo
decodeURI()✅ mayoría❌ no✅ URIError❌ noCadenas de URL completasNo
URLSearchParams✅ todos✅ sí❌ silencioso✅ síAnálisis de cadenas de consulta con decodificación automáticaNo
Constructor URL✅ todos✅ sí✅ TypeError✅ síAnálisis y normalización de URLs completasNo
decode-uri-component✅ todos❌ no❌ silencioso✅ síDecodificación por lotes con tolerancia a entrada malformadanpm 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

¿Cuál es la diferencia entre decodeURIComponent() y decodeURI() en JavaScript?
decodeURIComponent() decodifica todas las secuencias codificadas en porcentaje de la cadena, incluidos los caracteres que tienen significado estructural en una URL — & (%26), = (%3D), ? (%3F), # (%23), / (%2F) y : (%3A). Está diseñada para decodificar valores de parámetros de consulta individuales o segmentos de ruta. decodeURI() preserva esos caracteres estructurales — no decodifica %26, %3D, %3F, %23, %2F ni %3A — porque está diseñada para sanear una URL completa sin romper su estructura de cadena de consulta. Usar decodeURI() en un único valor de parámetro que contiene un & o = codificado dejará silenciosamente esas secuencias sin decodificar, produciendo una salida incorrecta.
¿Por qué decodeURIComponent() lanza un URIError para algunas cadenas?
decodeURIComponent() lanza URIError: URI malformed cuando la entrada contiene un % no seguido exactamente de dos dígitos hexadecimales válidos. Causas comunes: un % suelto al final de una cadena ("50% de descuento" escrito por un usuario), una secuencia incompleta ("%A") o un par no hexadecimal ("%GH"). Esto ocurre con mayor frecuencia en consultas de búsqueda escritas por usuarios o en valores pegados de texto que nunca fue pensado para ser codificado en URL. La solución es envolver decodeURIComponent() en un bloque try/catch y devolver la cadena raw en caso de error. El paquete npm decode-uri-component ofrece la misma solución sin requerir un wrapper try/catch.
¿URLSearchParams decodifica automáticamente los valores codificados en porcentaje?
Sí. Cada valor devuelto por URLSearchParams.get() y URLSearchParams.getAll() está completamente decodificado — nunca debes llamar a decodeURIComponent() sobre su salida. URLSearchParams también sigue la especificación application/x-www-form-urlencoded, que decodifica + como espacio, lo que lo hace correcto para los envíos de formularios HTML y las respuestas de tokens OAuth. El único caso en que URLSearchParams no puede ayudar es decodificar un valor codificado independiente que no forma parte de una cadena de consulta — para eso, usa decodeURIComponent() con un try/catch.
¿Cómo decodifico el signo + como espacio en JavaScript?
decodeURIComponent() trata + como un signo más literal — no convierte + en espacio. Esto es intencional: + como espacio es una convención de application/x-www-form-urlencoded, separada del estándar de codificación porcentual. Para decodificar + como espacio, usa URLSearchParams (que sigue la especificación de codificación de formularios) o reemplaza + antes de llamar a decodeURIComponent(): decodeURIComponent(str.replace(/\+/g, ' ')). Nota que reemplazar + por %20 antes de decodificar también funciona pero es ligeramente más verboso. Prefiere siempre URLSearchParams para analizar cadenas de consulta completas — maneja tanto %20 como + correctamente.
¿Cómo decodifico una URL en Node.js?
Usa las mismas funciones globales disponibles en todos los navegadores — no se necesita ninguna importación en Node.js 10+. decodeURIComponent() para valores individuales, decodeURI() para cadenas de URL completas, URLSearchParams para cadenas de consulta. Para URLs de solicitudes HTTP entrantes, usa new URL(req.url, 'http://localhost') — el constructor URL normaliza la URL y expone .searchParams (decodificado automáticamente) y .pathname (decodificado a nivel URL). La función más antigua querystring.unescape() todavía existe pero está obsoleta desde Node.js 16 — prefiere decodeURIComponent() en su lugar.
¿Cómo analizo una cadena de consulta de URL en un objeto en JavaScript?
Usa URLSearchParams con Object.fromEntries(): const params = Object.fromEntries(new URLSearchParams(queryString)). Esto da un objeto plano con todas las claves de parámetros y los valores decodificados. Nota que Object.fromEntries() conserva solo el último valor para claves duplicadas — para claves repetidas como tag=one&tag=two, usa URLSearchParams.getAll('tag') en su lugar. En el navegador, new URLSearchParams(window.location.search) analiza automáticamente la cadena de consulta de la página actual. En Node.js, analiza desde new URL(req.url, base).searchParams.

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.

AC
Alex ChenFront-end & Node.js Developer

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.

MW
Marcus WebbRevisor técnico

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.