Todos os artigos
82 artigos · atualizado semanalmente Veja nossas Ferramentas
Todos os artigos
Tutoriais

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.

COVER · Tutoriais

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:

  1. Não-reservados: letras (A-Z, a-z), dígitos (0-9) e os símbolos -, ., _, ~. Podem aparecer literalmente em qualquer parte da URL.
  2. Reservados: têm significado estrutural (:/?#[]@!$&'()*+,;=). Podem aparecer em posições específicas; fora dessas posições, precisam ser encodados.
  3. 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ê:

  1. Recebe um valor encodado como application/x-www-form-urlencoded (com + para espaço)
  2. Usa um decoder de RFC 3986 que não entende + como espaço
  3. 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 (&lt;, &gt;, &amp;, &quot;). 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 (&amp; 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.

RD
Autor
Rafael Duarte
Desenvolvedor backend com passagem por fintech e SaaS B2B — trabalhou em times que escalaram APIs de zero a milhões de requisições. Carrega cicatrizes de produção suficientes para ter opiniões fortes sobre ferramentas, padrões e decisões de arquitetura. Não é acadêmico: leu a RFC do UUID quando precisou escolher entre v4 e v7 para uma tabela de alta escrita.
Ver perfil