O que é URL encoding? Percent-encoding explicado
URL encoding converte caracteres especiais em sequências %HH para que possam aparecer em URLs sem ambiguidade. Entenda a diferença entre encodeURI e encodeURIComponent.
Um sistema de busca estava quebrando de forma intermitente. Depois de meia hora depurando, o problema era óbvio: alguém estava passando o valor de um parâmetro de query string diretamente na URL sem encodar, e o valor continha um &. O servidor recebia dois parâmetros onde deveria receber um. O fix foi uma linha de código que deveria ter sido escrita desde o início.
URL encoding — mais corretamente chamado de percent-encoding — é um dos mecanismos mais fundamentais da web e um dos mais frequentemente mal usados. A RFC 3986, que define a sintaxe de URIs, é de 2005. O problema persiste.
Por que URLs precisam de encoding
Uma URL tem estrutura. Os caracteres :, /, ?, #, [, ], @, !, $, &, ', (, ), *, +, ,, ; e = são caracteres reservados — têm significado especial dentro da URL. O ? separa o caminho da query string. O & separa pares de parâmetros. O = separa chave de valor.
Quando um desses caracteres aparece como parte de um valor (e não como delimitador), o parser não tem como distinguir os dois casos sem que o dado esteja encodado.
A RFC 3986 divide os caracteres em três categorias:
- Não-reservados: letras (
A-Z,a-z), dígitos (0-9) e os símbolos-,.,_,~. Podem aparecer literalmente em qualquer parte da URL. - Reservados: têm significado estrutural (
:/?#[]@!$&'()*+,;=). Podem aparecer em posições específicas; fora dessas posições, precisam ser encodados. - Tudo o mais: caracteres fora do ASCII, espaços, acentos, caracteres de controle — devem sempre ser encodados.
Como o percent-encoding funciona
A mecânica é simples: cada byte que precisa ser encodado é representado como % seguido de dois dígitos hexadecimais representando o valor do byte.
Alguns exemplos concretos:
| Caractere | Byte (UTF-8) | Encoded |
|---|---|---|
| Espaço | 0x20 | %20 |
@ |
0x40 | %40 |
& |
0x26 | %26 |
= |
0x3D | %3D |
+ |
0x2B | %2B |
ã |
0xC3 0xA3 | %C3%A3 |
Note que ã, por ser um caractere multibyte em UTF-8, gera dois tokens %HH. Isso é importante: o encoding opera sobre bytes, não sobre caracteres Unicode diretamente. A codificação do texto para bytes antes do percent-encoding é sempre UTF-8 para URLs modernas.
O caso do + como espaço
Existe uma inconsistência clássica que causa bugs até hoje. O formato application/x-www-form-urlencoded — usado quando você envia um formulário HTML com method="POST" ou method="GET" — usa + para representar um espaço. Isso é diferente da RFC 3986, que usa %20.
-- application/x-www-form-urlencoded
nome=Rafael+Duarte&cidade=S%C3%A3o+Paulo
-- RFC 3986 percent-encoding puro
nome=Rafael%20Duarte&cidade=S%C3%A3o%20Paulo
O problema aparece quando você:
- Recebe um valor encodado como
application/x-www-form-urlencoded(com+para espaço) - Usa um decoder de RFC 3986 que não entende
+como espaço - O
+chega como literal no valor
PHP tem urlencode() (que usa + para espaços, compatível com form encoding) e rawurlencode() (que usa %20, compatível com RFC 3986). Usar o errado em cada contexto é uma fonte clássica de bug. Para query strings construídas manualmente, rawurlencode() é a escolha mais segura e previsível.
encodeURI vs encodeURIComponent em JavaScript
Esta é a distinção que mais gera confusão em JavaScript. As duas funções existem porque servem a propósitos diferentes:
encodeURI() foi projetada para encodar uma URL completa. Ela deixa intactos todos os caracteres que têm significado estrutural em uma URL — o que significa que ela não encoda :, /, ?, #, [, ], @, !, $, &, ', (, ), *, +, ,, ;, =. Isso faz sentido se você quer encodar uma URL sem quebrar sua estrutura.
encodeURIComponent() foi projetada para encodar um componente de URL — um valor individual. Ela encoda tudo exceto os caracteres não-reservados (A-Z a-z 0-9 - _ . ~). Isso significa que ela encoda &, =, +, ? e todos os outros caracteres reservados.
A regra prática:
// ERRADO: encodeURI não encoda & e = → quebra a query string
const query = `https://api.exemplo.com/search?q=${encodeURI('café & bolo')}`;
// resultado: ?q=café%20&%20bolo ← o & vira um separador de parâmetro!
// CORRETO: encodeURIComponent encoda todos os caracteres reservados
const query = `https://api.exemplo.com/search?q=${encodeURIComponent('café & bolo')}`;
// resultado: ?q=caf%C3%A9%20%26%20bolo ← & encodado como %26
A regra é simples: se você está construindo uma URL completa, encodeURI pode fazer sentido. Se você está inserindo um valor em qualquer parte de uma URL — query param, path segment, fragmento — use sempre encodeURIComponent.
Para construir query strings de forma segura em JavaScript moderno, URLSearchParams é a opção mais robusta:
const params = new URLSearchParams({
q: 'café & bolo',
autor: 'Rafael Duarte',
tag: 'node.js'
});
const url = `https://api.exemplo.com/search?${params.toString()}`;
// resultado: ?q=caf%C3%A9+%26+bolo&autor=Rafael+Duarte&tag=node.js
Note que URLSearchParams usa form encoding (com + para espaços), não RFC 3986 puro. Para a maioria dos casos de uso com APIs web, isso é completamente compatível — os servidores esperam form encoding em query strings.
Quando precisar testar como um valor específico é encodado, o Codificador de URL permite comparar os resultados de diferentes estratégias de encoding lado a lado.
Os erros mais comuns
Não encodar o valor de um parâmetro: o bug clássico do início deste post. Qualquer valor dinâmico que vai para uma query string precisa ser encodado com encodeURIComponent ou via URLSearchParams.
Encodar a URL inteira com encodeURIComponent: transforma https:// em https%3A%2F%2F e quebra a estrutura completamente. Use encodeURI para URLs completas, encodeURIComponent para componentes isolados.
Double encoding: acontece quando você encoda algo que já está encodado. %26 (o & encodado) se torna %2526 — o % foi encodado como %25. Na hora de decodar, o servidor recebe %26 literal em vez de &. Isso costuma acontecer quando há múltiplas camadas de código manipulando a mesma URL sem coordenação.
Assumir que a URL está em ASCII: URLs modernas suportam caracteres internacionais via IDN (Internationalized Domain Names) e percent-encoding de UTF-8. https://exemplo.com/búsqueda?q=ação é uma URL válida, mas o servidor pode receber %C3%A7%C3%A3o dependendo de como o cliente trata os caracteres. Seja explícito sobre o encoding.
URL encoding vs Base64
Essa comparação aparece com frequência e é importante entender que os dois mecanismos têm propósitos diferentes.
Percent-encoding é para fazer um string arbitrário "caber" dentro de uma URL sem quebrar sua estrutura. O resultado é sempre ASCII, mas pode ser significativamente maior que o original (cada byte encodado vira 3 caracteres).
Base64 é para representar dados binários arbitrários — como uma imagem ou um arquivo — em formato de texto ASCII. Não tem nada a ver com URLs especificamente; é uma codificação de propósito geral para transmitir binário onde apenas texto é aceito. O post O que é Base64? cobre esse mecanismo em detalhe.
A confusão surge porque Base64 também é usado em URLs às vezes — por exemplo, em data URIs (data:image/png;base64,...) ou em tokens JWT que aparecem como query params. Nesses casos, os dois mecanismos coexistem: o valor Base64 ainda precisa ser percent-encodado se for um parâmetro de query string, porque Base64 usa + e = que são caracteres reservados.
Como construir query strings corretamente
Exemplo prático em JavaScript — construindo uma URL de busca com múltiplos parâmetros:
function buildSearchUrl(base, params) {
const url = new URL(base);
Object.entries(params).forEach(([key, value]) => {
url.searchParams.set(key, String(value));
});
return url.toString();
}
const searchUrl = buildSearchUrl('https://api.exemplo.com/search', {
q: 'node.js & express',
page: 2,
tags: 'backend,api',
autor: 'Rafael Duarte'
});
// https://api.exemplo.com/search?q=node.js+%26+express&page=2&tags=backend%2Capi&autor=Rafael+Duarte
A vantagem de usar a API URL e URLSearchParams em vez de concatenar strings manualmente é exatamente essa: o encoding é feito automaticamente e corretamente. Não é necessário lembrar quando usar encodeURIComponent — a API cuida disso.
Em PHP, a função equivalente é http_build_query(), que também cuida do encoding automaticamente:
$params = http_build_query([
'q' => 'node.js & express',
'page' => 2,
'autor' => 'Rafael Duarte',
]);
// q=node.js+%26+express&page=2&autor=Rafael+Duarte
Perguntas frequentes
Por que minha URL tem %20 em alguns lugares e + em outros?
São duas convenções de encoding diferentes. %20 é percent-encoding puro conforme a RFC 3986. + para espaço é da especificação application/x-www-form-urlencoded, usada em formulários HTML. Servidores web modernos entendem ambos em query strings, mas misturá-los pode causar problemas em sistemas mais antigos ou em parsers estritos. Para APIs novas, padronize em %20.
encodeURIComponent encoda o / de um path? Como encodar apenas o valor?
Sim, encodeURIComponent encoda / como %2F. Se você quer inserir um valor que pode conter / como parte de um segmento de path (por exemplo, /api/users/{id} onde id pode ter barras), encodar com encodeURIComponent é o correto — o servidor decodará o %2F de volta para / ao processar o parâmetro, sem confundi-lo com um separador de segmento.
Como decodar uma URL encodada?
Em JavaScript, decodeURIComponent() desfaz encodeURIComponent, e decodeURI() desfaz encodeURI. Para query strings recebidas em um servidor Node.js, new URLSearchParams(req.url.split('?')[1]) faz o parsing e decoding automaticamente. Em PHP, urldecode() e rawurldecode() correspondem às funções de encoding. Nunca faça decode manual substituindo %HH por regex — as implementações padrão lidam com edge cases (multibyte, double encoding, etc.) que são difíceis de replicar corretamente.
URL encoding é o mesmo que HTML encoding?
Não. HTML encoding (ou HTML entity encoding) converte caracteres como <, >, & e " para entidades HTML (<, >, &, "). Serve para escapar conteúdo dentro de HTML sem quebrar a estrutura do documento. URL encoding (percent-encoding) converte bytes arbitrários para o formato %HH. São mecanismos independentes, embora ambos possam ser necessários ao mesmo tempo — por exemplo, uma URL dentro de um atributo href pode precisar tanto de percent-encoding nos valores dos parâmetros quanto de HTML encoding do & que separa os parâmetros (& em vez de & dentro do HTML).
O que lembrar
Percent-encoding é simples no conceito: byte vira %HH. A complexidade vem das convenções que se acumularam em cima: form encoding com + para espaço, a diferença entre encodar uma URL completa e um componente isolado, e as armadilhas de double encoding.
A regra prática que elimina a maioria dos bugs: use URLSearchParams em JavaScript e http_build_query() em PHP para construir query strings. Essas APIs fazem o encoding correto automaticamente. Recorra a encodeURIComponent manualmente apenas quando precisar encodar um path segment ou outro componente específico. Nunca concatene valores dinâmicos numa URL sem passar por uma dessas funções.
- 01 O que é JSON e como esse formato funciona Entenda o que é JSON, seus seis tipos de dados, sintaxe obrigatória e onde o formato é usado — com exemplos reais em JavaScript, Python e Go.
- 02 Como os LLMs geram respostas: tokens, predição e sampling explicados Tokenização, predição autorregressiva, temperatura e Top-P: a mecânica interna de como modelos de linguagem transformam um prompt em texto.