Ferramentas

Imagem Vídeo Áudio Texto Vetor Desenvolvedor Geral

⚡ 20 créditos gratuitos restantes hoje

Cadastre-se grátis

Como converter texto para Base64 em JavaScript (e decodificar)

A linha de código que funciona, a que gera erro e o motivo — além do ciclo seguro com UTF-8, os métodos nativos de 2025 e as diferenças no Node.js.

Ilustração suave em 3D de formas de documentos azuis em camadas se transformando em uma grade de cubos azuis codificados que se dissolvem em…

Para codificar uma string em Base64 em JavaScript, chame btoa('Hello, world!'), que retorna 'SGVsbG8sIHdvcmxkIQ=='. Essa linha única só funciona para texto ASCII e Latin-1: no momento em que você passa um emoji, um acento ou um caractere chinês, ela gera InvalidCharacterError. Este guia mostra por que isso acontece e o ciclo seguro com UTF-8 que resolve o problema, incluindo os novos métodos nativos de 2025.

Base64 em uma linha: btoa() e atob()

A resposta mais rápida: codifique com btoa('Hello, world!'), decodifique com atob('SGVsbG8sIHdvcmxkIQ=='). Ambos verificados: btoa('a') retorna 'YQ==', e atob('SA==') retorna 'S'.

O que btoa() realmente faz é tratar sua string como uma "string binária", uma string onde cada caractere representa exatamente um byte de dados binários, e então codifica esses bytes em Base64. O caso de uso da MDN é para dados que causam problemas de transmissão, como caracteres de controle ASCII (0–31), que você deseja transportar por um canal apenas de texto.

Essa linha única é suficiente quando seu texto é ASCII: o ponto de código de cada caractere deve ser inferior a 256, e, conforme a MDN, a correspondência byte-ponto de código só é confiável até 0x7F. O intervalo 0x80–0xFF funciona tecnicamente, mas depende do comportamento de codificação Latin-1 que não é portátil entre ambientes. Uma boa regra: qualquer coisa não ASCII precisa do caminho UTF-8 abaixo.

Por que btoa() gera InvalidCharacterError em emojis, acentos e CJK

Base64 é uma codificação binária, não uma codificação de texto. O glossário da MDN chama o hábito de passar texto arbitrário ou HTML para btoa() de um equívoco comum: btoa/atob antecedem arrays tipados, então usam strings para representar bytes. O problema começa quando sua string não tem um byte por caractere.

Strings JavaScript são UTF-16, então uma unidade de código tem sempre dois bytes, e caracteres fora do BMP (como emojis) ocupam quatro bytes como par substituto; UTF-8 usa de um a quatro bytes por ponto de código. btoa() e atob() funcionam apenas com caracteres de byte único (intervalo Latin-1). Tente codificar algo mais largo e você obterá este erro exato:

DOMException: Failed to execute 'btoa' on 'Window':
The string to be encoded contains characters outside of the Latin1 range

O caso problemático é trivial de reproduzir: btoa('✓') gera erro, e btoa('a Ā 𐀀 文 🦄') também. Emojis, letras acentuadas além do Latin-1 e caracteres CJK estão todos acima de 0xFF, então nenhum deles se encaixa na suposição de um byte por caractere. '✓' é U+2713, bem acima de 0xFF, então btoa() recusa em vez de corromper seus dados silenciosamente. Essa recusa é talvez o melhor recurso da API: ela falha abertamente em vez de produzir mojibake que você descobriria semanas depois.

Mais uma coisa vale saber antes de corrigir isso: Base64 aumenta o tamanho dos dados, em cerca de 33% comparado aos bytes brutos. Isso é aceitável para strings curtas, tokens e URIs de dados, mas é um motivo real para não codificar Base64 grandes payloads — codifique na fronteira necessária, não em todos os lugares.

Um laço azul suave em 3D fluindo pela esquerda e dividindo-se em dois caminhos: uma das mechas forma grupos agrupados ordenados de…
Um fluxo de dados, duas pegadas de bytes: clusters multi-byte versus bytes únicos.

O caso de borda do substituto solitário (e String.isWellFormed())

A maioria dos artigos pula isso, mas causa dores de cabeça: uma string UTF-16 contendo um substituto solitário (metade de um par de emoji, geralmente devido a um bug de fatiamento) é "malformada". Conforme o guia do web.dev, TextDecoder substitui silenciosamente U+FFFD por padrão. Antes de codificar, verifique a conformidade com String.prototype.isWellFormed(), ou capture o URIError que encodeURIComponent() gera em substitutos solitários, então decida explicitamente: rejeitar os dados, permitir a substituição deliberadamente ou gerar erro para análise posterior.

O jeito correto: TextEncoder → bytes UTF-8 → Base64

A solução correta é converter sua string para bytes UTF-8 com TextEncoder primeiro, e então codificar os bytes em Base64 em vez da string. new TextEncoder().encode(str) transforma qualquer string JavaScript em uma Uint8Array de bytes UTF-8 (TextEncoder só fala UTF-8). No caminho de volta, new TextDecoder() assume UTF-8 por padrão e reverte exatamente. Observe o que atob() oferece aqui: uma string binária, um caractere por byte, não texto UTF-8. É por isso que o lado da decodificação precisa de TextDecoder para transformar bytes de volta em texto legível.

Auxiliares bytesToBase64() e base64ToBytes() (código pronto para copiar e colar)

Aqui está o ciclo canônico da MDN, que roda em todos os navegadores que suportam TextEncoder (IE excluído):

function bytesToBase64(bytes) {
  const binString = Array.from(bytes, (b) => String.fromCodePoint(b)).join('');
  return btoa(binString);
}

function base64ToBytes(base64) {
  const binString = atob(base64);
  return Uint8Array.from(binString, (m) => m.codePointAt(0));
}

Verifique com um ciclo completo

Codificar uma string torna-se uma linha:

const encoded = bytesToBase64(new TextEncoder().encode('a Ā 𐀀 文 🦄'));
// 'YSDEgCDwkICAIOaWhyDwn6aE'

Decodificar inverte o processo:

const decoded = new TextDecoder().decode(base64ToBytes(encoded));
// 'a Ā 𐀀 文 🦄'

O ciclo verificado do web.dev confirma o mesmo padrão: 'hello⛳❤️🧀' codifica para 'aGVsbG/im7PinaTvuI/wn6eA' e decodifica de volta exatamente, inclusive emojis.

Ignore o truque unescape(encodeURIComponent())

Você encontrará btoa(unescape(encodeURIComponent(str))) copiado em todos os lugares. Funciona, mas unescape() é legado do Anexo B, obsoleto desde o ECMAScript 3. Código novo deve usar TextEncoder em vez disso: é explícito, padrão e não depende de um global obsoleto.

O jeito de 2025: Uint8Array.toBase64() e Uint8Array.fromBase64()

Desde setembro de 2025, JavaScript tem métodos nativos de Base64 em Uint8Array próprios, então os auxiliares de string de bytes acima já não são a única opção. A proposta Base64 do TC39 atingiu o Estágio 4 e faz parte da linguagem; a justificativa do TC39 é que base64 não é uma codificação de texto, então TextEncoder/TextDecoder são conceitualmente a ferramenta errada para isso.

De acordo com a MDN, estas são funções da categoria "Disponíveis recentemente" nos navegadores modernos. Verifique os ambientes de execução antes de lançar: se você precisa suportar navegadores antigos ou versões antigas do Node, mantenha o fallback com TextEncoder + btoa. A MDN recomenda esses métodos em vez de btoa()/atob() quando você já possui um Uint8Array, pois evita a criação de strings brutas de bytes.

Codificação: bytes.toBase64()

const b64 = new TextEncoder().encode('Hello, world!').toBase64();
// 'SGVsbG8sIHdvcmxkIQ=='

Uma conversão UTF-8, uma chamada de método e pronto.

Decodificação: Uint8Array.fromBase64()

const text = new TextDecoder().decode(Uint8Array.fromBase64(b64));
// 'Hello, world!'

A MDN observa que fromBase64() deve ser preferido sobre atob() porque retorna um array de bytes real, a menos que os dados decodificados se destinem a ser texto ASCII. Ele aceita apenas o alfabeto base64 (A–Z, a–z, 0–9, além de +/ ou -_), o preenchimento = opcional e ignora espaços em branco ASCII; ele lança SyntaxError ao encontrar caracteres fora do alfabeto.

Opções: alphabet, omitPadding, lastChunkHandling

O pacote de opções da especificação oferece controle preciso:

Opção Valores O que faz
alphabet 'base64' (padrão), 'base64url' +// vs -/_
omitPadding booleano Remove o = final: new Uint8Array([72]).toBase64() → 'SA==', com omitPadding → 'SA'
lastChunkHandling 'loose' (padrão), 'strict', 'stop-before-partial' Tolerância do decodificador para o último bloco

'loose' tolera falta de preenchimento e espaços em branco. 'strict' exige blocos exatos de 4 caracteres com bits de estouro zerados, o que é ideal ao validar entradas não confiáveis. 'stop-before-partial' é usado para streaming. O modo padrão flexível também tolera bits de estouro não nulos, que o modo estrito rejeita.

Base64 vs base64url: qual alfabeto você precisa?

O base64 padrão usa + e / mais o preenchimento =, sendo adequado para data: URLs, pois essas não possuem segmentos de caminho ou strings de consulta. O base64url (RFC 4648 §5) substitui por - e _ e geralmente omite o preenchimento, sendo obrigatório sempre que a saída viaja dentro de uma URL: JWTs usam base64url para seus três segmentos, por exemplo.

Quando usar base64url

Use base64url quando a string codificada fizer parte de um caminho de URL ou parâmetro de consulta, onde + e / precisariam de escape (e o preenchimento = polui as strings de consulta). Use o base64 padrão para data URIs, incorporação de bytes em payloads JSON ou qualquer coisa que não seja uma URL.

Como gerar base64url

Três maneiras, dependendo do ambiente:

  • Navegadores modernos: bytes.toBase64({ alphabet: 'base64url' })
  • Node 16+: Buffer.from(str).toString('base64url')
  • Pós-processamento: pegue a saída padrão e substitua + → -, / → _, depois remova o =

Um detalhe importante: a RFC 4648 exige que os codificadores zerem os bits de estouro não utilizados no último bloco, e os decodificadores estritos podem lançar erro se eles não estiverem zerados. O glossário da MDN dá o exemplo de 'VR==', que é o mesmo byte que 'VQ==' mas com bits de preenchimento sujos. Gere base64 com um codificador bem comportado e isso nunca acontecerá; se criar um manualmente, pode acontecer.

Codificar um arquivo ou imagem para Base64 (data URLs)

Para arquivos, a rota no navegador é FileReader com readAsDataURL(), que gera uma data URL completa, e então você remove o prefixo para obter o base64 puro. Para imagens já renderizadas em um canvas, toDataURL('image/png') faz isso em uma única chamada.

FileReader.readAsDataURL() → data URL → base64 puro

function fileToDataURL(file) {
  return new Promise((resolve) => {
    const reader = new FileReader();
    reader.onload = () => resolve(reader.result);
    reader.readAsDataURL(file);
  });
}

const dataUrl = await fileToDataURL(fileInput.files[0]);
const rawBase64 = dataUrl.replace(/^data:\w+\/\w+;base64,/, '');

canvas.toDataURL() para imagens

Se você desenhou uma imagem em um canvas (redimensionar, recortar, etc.), ignore o FileReader completamente:

const dataUrl = canvas.toDataURL('image/png');

A mesma remoção de prefixo se aplica se você precisar apenas da string base64 crua.

Data URL de volta para bytes

O sentido inverso é um fetch e um array buffer. O exemplo da MDN: uma data URL construída a partir de new Uint8Array([0,1,2]) é 'data:application/octet-stream;base64,AAEC', e você recupera os bytes assim:

const res = await fetch(dataUrl);
const bytes = new Uint8Array(await res.arrayBuffer());
// [0, 1, 2]

E o lembrete pelo qual este artigo inteiro existe: não chame btoa() em texto ou HTML arbitrários para construir data URLs. É um codificador byte a byte, e entradas não Latin-1 causarão erro.

Node.js: Buffer.from(str, 'base64') — e a sua única diferença perigosa

O Node tem suas próprias soluções idiomáticas que dispensam a dança do TextEncoder: Buffer.from('Hello, world! 🌍').toString('base64') lida com Unicode automaticamente, pois o Buffer trabalha com bytes desde o início. Decodifique com Buffer.from(encoded, 'base64').toString('utf8').

Outras formas úteis: Buffer.from(str).toString('base64url') (Node 16+), e arquivos inteiros via fs.readFileSync('image.png').toString('base64').

const encoded = Buffer.from('Hello, world! 🌍').toString('base64');
const decoded = Buffer.from(encoded, 'base64').toString('utf8');

O Node não valida seu base64 (falhas silenciosas)

Aqui está a parte que surpreende as pessoas: o decodificador base64 do Node não valida a entrada. Buffer.from('!!!!', 'base64').toString() retorna '' sem erro, e Buffer.from('aGVsbG8!!!', 'base64') descarta silenciosamente o lixo e retorna 'hello'. Compare isso com atob('aGVsbG8!!!'), que lança InvalidCharacterError. Se o base64 vier de usuários, uma API ou terceiros, valide-o antes de decodificar no Node, ou use Uint8Array.fromBase64() com lastChunkHandling: 'strict' onde os ambientes de execução suportarem.

btoa/atob estão obsoletos? Navegadores vs Node

Isso é realmente contraditório, então aqui vai uma resolução honesta. Nos navegadores, a MDN marca btoa() e atob() como Baseline amplamente disponível desde julho de 2015, ou seja, não obsoletas. No Node, as definições de tipo as marcam como @deprecated com o comentário de que "nunca devem ser usadas em código novo", recomendando Buffer.from(str, 'base64') em vez disso.

Uma boa regra: é uma API legada, não removida. Aceitável no código do navegador, mas prefira Buffer em código específico do Node. Por consenso da comunidade (não uma garantia da especificação), btoa/atob também são a escolha prática multiambiente se você precisar de um único caminho de código que rode inalterado em navegadores, Deno e Node sem dependências.

O que realmente usar

Se você levar apenas uma recomendação deste artigo, são as três linhas abaixo. Mantenha btoa/atob para ASCII puro e bases de código legadas, e use o helper bytesToBase64 como fallback do navegador até que toBase64() cubra todo o seu público.

// Modern browsers (2025+)
const enc = new TextEncoder().encode(str).toBase64();
const dec = new TextDecoder().decode(Uint8Array.fromBase64(b64));

// Browser fallback (MDN helper)
const encFallback = bytesToBase64(new TextEncoder().encode(str));

// Node (validate input first if it isn't yours)
const encNode = Buffer.from(str).toString('base64');

Antes de lançar, verifique sua saída com uma rápida ida e volta em uma ferramenta de navegador. Cole 'Hello, world!' no Codificador / Decodificador Base64 gratuito da Tool Cloud, confirme que você obtém 'SGVsbG8sIHdvcmxkIQ==', depois decodifique de volta e verifique se é idêntico. Há também um conversor de Texto para Base64 e um conversor de Base64 para Texto independentes; os arquivos são excluídos automaticamente e nada precisa ser instalado.

Perguntas frequentes

Por que btoa() falha com emojis, mas funciona com texto em inglês?

btoa() trata cada caractere como um único byte e exige que cada ponto de código seja inferior a 256 (de forma confiável, apenas até 0x7F). Emojis, caracteres acentuados e textos em CJK precisam de múltiplos bytes, então eles acionam um DOMException: InvalidCharacterError. Converta para bytes UTF-8 usando TextEncoder primeiro, depois codifique os bytes.

btoa() está obsoleto?

Não nos navegadores — a MDN marca btoa() e atob() como amplamente disponíveis desde julho de 2015. No entanto, as definições de tipo do Node as marcam como obsoletas em favor de Buffer.from(str, 'base64') em código específico do Node. Elas são APIs legado, não removidas.

Como codificar Base64 texto Unicode (acentos, chinês, emoji)?

Use new TextEncoder().encode(str) para obter bytes UTF-8, depois codifique os bytes (btoa() via um auxiliar de string de bytes, ou Uint8Array.toBase64() em navegadores modernos). Para decodificar, use Uint8Array.fromBase64() ou atob() para recuperar os bytes, depois new TextDecoder().decode(bytes).

Qual a diferença entre base64 e base64url?

base64url (RFC 4648 §5) usa '-' e '_' em vez de '+' e '/', e geralmente omite o preenchimento '='. Use base64url quando a saída for inserida em URLs ou JWTs; o base64 padrão é adequado para data: URLs, que não possuem segmentos de caminho ou strings de consulta.

Por que o Node.js não gera erro em Base64 inválido?

O Buffer.from(str, 'base64') do Node não valida a entrada: ele ignora silenciosamente caracteres inválidos, então Buffer.from('aGVsbG8!!!', 'base64').toString() retorna 'hello' sem erro. Nos navegadores, atob() gera InvalidCharacterError na mesma entrada. Valide Base64 não confiável antes de decodificar no Node.