btoa und atob: nur Latin-1
btoa („binary to ASCII“) kodiert einen String, in dem jedes Zeichen als ein Byte behandelt wird. atob kehrt das um. Für ASCII funktioniert das:
btoa("hello"); // "aGVsbG8="
atob("aGVsbG8="); // "hello"
Bei allem außerhalb der Codepoints 0–255 scheitert es:
btoa("héllo ✓");
// InvalidCharacterError: Failed to execute 'btoa' on 'Window':
// The string to be encoded contains characters outside of the Latin1 range.
Schlimmer noch: Zeichen, die in Latin-1 passen, werden ohne Fehler kodiert, aber als falsche Bytes. btoa("héllo") liefert aOlsbG8= (Latin-1 é = 0xE9), während jede andere Sprache und der Base64-Encoder die UTF-8-Version aMOpbGxv erzeugen.
btoa und atob gibt es ab Node.js 16 auch als Legacy-Globals, auf dem Server ist die Buffer-API aber schneller und hat eine eingebaute base64url-Kodierung. Siehe Base64 in Node.js.
UTF-8-Strings kodieren und dekodieren
Die portable Lösung: Den String mit TextEncoder in UTF-8-Bytes umwandeln und diese Bytes dann als „Binärstring“ an btoa übergeben:
function encodeUtf8(str) {
const bytes = new TextEncoder().encode(str);
let bin = "";
for (const b of bytes) bin += String.fromCharCode(b);
return btoa(bin);
}
function decodeUtf8(b64) {
const bin = atob(b64);
const bytes = Uint8Array.from(bin, c => c.charCodeAt(0));
return new TextDecoder().decode(bytes);
}
encodeUtf8("héllo wörld"); // "aMOpbGxvIHfDtnJsZA=="
decodeUtf8("aMOpbGxvIHfDtnJsZA=="); // "héllo wörld"
atob("aMOpbGxvIHfDtnJsZA=="); // "héllo wörld" (no TextDecoder)
Der alte Trick btoa(unescape(encodeURIComponent(str))) liefert dasselbe Ergebnis, stützt sich aber auf das veraltete unescape.
Uint8Array.toBase64 und fromBase64
Die neuere API arbeitet direkt auf Bytes, ganz ohne Umweg über Binärstrings. Sie wird von Chrome und Edge ab 140, Firefox ab 133 und Safari ab 18.2 unterstützt.
const bytes = new TextEncoder().encode("héllo wörld");
bytes.toBase64(); // "aMOpbGxvIHfDtnJsZA=="
new TextDecoder().decode(Uint8Array.fromBase64("aMOpbGxvIHfDtnJsZA=="));
// "héllo wörld"
Dazu gibt es setFromBase64, das in einen vorhandenen Puffer dekodiert und meldet, wie viel es verbraucht hat:
const target = new Uint8Array(8);
target.setFromBase64("aGVsbG8K"); // { read: 8, written: 6 }
Prüfen Sie die Unterstützung, bevor Sie sich darauf verlassen: typeof Uint8Array.prototype.toBase64 === "function". Die Anleitung zu Uint8Array.toBase64 behandelt jede Option.
URL-sicheres Base64 mit und ohne Padding
Mit der neuen API übergeben Sie Optionen:
const d = new TextEncoder().encode("<<???>>");
d.toBase64({ alphabet: "base64url" }); // "PDw_Pz8-Pg=="
d.toBase64({ alphabet: "base64url", omitPadding: true }); // "PDw_Pz8-Pg"
Uint8Array.fromBase64("PDw_Pz8-Pg", { alphabet: "base64url" }); // bytes of "<<???>>"
Mit btoa/atob übersetzen Sie das Alphabet selbst:
const toB64Url = b64 =>
b64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
const fromB64Url = s => {
s = s.replace(/-/g, "+").replace(/_/g, "/");
return s + "=".repeat((4 - s.length % 4) % 4);
};
toB64Url(btoa("<<???>>")); // "PDw_Pz8-Pg"
atob(fromB64Url("PDw_Pz8-Pg")); // "<<???>>"
Genau das verwenden JWTs; siehe Base64 vs. Base64URL, oder fügen Sie ein Token in den JWT-Decoder ein.
Dateien, Blobs und große Datenmengen
Um eine vom Nutzer ausgewählte Datei zu lesen, liefert FileReader.readAsDataURL eine Data-URI. Für reines Base64 entfernen Sie alles bis einschließlich Komma:
const reader = new FileReader();
reader.onload = () => {
const dataUrl = reader.result; // "data:text/plain;base64,aGVsbG8K"
const b64 = dataUrl.slice(dataUrl.indexOf(",") + 1);
};
reader.readAsDataURL(file);
Mit der neuen API geht es kürzer: new Uint8Array(await file.arrayBuffer()).toBase64().
Um aus Base64 wieder eine herunterladbare Datei zu machen, dekodieren Sie in Bytes und verpacken sie in einen Blob:
const bytes = Uint8Array.fromBase64(b64);
const url = URL.createObjectURL(new Blob([bytes], { type: "application/pdf" }));
Große Puffer sollten Sie nie per Spread an String.fromCharCode(...bytes) übergeben. Bei 500.000 Bytes wirft Chromium RangeError: Maximum call stack size exceeded. Bauen Sie den Binärstring stattdessen in Abschnitten:
function bytesToBase64(bytes) {
let bin = "";
for (let i = 0; i < bytes.length; i += 0x8000) {
bin += String.fromCharCode.apply(null, bytes.subarray(i, i + 0x8000));
}
return btoa(bin);
}
// 5,000,000 bytes -> 6,666,668 characters
Die Ausgabe ist etwa ein Drittel größer als die Eingabe; die genaue Formel steht in der Anleitung zum Größenzuwachs.
Validierung und Fehlermeldungen
atob ignoriert ASCII-Leerraum und toleriert fehlendes Padding, atob(" aG k= ") und atob("aGk") liefern also beide "hi". Zeichen außerhalb des Standardalphabets lehnt es ab:
atob("PDw_Pz8-Pg");
// InvalidCharacterError: Failed to execute 'atob' on 'Window':
// The string to be decoded is not correctly encoded.
fromBase64 wirft einen SyntaxError und lässt sich mit lastChunkHandling strenger einstellen:
Uint8Array.fromBase64("PDw_Pz8-Pg");
// SyntaxError: Found a character that cannot be part of a valid base64 string.
Uint8Array.fromBase64("aGl="); // 2 bytes, accepted
Uint8Array.fromBase64("aGl=", { lastChunkHandling: "strict" });
// SyntaxError: The base64 input terminates with non-zero padding bits.
Der Standard "loose" akzeptiert fehlendes Padding. "strict" verlangt korrektes Padding und Padding-Bits gleich null; "stop-before-partial" lässt einen unvollständigen letzten Block für Streaming-Decoder übrig. Der Meldungstext unterscheidet sich zwischen Browsern, die Fehlernamen sind aber standardisiert. Für eine visuelle Prüfung markiert der Base64-Validator das genaue Problem, und Häufige Base64-Fehler listet die Ursachen auf.
Häufige Fragen
Warum wirft btoa einen InvalidCharacterError?
btoa akzeptiert nur Zeichen im Latin-1-Bereich (Codepoints 0–255). Jedes Emoji, jedes kyrillische oder CJK-Zeichen löst den Fehler aus. Wandeln Sie den String zuerst mit TextEncoder in UTF-8-Bytes um oder verwenden Sie Uint8Array.prototype.toBase64().
Kann ich Uint8Array.toBase64 in Produktion einsetzen?
Es ist in aktuellen Versionen von Chrome, Edge, Firefox und Safari enthalten, nicht aber in älteren Browsern oder in Node.js 24. Prüfen Sie mit typeof Uint8Array.prototype.toBase64 === "function" und greifen Sie sonst auf die btoa-Hilfsfunktion zurück. Details unter Uint8Array.toBase64 erklärt.
Warum liefert atob seltsame Zeichen wie é statt é?
atob liefert ein Zeichen pro Byte, eine 2-Byte-UTF-8-Sequenz wird also zu zwei Latin-1-Zeichen. Schicken Sie das Ergebnis durch Uint8Array.from(bin, c => c.charCodeAt(0)) und TextDecoder, um korrekten Text zu erhalten.
Wie dekodiere ich Base64URL mit atob?
Ersetzen Sie - durch + und _ durch / und füllen Sie dann mit = auf ein Vielfaches von 4 Zeichen auf. Mit der neuen API rufen Sie Uint8Array.fromBase64(s, { alphabet: "base64url" }) auf und sparen sich beide Schritte.