Strumenti

Immagine Video Audio Testo Vettoriale Sviluppatore Generale

⚡ 20 crediti gratuiti rimasti oggi

Registrati gratis

Come convertire testo in Base64 con JavaScript (e decodificarlo)

La riga di codice che funziona, quella che genera un errore e il motivo; oltre al round-trip sicuro per UTF-8, i metodi nativi del 2025 e le differenze in Node.js.

Illustrazione soft 3D di forme di documenti blu sovrapposti che si trasformano in una griglia di cubi blu codificati, i quali si dissolvono in…

Per codificare in Base64 una stringa in JavaScript, chiama btoa('Hello, world!'), che restituisce 'SGVsbG8sIHdvcmxkIQ=='. Questa riga di codice funziona solo per testo ASCII e Latin-1: nel momento in cui passi un emoji, un accento o un carattere cinese, genera InvalidCharacterError. Questa guida mostra il motivo di questo comportamento e il round-trip sicuro per UTF-8 che lo risolve, inclusi i nuovi metodi nativi del 2025.

Base64 in una riga: btoa() e atob()

La risposta più veloce: codifica con btoa('Hello, world!'), decodifica con atob('SGVsbG8sIHdvcmxkIQ=='). Entrambi verificati: btoa('a') restituisce 'YQ==', e atob('SA==') restituisce 'S'.

Ciò che btoa() fa realmente è trattare la tua stringa come una "stringa binaria", ovvero una stringa in cui ogni carattere rappresenta esattamente un byte di dati binari, per poi codificare quei byte in Base64. Il caso d'uso segnalato da MDN riguarda dati che causano problemi di trasmissione, come i caratteri di controllo ASCII (0–31), che vuoi far passare attraverso un canale solo testuale.

Questa riga di codice è sufficiente quando il tuo testo è ASCII: il code point di ogni carattere deve essere inferiore a 256 e, secondo MDN, la corrispondenza tra byte e code point vale in modo affidabile solo fino a 0x7F. L'intervallo 0x80–0xFF funziona tecnicamente, ma si basa sul comportamento di codifica Latin-1 che non è portabile tra ambienti diversi. Una buona regola: tutto ciò che non è ASCII richiede il percorso UTF-8 descritto di seguito.

Perché btoa() genera InvalidCharacterError su emoji, accenti e CJK

Base64 è una codifica binaria, non una codifica di testo. Il glossario di MDN definisce la pratica di passare testo arbitrario o HTML a btoa() un malinteso comune: btoa/atob sono anteriori agli array tipizzati, quindi usano le stringhe per rappresentare i byte. I problemi iniziano quando la tua stringa non ha un byte per carattere.

Le stringhe JavaScript sono UTF-16, quindi un'unità di codice è sempre due byte e i caratteri fuori dal BMP (come gli emoji) occupano quattro byte come una coppia di surrogate; UTF-8 usa da uno a quattro byte per code point. btoa() e atob() funzionano solo con caratteri a byte singolo (intervallo Latin-1). Prova a codificare qualcosa di più ampio e otterrai esattamente questo errore:

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

Il caso problematico è banale da riprodurre: btoa('✓') genera un errore, così fa btoa('a Ā 𐀀 文 🦄'). Emoji, lettere accentate oltre Latin-1 e caratteri CJK si trovano tutti sopra 0xFF, quindi nessuno di essi si adatta all'assunzione di un byte per carattere. '✓' è U+2713, ben al di sopra di 0xFF, quindi btoa() rifiuta piuttosto che corrompere silenziosamente i tuoi dati. Questo rifiuto è probabilmente la caratteristica migliore dell'API: fallisce in modo evidente invece di produrre mojibake che scopriresti settimane dopo.

Un'altra cosa utile da sapere prima di risolvere questo problema: Base64 rende i dati più grandi, circa del 33% rispetto ai byte grezzi. Va bene per stringhe brevi, token e URI dei dati, ma è un motivo valido per non codificare in Base64 payload grandi: codifica al confine in cui ne hai bisogno, non ovunque.

Un nastro blu 3D morbido che scorre da sinistra dividendosi in due percorsi: un filamento forma gruppi ordinati e raggruppati di…
Un flusso di dati, due impronte di byte: cluster multi-byte contro byte singoli.

Il caso limite delle surrogate isolate (e String.isWellFormed())

La maggior parte degli articoli salta questo punto, ma causa problemi: una stringa UTF-16 contenente una surrogate isolata (metà di una coppia di emoji, spesso dovuta a un bug di slicing) è "malformata". Secondo la guida di web.dev, TextDecoder sostituisce silenziosamente U+FFFD per impostazione predefinita. Prima della codifica, verifica la conformità con String.prototype.isWellFormed(), oppure intercetta l'errore URIError che encodeURIComponent() genera sulle surrogate isolate, quindi decidi esplicitamente: rifiutare i dati, abilitare la sostituzione deliberatamente o generare un errore per analisi successive.

Il metodo corretto: TextEncoder → byte UTF-8 → Base64

La soluzione corretta consiste nel convertire la tua stringa in byte UTF-8 con TextEncoder prima, poi codificare in Base64 i byte invece della stringa. new TextEncoder().encode(str) trasforma qualsiasi stringa JavaScript in un Uint8Array di byte UTF-8 (TextEncoder parla solo UTF-8). Sul lato decodifica, new TextDecoder() usa UTF-8 per impostazione predefinita e lo inverte esattamente. Nota cosa ti restituisce atob() qui: una stringa binaria, un carattere per byte, non testo UTF-8. Ecco perché il lato decodifica necessita di TextDecoder per trasformare i byte nuovamente in testo leggibile.

Helper bytesToBase64() e base64ToBytes() (codice copia-incolla)

Ecco il round trip canonico di MDN, che funziona in ogni browser che supporta TextEncoder (IE escluso):

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));
}

Verifica con un round trip completo

Codificare una stringa diventa una riga:

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

La decodifica la inverte:

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

Il round trip verificato da web.dev conferma lo stesso pattern: 'hello⛳❤️🧀' codifica in 'aGVsbG/im7PinaTvuI/wn6eA' e la decodifica restituisce esattamente l'originale, emoji inclusi.

Salta il trucco unescape(encodeURIComponent())

Troverai btoa(unescape(encodeURIComponent(str))) copiato ovunque. Funziona, ma unescape() è un'eredità dell'Annesso B, deprecato dall'ECMAScript 3. Il nuovo codice dovrebbe usare TextEncoder al suo posto: è esplicito, standardizzato e non dipende da una globale deprecata.

Il modo del 2025: Uint8Array.toBase64() e Uint8Array.fromBase64()

Da settembre 2025, JavaScript dispone di metodi Base64 nativi direttamente su Uint8Array, quindi gli helper per stringhe di byte sopra non sono più l'unica opzione. La proposta Base64 di TC39 ha raggiunto lo Stage 4 ed è parte del linguaggio; la motivazione di TC39 è che base64 non è una codifica di testo, quindi TextEncoder/TextDecoder sono concettualmente lo strumento sbagliato per questo scopo.

Secondo MDN, queste sono funzionalità "Appena disponibili" nella baseline dei browser evergreen. Verifica i runtime di destinazione prima di rilasciare: se supporti browser o versioni Node più vecchie, mantieni il fallback TextEncoder + btoa. MDN preferisce questi metodi rispetto a btoa()/atob() quando hai già un Uint8Array, perché non è necessario creare una stringa di byte grezzi.

Codifica: bytes.toBase64()

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

Una conversione UTF-8, una chiamata al metodo, fatto.

Decodifica: Uint8Array.fromBase64()

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

MDN segnala che fromBase64() dovrebbe essere preferito a atob() perché restituisce un vero array di byte, a meno che i dati decodificati non siano intesi come testo ASCII. Accetta solo l'alfabeto base64 (A–Z, a–z, 0–9, più +/ o -_), il padding = opzionale e ignora gli spazi bianchi ASCII; genera un errore SyntaxError per caratteri esterni all'alfabeto.

Opzioni: alphabet, omitPadding, lastChunkHandling

Il pacchetto di opzioni della specifica ti dà un controllo preciso:

Opzione Valori Cosa fa
alphabet 'base64' (default), 'base64url' +// vs -/_
omitPadding booleano Rimuove il trailing =: new Uint8Array([72]).toBase64() → 'SA==', con omitPadding → 'SA'
lastChunkHandling 'loose' (default), 'strict', 'stop-before-partial' Tolleranza del decoder per l'ultimo chunk

'loose' tollera la mancanza di padding e spazi bianchi. 'strict' richiede chunk esatti da 4 caratteri con bit di overflow azzerati, che è ciò che vuoi quando validi input non attendibili. 'stop-before-partial' è per lo streaming. La modalità loose predefinita tollera anche bit di overflow non nulli, che la modalità strict rifiuta.

Base64 vs base64url: quale alfabeto ti serve?

La base64 standard usa + e / più il padding =, e va bene per gli URL data: poiché questi non hanno segmenti di percorso o query string. base64url (RFC 4648 §5) sostituisce - e _ e solitamente omette il padding, ed è richiesta ogni volta che l'output viaggia all'interno di un URL: ad esempio, i JWT usano base64url per tutti e tre i loro segmenti.

Quando usare base64url

Usa base64url quando la stringa codificata diventa parte di un percorso URL o di un parametro query, dove + e / richiederebbero l'escaping (e il padding = appesantisce le query string). Usa la base64 standard per i data URI, per incorporare byte nei payload JSON o per qualsiasi cosa che non sia un URL.

Come produrre base64url

Tre modi, a seconda dell'ambiente:

  • Browser moderni: bytes.toBase64({ alphabet: 'base64url' })
  • Node 16+: Buffer.from(str).toString('base64url')
  • Post-processing: prendi l'output standard e sostituisci + → -, / → _, poi rimuovi il =

Una sottigliezza da conoscere: RFC 4648 richiede agli encoder di azzerare i bit di overflow non dati nell'ultimo chunk, e i decoder strict potrebbero generare errori se non sono azzerati. Il glossario di MDN fornisce l'esempio di 'VR==', che è lo stesso byte di 'VQ==' con bit di padding sporchi. Genera base64 con un encoder ben comportato e questo problema non si presenterà; se ne scrivi uno tuo, potrebbe presentarsi.

Codificare un file o un'immagine in Base64 (data URLs)

Per i file, la via del browser è FileReader con readAsDataURL(), che produce un full data URL, poi rimuovi il prefisso per ottenere il base64 grezzo. Per le immagini già renderizzate in un canvas, toDataURL('image/png') lo fa in una sola chiamata.

FileReader.readAsDataURL() → data URL → base64 grezzo

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() per le immagini

Se hai disegnato un'immagine su un canvas (resize, crop, ecc.), salta FileReader completamente:

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

Vale la stessa rimozione del prefisso se ti serve la stringa base64 nuda e cruda.

Data URL ai byte

La direzione inversa è un fetch più un array buffer. L'esempio di MDN: un data URL costruito da new Uint8Array([0,1,2]) è 'data:application/octet-stream;base64,AAEC', e puoi recuperare i byte in questo modo:

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

Ed è il promemoria per cui esiste questo articolo: non chiamare btoa() su testo o HTML arbitrari per costruire data: URL. È un encoder byte-to-byte e l'input non Latin-1 genererà un errore.

Node.js: Buffer.from(str, 'base64') — e la sua unica differenza pericolosa

Node ha i suoi idiomi che saltano completamente il passaggio con TextEncoder: Buffer.from('Hello, world! 🌍').toString('base64') gestisce Unicode automaticamente, perché Buffer lavora sui byte fin dall'inizio. Per decodificare usa Buffer.from(encoded, 'base64').toString('utf8').

Altre forme utili: Buffer.from(str).toString('base64url') (Node 16+), e interi file tramite fs.readFileSync('image.png').toString('base64').

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

Node non valida la tua base64 (fallimenti silenziosi)

Ecco la parte che sorprende le persone: il decoder base64 di Node non valida l'input. Buffer.from('!!!!', 'base64').toString() restituisce '' senza errori, e Buffer.from('aGVsbG8!!!', 'base64') scarta silenziosamente la spazzatura e restituisce 'hello'. Confrontalo con atob('aGVsbG8!!!'), che genera InvalidCharacterError. Se la base64 proviene da utenti, da un'API o da terzi, validala prima di decodificarla in Node, oppure usa Uint8Array.fromBase64() con lastChunkHandling: 'strict' dove i runtime lo supportano.

btoa/atob sono deprecati? Browser vs Node

Qui le fonti si contraddicono davvero, quindi ecco una risposta onesta. Nei browser, MDN segna btoa() e atob() come ampiamente disponibili nella Baseline da luglio 2015, non deprecati. In Node, le type definitions li segnano come @deprecated con il commento che "non dovrebbero mai essere usati in nuovo codice", raccomandando invece Buffer.from(str, 'base64').

Una buona regola: è un'API legacy, non rimossa. Va bene nel codice del browser, preferisci Buffer nel codice specifico per Node. Per consenso della community (non una garanzia della specifica), btoa/atob sono anche la scelta pratica cross-environment se ti serve un unico percorso di codice che giri invariato in browser, Deno e Node senza dipendenze.

Cosa usare effettivamente

Se prendi un solo consiglio da questo articolo, prendi le tre righe di codice qui sotto. Mantieni btoa/atob per ASCII puro e codebase legacy, e usa la funzione helper bytesToBase64 come fallback per il browser, finché toBase64() non copre tutto il tuo pubblico.

// 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');

Prima di rilasciare, verifica il tuo output con un rapido round trip in uno strumento nel browser. Incolla 'Hello, world!' nel Encoder / Decoder Base64 gratuito di Tool Cloud, conferma di ottenere 'SGVsbG8sIHdvcmxkIQ==', poi decodificalo di nuovo e controlla che sia identico. Esistono anche un convertitore Text to Base64 e un convertitore Base64 to Text autonomi; i file vengono eliminati automaticamente e non serve installare nulla.

Domande frequenti

Perché btoa() fallisce con gli emoji ma funziona con il testo inglese?

btoa() tratta ogni carattere come un singolo byte e richiede che ogni code point sia inferiore a 256 (in modo affidabile solo fino a 0x7F). Emoji, caratteri accentati e testo CJK richiedono più byte, quindi generano un DOMException: InvalidCharacterError. Converti prima in byte UTF-8 con TextEncoder, poi codifica i byte.

btoa() è deprecato?

Nei browser no — MDN segnala btoa() e atob() come Baseline ampiamente disponibile da luglio 2015. Tuttavia, le definizioni dei tipi di Node li contrassegna come deprecati a favore di Buffer.from(str, 'base64') nel codice specifico per Node. Si tratta di un'API legacy, non rimossa.

Come codifico in Base64 il testo Unicode (accenti, cinese, emoji)?

Usa new TextEncoder().encode(str) per ottenere i byte UTF-8, poi codifica i byte (btoa() tramite un helper per stringhe di byte, o Uint8Array.toBase64() nei browser moderni). Per decodificare, usa Uint8Array.fromBase64() o atob() per riottenere i byte, poi new TextDecoder().decode(bytes).

Qual è la differenza tra base64 e base64url?

base64url (RFC 4648 §5) usa '-' e '_' invece di '+' e '/', e solitamente omette il padding '='. Usa base64url quando l'output finisce negli URL o nei JWT; il base64 standard va bene per i data: URL, che non hanno segmenti di percorso o query string.

Perché Node.js non genera un errore su un Base64 non valido?

Buffer.from(str, 'base64') di Node non convalida l'input: ignora silenziosamente i caratteri non validi, quindi Buffer.from('aGVsbG8!!!', 'base64').toString() restituisce 'hello' senza errori. Nei browser, atob() genera InvalidCharacterError sullo stesso input. Convalida sempre il Base64 non attendibile prima di decodificarlo in Node.