Guide
Codificación de URL Explicada: percent-encoding, query strings y los errores comunes
La mitad de los errores en el manejo de URL provienen de codificar la parte equivocada, o codificarla dos veces.
By Buğra SözeriPublished
La codificación de URL es uno de esos temas que parece trivial hasta que producción empieza a devolver 400 Bad Request en cada búsqueda que contiene un acento francés. Las reglas son simples, pero están dispersas en cuatro especificaciones que no están completamente de acuerdo, y los helpers de JavaScript tienen nombres que inducen activamente a error. Esta guía reúne las reglas en un solo lugar y marca los lugares donde los estándares discrepan silenciosamente.
Qué significa realmente la “codificación de URL”
Las URL están restringidas a un pequeño subconjunto de ASCII. Cualquier carácter fuera de ese conjunto, o cualquier carácter que tenga un significado estructural que el analizador interpretaría de otro modo, tiene que representarse como uno o más bytes prefijados por %. Eso es el percent-encoding, y las reglas para qué codificar provienen de RFC 3986. Puedes probar cualquiera de los ejemplos a continuación con nuestro codificador de URL.
RFC 3986 divide los caracteres en tres grupos. El conjunto no reservado es A-Z a-z 0-9 - . _ ~; estos siempre son seguros y nunca necesitan codificación. El conjunto reservado (: / ? # [ ] @ ! $ & ' ( ) * + , ; =) tiene significado estructural en alguna parte de la URL; codifícalos cuando quieras el carácter literal, déjalos solos cuando quieras el delimitador. Todo lo demás, incluido el espacio, ", <, >, { y todo lo no ASCII, debe estar percent-encoded.
Dónde cada carácter necesita codificarse
El mismo byte puede o no necesitar codificación dependiendo de en qué componente de la URL aparece. Un segmento de ruta permite : y @ sin codificar; un valor de query no necesita codificar / ni ?(porque el analizador se detiene en #); un fragmento es el componente más permisivo de todos. En la práctica, casi todos ignoran esas distinciones y codifican todo lo que está fuera del conjunto no reservado cuando interpolan un valor en una URL. Eso es conservador y correcto.
El único lugar donde importa: no codifiques los caracteres / que separan tus segmentos de ruta. Codifica cada segmento y luego únelos con /. Si codificas también la barra, obtienes %2F en la ruta, que la mayoría de los servidores rechazará por razones de seguridad (de lo contrario, permitiría a los atacantes colar secuencias .. más allá de los guardias de directorio).
encodeURI vs encodeURIComponent en JavaScript
JavaScript incluye dos codificadores, y la diferencia entre ellos es la fuente de una enorme cantidad de tiempo de depuración desperdiciado.
encodeURI(value)asume que value ya es una URL completa. Deja los caracteres reservados (; / ? : @ & = + $ , #) intactos para que la URL siga siendo analizable. Codifica espacios, acentos y el pequeño conjunto de caracteres que nunca están permitidos en ningún lugar de una URL.encodeURIComponent(value)asume que value es un único componente que se insertará en una URL, normalmente un segmento de ruta o un valor de query. Codifica todo excepto el conjunto no reservado más! ' ( ) *.
La regla general: si estás construyendo una URL por concatenación, usa encodeURIComponent en cada valor interpolado. Si estás normalizando una URL ya construida, encodeURI es la herramienta correcta. Casi nunca querrás aplicar ambas.
Datos de formulario: la excepción +/espacio
Los envíos de formularios HTML se codifican con el tipo de medio application/x-www-form-urlencoded, definido en el estándar WHATWG URL. Ese formato diverge de RFC 3986 en dos aspectos. Primero, los espacios se convierten en + en lugar de %20. Segundo, un + literal en el valor se codifica como %2B para que el decodificador pueda distinguirlos.
Tanto %20 como + se decodifican a un espacio en la práctica: todos los analizadores del lado del servidor convencionales aceptan ambos en una query string. Pero los emisores deben ser consistentes. Usa +en cuerpos de formularios y en query strings que imitan cuerpos de formularios; usa %20 en otros lugares, especialmente en segmentos de ruta donde + es un signo de más literal.
Arrays y objetos anidados: no hay un estándar
Las query strings fueron diseñadas para pares clave=valor planos. Las estructuras de datos anidadas tienen que aplanarse, y la regla de aplanamiento es una decisión por framework. Las cuatro convenciones de uso generalizado:
- Claves repetidas.
?tag=js&tag=tsse convierte en un array en el servidor. La convención más simple; la predeterminada en Express,net/urlde Go y la mayoría de los analizadores Node. - Corchetes estilo PHP.
?tag[]=js&tag[]=tspara arrays;?user[name]=alexpara objetos anidados. Los corchetes deben estar percent-encoded (%5B/%5D) para ser estrictamente conformes con los estándares, aunque la mayoría de los servidores aceptan la forma sin codificar. - Notación de punto de Rails.
?user.name=alex&user.age=30. Menos común hoy; aparece en algunas APIs antiguas de Ruby y .NET. - Separados por comas.
?tag=js,ts. Común en APIs REST (OpenAPI lo lista como el estilo form/explode=false). Conciso pero ambiguo si los valores en sí pueden contener comas.
Elige una convención por API y documéntala. Los errores más dolorosos en esta área provienen de un cliente que emite corchetes hablando con un servidor que espera claves repetidas: ambos parecen funcionar, pero uno de ellos silenciosamente solo conserva el último valor.
CJK, emoji y otros caracteres no ASCII
Todo carácter fuera de ASCII debe codificarse como sus bytes UTF-8, luego con percent-encoding byte a byte. El carácter 北 (U+5317) son tres bytes E5 8C 97 en UTF-8, que se convierten en %E5%8C%97 en una URL. Los emoji siguen la misma regla: 🔥 (U+1F525) son cuatro bytes F0 9F 94 A5, codificados como %F0%9F%94%A5.
Los sistemas heredados a veces emiten el mismo carácter en una codificación diferente: GBK para chino, Shift-JIS para japonés, Windows-1251 para cirílico. Si estás integrando con un backend anterior a 2010 y tu texto decodificado sale como basura, intenta interpretar los bytes como Windows-1252 o la codificación nativa del locale antes de asumir que la URL está corrupta.
Nombres de dominio internacionalizados
Los nombres de host siguen una regla diferente. Los nombres de host no ASCII se codifican como Punycode (RFC 3492), no como percent-encoding. El dominio 例え.jp se convierte en xn--r8jz45g.jp en la consulta DNS real. Los navegadores muestran la forma Unicode para scripts reconocidos pero transmiten la forma Punycode en el cable. Si te encuentras construyendo una URL con un host no ASCII, convierte el host con un codificador Punycode, no con el codificador de URL.
Errores comunes
- Codificación doble. Codificar un valor y luego pasar el resultado a otro codificador produce
%2520en lugar de%20. Rastrea cada paso de codificación explícitamente; nunca “codifiques por si acaso” un valor que no produjiste. - Los fragmentos de hash son solo del lado del cliente. Todo lo que hay después de
#nunca se envía al servidor. Poner estado en el fragmento es una elección deliberada (privacidad, enrutamiento de aplicaciones de una sola página); hacerlo accidentalmente oculta errores hasta que el servidor empieza a depender de él. - Signos más en enlaces mailto.
mailto:[email protected]funciona porque las URL mailto usan las reglas de RFC 3986, no la codificación de formularios. Construye los enlaces mailto a mano, no con un codificador de formularios. - El conjunto no reservado excluye la tilde históricamente. RFC 2396 (el predecesor del 3986) trataba
~como reservado. Los codificadores más antiguos todavía emiten%7E; el estándar moderno lo considera no reservado. Ambos se decodifican igual. - Los límites de longitud son reales. La mayoría de los servidores limitan las URL a 8 KB. Los navegadores varían de 2 KB (IE antiguo) a 32 KB (Chrome moderno). Si tu query string se acerca a ese rango, mueve el payload a un cuerpo POST.
Un flujo de trabajo práctico
Al construir URL en código: construye un objeto que represente los componentes (esquema, host, segmentos de ruta, mapa de query), luego serializa una sola vez con una biblioteca de confianza: URL y URLSearchParams en navegadores, sus equivalentes en todos los demás lenguajes principales. Las URL ensambladas a mano son los errores.
Recuerda que la URL codificada es solo la línea de solicitud; la misma capa HTTP lleva cabeceras que tu código raramente maneja manualmente pero que deberías tratar con cuidado. La cabecera Referer filtra la URL anterior (incluida su query string) al siguiente host a menos que la elimines con una política de referencia; la cadena User-Agent permite a los servidores variar el comportamiento por cliente; y la dirección IP de conexión llega en la capa TCP o, detrás de un proxy, en una cabecera X-Forwarded-For. Cualquier cosa que pongas en la query string es visible para los tres.
Al depurar una URL codificada que alguien te envió, pégala en nuestro codificador/decodificador de URL y alterna la dirección. La herramienta decodifica una vez por clic, por lo que si tienes que hacer clic dos veces para obtener texto legible has confirmado codificación doble en la cadena.
La conclusión honesta
Usa encodeURIComponent en cada valor interpolado, nunca en una URL completa. Haz coincidir tu codificación de formulario (espacio como más) con tu contexto de transporte. Elige una convención de array por API y documéntala. Confía en UTF-8. Y cuando algo parece doblemente codificado, casi con certeza lo está: rastrea el segundo codificador en lugar de cubrirlo con un paso de decodificación.
Frequently asked questions
- ¿Debo codificar toda la URL o solo partes de ella?
- Solo codifica las partes que controlas: segmentos de ruta y valores de query. Nunca codifiques una URL completa con encodeURIComponent porque mutilará el separador de esquema (`://`) y los delimitadores `?` y `&`. Codifica cada segmento de ruta y cada valor de query de forma independiente y ensámblalos después.
- ¿Por qué mi URL muestra + en lugar de %20 para los espacios?
- Ese es el comportamiento de `application/x-www-form-urlencoded`, usado por los envíos de formularios HTML. En ese tipo de medio, un espacio se codifica como `+` y un `+` literal se codifica como `%2B`. En las rutas de URL de RFC 3986, los espacios siempre son `%20`. Los servidores generalmente decodifican ambos, pero si construyes una URL a mano, prefiere `%20` en todas partes excepto en los cuerpos de formularios.
- ¿Es encodeURI() alguna vez la opción correcta en JavaScript?
- Raramente. `encodeURI()` deja los caracteres reservados como `?`, `#`, `&` y `/` intactos porque asume que estás codificando una URL completa. Eso casi nunca es lo que realmente quieres: normalmente quieres codificar un valor que se insertará en una URL, que es el trabajo de `encodeURIComponent`. Usa `encodeURI` solo al sanear una URL literal escrita por el usuario que contiene caracteres no ASCII.
- ¿Cómo se codifican los caracteres CJK?
- RFC 3986 requiere UTF-8 seguido de percent-encoding de cada byte. El carácter `日` (U+65E5) es `E6 97 A5` en UTF-8, por lo que se codifica como `%E6%97%A5`. Sistemas más antiguos a veces usaban GBK o Shift-JIS, razón por la cual al hacer scraping de sitios CJK heredados ocasionalmente aparece `%C8%D5` para el mismo carácter. Los navegadores modernos siempre emiten UTF-8.
- ¿Cómo codifico arrays en query strings?
- No hay un estándar único. PHP y Rails usan notación de corchetes (`tags[]=a&tags[]=b`); Express y la mayoría de los frameworks Node aceptan corchetes o claves repetidas (`tags=a&tags=b`); algunas APIs requieren separación por comas (`tags=a,b`). Consulta el analizador del consumidor antes de elegir una convención y documenta la elección en tu referencia de API para que los clientes no tengan que adivinar.
- ¿Por qué obtengo valores doblemente codificados como %2520?
- Algo codificó el valor, luego algo más codificó el resultado. `%20` (un espacio codificado) se convirtió en `%2520` porque el `%` fue re-codificado como `%25`. La solución es encontrar el segundo paso de codificación y eliminarlo; nunca decodifiques y vuelvas a codificar como solución temporal, porque perderás información para valores que contenían legítimamente `%25` antes de cualquier codificación.
Related
Published May 31, 2026