Desenvolvimento

Entidades HTML e os cinco caracteres que realmente precisam de uma

Uma entidade é um caractere escrito em ASCII puro. Você precisa de cinco, e o resto dá para digitar direto no arquivo.

Uma entidade HTML é um jeito de escrever um caractere usando só ASCII puro, para que o navegador mostre aquele caractere em vez de lê-lo como markup. & produz um ampersand, < produz um sinal de menor que, — produz um travessão. Numa página UTF-8 — que hoje é qualquer página — só cinco caracteres chegam a precisar disso, e só dois precisam em todo lugar. Acentos, setas, símbolos de moeda e emoji você digita direto no arquivo, e as entidades que aparecem para eles em tutoriais antigos são resquícios de uma época em que não dava.

As três formas de escrever o mesmo caractere

Toda entidade é uma referência de entidade: um ampersand, um nome ou um número, e um ponto e vírgula. São três formas, e os navegadores tratam todas como idênticas.

As três formas com ampersand produzem o mesmo caractere. As referências numéricas existem para tudo o que o Unicode consegue expressar; as com nome, só para a lista curta, e é por isso que os escapadores acabam recorrendo aos números. O encoder daqui mantém uma lista propositalmente curta — cerca de setenta nomes que parsers antigos e clientes de e-mail reconhecem de forma confiável — e escreve todo o resto como número.

O ponto e vírgula final é necessário?

O HTML5 decodifica cerca de cem referências com nome antigas mesmo sem o ponto e vírgula final, por compatibilidade com páginas escritas em 1998. Então &copy no texto do corpo aparece como ©, você querendo ou não. Isso é uma armadilha, não um recurso: parsers de XML, SVG e RSS rejeitam a mesma coisa de cara, e o XML define só cinco entidades com nome no total — amp, lt, gt, quot e apos. Escreva o ponto e vírgula.

Quais caracteres você realmente precisa escapar?

Cinco, e só porque senão o parser leria cada um deles como algo que não é texto.

O caso do ampersand é o que gera discussão. AT&T aparece certo, porque &T não é uma referência. Salt & Pepper também está ok. Mas uma query string como ?page=2&copy=full contém &copy, e em texto visível isso vira ?page=2©=full. Dentro de um valor de atributo o HTML5 tem uma regra especial que te salva — um nome sem ponto e vírgula seguido de = ou de uma letra ou dígito é deixado como está — que é exatamente o tipo de exceção da qual você não quer depender. Escape o ampersand e pare de pensar nisso.

Fazer isso a olho num bloco de texto é lento e fácil de fazer pela metade. Um escapador com modo mínimo converte esses cinco e deixa todo o resto como caracteres reais, que é a saída correta para uma página UTF-8 e mantém o código-fonte legível.

O que você pode parar de escapar

Se o seu documento declara <meta charset="utf-8"> e está mesmo salvo como UTF-8, digite o caractere. Um travessão, um ı turco, um ₹, um 🙂 — todos funcionam como texto literal, e todos são mais curtos, mais legíveis e menos sujeitos a erro do que &#8212; e companhia.

As entidades continuam sendo a melhor escolha para os caracteres que são invisíveis ou ambíguos no código-fonte, porque ninguém consegue revisar o que não vê:

Vale a pena entender direito o espaço não separável. Espaços normais colapsam: dez seguidos aparecem como um. Um espaço não separável não colapsa, e não permite quebra de linha. É por isso que 5&nbsp;kg nunca termina com o 5 numa linha e o kg na seguinte, e é a correção certa para um número separado da sua unidade. Escrever um de propósito é o caso fácil. Achar os que outra pessoa deixou nos seus dados é o caso difícil, e é outro trabalho.

Por que a minha página mostra &amp; em vez de &?

Porque alguma coisa escapou um texto que já estava escapado. O ampersand de &amp; foi escapado uma segunda vez e virou &amp;amp;, e o navegador decodifica fielmente uma camada e imprime o resto.

É um problema de pipeline, não um erro de digitação. Um formulário escapa a entrada ao salvar, o template escapa de novo ao renderizar, e a segunda passada não tem como saber que a primeira aconteceu. O estrago se acumula de forma previsível, então decodificar a string repetidamente até ela parar de mudar diz quantas camadas existem. Depois tire o passo de escape que não deveria estar rodando. Escapar uma vez só, no ponto de saída, é o único arranjo que não acaba produzindo isso.

Entidades, URL encoding e escape JSON: o que vai onde?

Três sistemas de escape diferentes, três alfabetos diferentes, aplicados em três momentos diferentes. Um espaço é &#32; em HTML, %20 numa URL e \u0020 em JSON, e usar um no lugar do outro produz um texto que parece quase certo e está quebrado.

A sobreposição que causa mais estrago é um link. O href é HTML, então leva escape de entidade; o que está dentro dele é uma URL, então os caracteres reservados dela precisam de percent-encoding antes. Os dois, nessa ordem. Se você já viu um link quebrar no instante em que alguém colocou um espaço ou um sinal de mais num parâmetro, as regras por trás do percent-encoding são a metade da resposta que as entidades não cobrem.

JSON é um mundo à parte de novo. Entidades HTML não significam nada dentro de uma string JSON — são só caracteres — e os escapes \u do JSON não significam nada para um parser HTML. Quando uma resposta chega como uma única linha ininterrupta de sequências escapadas, formatar antes de tentar ler diz em segundos se o problema são os dados ou a codificação.

Escapar é suficiente para impedir XSS?

No texto HTML e em valores de atributo entre aspas, escapar esses cinco é o que impede que o markup injetado seja markup. Fora desses dois lugares, isso ajuda muito menos do que parece. Escapar faz o markup aparecer como texto; não remove nada, e só é correto para o contexto em que você está escrevendo. Quatro lugares onde não basta:

E se você quer manter algumas tags e descartar outras — permitir <b>, rejeitar <script> — isso é sanitizar, e exige um parser de verdade com uma allowlist. Escapar é tudo ou nada por design.

Por que meu emoji saiu como duas entidades quebradas?

Caracteres acima de U+FFFF — emoji, CJK mais raro, quase todas as escritas históricas — são guardados em JavaScript como duas unidades de código. Um escapador que percorre uma string um índice por vez emite duas referências como &#55357;&#56832;, e nenhum parser consegue juntar isso de volta; o HTML transforma cada metade num caractere de substituição. Um escapador correto itera por code point, então um rosto sorridente vira uma única referência, &#x1F600;. Se você já viu uma fileira de losangos pretos onde deveria estar um emoji, normalmente é por isso.

Referências numéricas entre &#128; e &#159; dão problema de outro jeito. Os parsers HTML deliberadamente não exibem essas referências como os caracteres de controle Unicode que elas nomeiam; eles mapeiam para os caracteres do Windows-1252 que os autores queriam nos anos 1990, então &#151; sai como um travessão. Correto segundo o padrão, nada óbvio, e mais um motivo para digitar o caractere em vez do número.

Se você está encarando agora uma string cheia de &amp;, o encoder e decoder de entidades daqui roda ela no sentido contrário, e decodificar de novo até o texto parar de mudar diz quantas camadas foram aplicadas. Ele também faz o sentido de ida, com o modo mínimo que você quer para qualquer coisa que vá para uma página UTF-8. Uma coisa que ele não vai te dizer: um nome de entidade que ele não reconhece sai inalterado em vez de sinalizado, exatamente como um navegador exibiria, então um nome escrito errado parece que decodificou certo.

Entidades são um caso de um truque maior: pegar algo em que um parser engasgaria e reescrever num alfabeto seguro. Embutir uma imagem como Base64 é a mesma ideia aplicada ao binário, com a mesma pegadinha — é maior do que aquilo que substitui, e vale saber quando essa troca é ruim.

Perguntas frequentes

O que são entidades HTML?

São caracteres escritos em ASCII puro para que o navegador os mostre em vez de lê-los como markup. Cada uma começa com um ampersand e termina com um ponto e vírgula, e pode usar um nome (&amp;), um número decimal (&#38;) ou um número hexadecimal (&#x26;). Os três exemplos produzem um ampersand.

Quais caracteres eu preciso escapar em HTML?

Ampersand e menor que sempre, maior que por convenção, e o caractere de aspa com que você envolveu o valor de um atributo. Numa página UTF-8 nada mais é exigido: letras acentuadas, travessões, símbolos de moeda e emoji podem ser digitados direto no código-fonte.

Por que minha página mostra &amp; em vez de &?

O texto foi escapado duas vezes, então o ampersand da primeira entidade foi escapado de novo. Normalmente são dois estágios de um pipeline fazendo os dois o mesmo trabalho, como um formulário escapando ao salvar e um template escapando ao renderizar. Resolve-se escapando uma vez só, no ponto de saída.

&nbsp; é a mesma coisa que um espaço normal?

Não. Um espaço não separável é U+00A0: ele não colapsa quando vários aparecem seguidos, e não permite quebra de linha. Isso faz dele o jeito certo de manter um número grudado na sua unidade, e o jeito errado de indentar ou espaçar um layout.

Entidades HTML são a mesma coisa que URL encoding?

Não, são sistemas separados aplicados em momentos diferentes. Um espaço é &#32; em HTML e %20 numa URL, e o JSON usa \u0020 para o mesmo caractere. Um link precisa dos dois: primeiro a URL leva percent-encoding, depois o atributo inteiro leva escape de entidade.

Última atualização 19 de setembro de 2026