Tools

Bild Video Audio Text Vektor Entwickler Allgemein

⚡ 20 kostenlose Credits heute übrig

Kostenlos registrieren

So konvertierst du Text in Base64 mit JavaScript (und decodierst ihn)

Die One-Liner, die funktioniert, und der, der fehlschlägt – warum das so ist. Plus die UTF-8-sichere Rundreise, die nativen Methoden von 2025 und Node.js-Unterschiede.

Weiche 3D-Illustration: Schichtweise angeordnete blaue Dokumentenformen verwandeln sich in ein Gitter aus codierten blauen Würfeln, die in… zerfließen

Um einen String in JavaScript in Base64 zu kodieren, rufe btoa('Hello, world!') auf, was 'SGVsbG8sIHdvcmxkIQ==' zurückgibt. Dieser One-Liner funktioniert jedoch nur für ASCII- und Latin-1-Text: Sobald du ein Emoji, einen Akzent oder ein chinesisches Zeichen übergibst, wirft er InvalidCharacterError. Dieser Leitfaden zeigt, warum das passiert, und die UTF-8-sichere Rundreise, die das Problem behebt, einschließlich der neuen nativen Methoden von 2025.

Base64 in einer Zeile: btoa() und atob()

Die schnellste Antwort: Kodiere mit btoa('Hello, world!'), decodiere mit atob('SGVsbG8sIHdvcmxkIQ=='). Beide verifiziert: btoa('a') gibt 'YQ==' zurück, und atob('SA==') gibt 'S' zurück.

Was btoa() tatsächlich tut, ist, deinen String als „Binary String“ zu behandeln – also einen String, bei dem jedes Zeichen genau einem Byte binärer Daten entspricht – und diese Bytes dann in Base64 zu kodieren. Der Anwendungsfall laut MDN sind Daten, die Übertragungsprobleme verursachen, wie ASCII-Steuerzeichen (0–31), die du durch einen textbasierten Kanal schleusen möchtest.

Dieser One-Liner reicht aus, wenn dein Text ASCII ist: Der Codepoint jedes Zeichens muss unter 256 liegen, und laut MDN gilt die Byte-zu-Codepoint-Zuordnung nur zuverlässig bis 0x7F. Der Bereich 0x80–0xFF funktioniert technisch zwar, basiert aber auf Latin-1-Kodierungsverhalten, das nicht portabel über verschiedene Umgebungen hinweg ist. Eine gute Regel: Alles, was nicht-ASCII ist, benötigt den unten beschriebenen UTF-8-Weg.

Warum btoa() bei Emojis, Akzenten und CJK einen InvalidCharacterError wirft

Base64 ist eine Binärkodierung, keine Textkodierung. MDNs Glossar bezeichnet die Gewohnheit, beliebigen Text oder HTML an btoa() zu übergeben, als häufiges Missverständnis: btoa/atob sind älter als typisierte Arrays, daher verwenden sie Strings, um Bytes zu repräsentieren. Das Problem beginnt, wenn dein String nicht ein Byte pro Zeichen hat.

JavaScript-Strings sind UTF-16, daher ist eine Code-Einheit immer zwei Bytes, und Zeichen außerhalb des BMP (wie Emojis) benötigen vier Bytes als Surrogate-Pair; UTF-8 verwendet ein bis vier Bytes pro Codepoint. btoa() und atob() funktionieren nur mit Single-Byte-Zeichen (Latin-1-Bereich). Wenn du versuchst, etwas Breiteres zu kodieren, erhältst du genau diesen Fehler:

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

Der bruchauslösende Fall ist trivial nachzustellen: btoa('✓') wirft, und ebenso btoa('a Ā 𐀀 文 🦄'). Emojis, akzentierte Buchstaben jenseits von Latin-1 und CJK-Zeichen liegen alle oberhalb von 0xFF, sodass keines davon der Annahme eines Bytes pro Zeichen entspricht. '✓' ist U+2713, weit oberhalb von 0xFF, daher lehnt btoa() ab, statt deine Daten stillschweigend zu beschädigen. Diese Ablehnung ist wohl das beste Feature der API: Sie scheitert lautstark, anstatt Mojibake zu produzieren, das du erst Wochen später entdeckst.

Noch ein Hinweis, den es vor der Behebung zu wissen lohnt: Base64 macht Daten größer, nämlich um ca. 33 % im Vergleich zu den rohen Bytes. Das ist für kurze Strings, Tokens und Data-URIs in Ordnung, aber ein echter Grund, große Payloads nicht in Base64 zu kodieren – kodiere an der Grenze, an der du es brauchst, nicht überall.

Ein weiches, blaues 3D-Band, das von links einfließt und sich in zwei Pfade teilt: Ein Strang bildet ordentliche, gruppierte Cluster von…
Ein Datenstrom, zwei Byte-Fußabdrücke: Multi-Byte-Cluster versus Einzelbytes.

Der Edgecase des einzelnen Surrogats (und String.isWellFormed())

Die meisten Artikel überspringen dies, aber es hinterlässt Spuren: Ein UTF-16-String, der ein isoliertes Surrogat enthält (die Hälfte eines Emoji-Pairs, oft durch einen Slicing-Bug entstanden), ist „malformed“. Laut web.dev-Leitfaden substituiert TextDecoder standardmäßig stillschweigend U+FFFD. Prüfe vor der Kodierung die Wohlgeformtheit mit String.prototype.isWellFormed() oder fange den URIError ab, den encodeURIComponent() bei isolierten Surrogaten wirft, und entscheide dann explizit: Daten ablehnen, Ersetzung bewusst aktivieren oder einen Fehler zur späteren Analyse werfen.

Der korrekte Weg: TextEncoder → UTF-8-Bytes → Base64

Die korrekte Lösung besteht darin, deinen String zuerst mit TextEncoder in UTF-8-Bytes umzuwandeln und dann die Bytes statt des Strings in Base64 zu kodieren. new TextEncoder().encode(str) wandelt jeden JavaScript-String in ein Uint8Array aus UTF-8-Bytes um (TextEncoder spricht nur UTF-8). Auf dem Rückweg nimmt new TextDecoder() standardmäßig UTF-8 an und kehrt den Vorgang exakt um. Beachte, was atob() dir hier liefert: einen Binary String, ein Zeichen pro Byte, keinen UTF-8-Text. Deshalb benötigt die Decodierungsseite TextDecoder, um die Bytes wieder in lesbaren Text umzuwandeln.

bytesToBase64()- und base64ToBytes()-Helfer (Copy-Paste-Code)

Hier ist MDNs kanonische Rundreise, die in jedem Browser läuft, der TextEncoder unterstützt (IE ausgeschlossen):

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

Verifiziere mit einer vollständigen Rundreise

Das Kodieren eines Strings wird zu einer Zeile:

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

Das Decodieren kehrt es um:

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

Die verifizierte Rundreise auf web.dev bestätigt dasselbe Muster: 'hello⛳❤️🧀' kodiert zu 'aGVsbG/im7PinaTvuI/wn6eA' und decodiert exakt zurück, inklusive Emojis.

Überspringe den unescape(encodeURIComponent())-Trick

Du wirst btoa(unescape(encodeURIComponent(str))) überall kopiert finden. Es funktioniert, aber unescape() ist Annex B Legacy und seit ECMAScript 3 veraltet. Neuer Code sollte stattdessen TextEncoder verwenden: Es ist explizit, standardisiert und hängt nicht von einer veralteten globalen Funktion ab.

Der Weg von 2025: Uint8Array.toBase64() und Uint8Array.fromBase64()

Seit September 2025 bietet JavaScript native Base64-Methoden direkt auf Uint8Array, sodass die oben genannten Byte-String-Helfer nicht mehr die einzige Option sind. Das TC39-Base64-Proposal hat Stufe 4 erreicht und ist Teil der Sprache; die Begründung von TC39 lautet, dass Base64 keine Textkodierung ist, daher sind TextEncoder/TextDecoder konzeptionell das falsche Werkzeug dafür.

Laut MDN sind diese Methoden in allen modernen Browsern als „Neu verfügbar" im Baseline-Status gelistet. Prüfe deine Zielumgebungen, bevor du deployst: Falls du ältere Browser oder ältere Node-Versionen unterstützen musst, behalte das TextEncoder + btoa Fallback. MDN bevorzugt diese Methoden gegenüber btoa()/atob(), wenn du bereits einen Uint8Array hast, da dabei kein Roh-Byte-String erstellt werden muss.

Kodierung: bytes.toBase64()

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

Eine UTF-8-Konvertierung, ein Methodenaufruf, fertig.

Dekodierung: Uint8Array.fromBase64()

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

MDN empfiehlt, fromBase64() gegenüber atob() zu verwenden, da es ein echtes Byte-Array zurückgibt, es sei denn, die dekodierten Daten sollen als ASCII-Text interpretiert werden. Es akzeptiert nur das Base64-Alphabet (A–Z, a–z, 0–9, plus +/ oder -_), optionale =-Polsterung und ignoriert ASCII-Leerraum; bei Zeichen außerhalb des Alphabets wird SyntaxError geworfen.

Optionen: alphabet, omitPadding, lastChunkHandling

Das Optionsobjekt der Spezifikation gibt dir präzise Kontrolle:

Option Werte Funktion
alphabet 'base64' (Standard), 'base64url' +// vs -/_
omitPadding boolean Entfernt abschließendes =: new Uint8Array([72]).toBase64() → 'SA==', mit omitPadding → 'SA'
lastChunkHandling 'loose' (Standard), 'strict', 'stop-before-partial' Toleranz des Decoders für den letzten Chunk

'loose' toleriert fehlende Polsterung und Leerraum. 'strict' erfordert exakte 4-Zeichen-Chunks mit auf Null gesetzten Überlaufbits, was du beim Validieren von nicht vertrauenswürdigen Eingaben möchtest. 'stop-before-partial' ist für Streaming gedacht. Der Standardmodus (loose) toleriert auch nicht-null Überlaufbits, was der strenge Modus ablehnt.

Base64 vs base64url: Welches Alphabet brauchst du?

Standard-Base64 verwendet + und / sowie =-Polsterung und ist für data:-URLs in Ordnung, da diese keine Pfadsegmente oder Query-Strings enthalten. base64url (RFC 4648 §5) ersetzt dies durch - und _ und lässt die Polsterung meist weg; sie ist erforderlich, wenn die Ausgabe innerhalb einer URL übertragen wird: JWTs verwenden beispielsweise für alle drei Segmente base64url.

Wann base64url verwenden

Verwende base64url, wenn der kodierte String Teil eines URL-Pfads oder Query-Parameters wird, wo + und / maskiert werden müssten (und =-Polsterung Query-Strings unübersichtlich macht). Verwende Standard-Base64 für Data URIs, zum Einbetten von Bytes in JSON-Payloads oder für alles, was keine URL ist.

So erzeugst du base64url

Drei Möglichkeiten, je nach Umgebung:

  • Moderne Browser: bytes.toBase64({ alphabet: 'base64url' })
  • Node 16+: Buffer.from(str).toString('base64url')
  • Nachbearbeitung: Nimm die Standardausgabe und ersetze + → -, / → _, dann entferne die =

Eine feine Nuance, die man kennen sollte: RFC 4648 verlangt von Encodern, die nicht-Daten-Überlaufbits im letzten Chunk auf Null zu setzen, und strenge Decoder können Fehler werfen, wenn sie nicht null sind. MDNs Glossar liefert das Beispiel von 'VR==', das dasselbe Byte wie 'VQ==' ist, nur mit „schmutzigen" Polsterbits. Erzeuge Base64 mit einem gut funktionierenden Encoder, dann tritt das Problem nie auf; schreibst du einen eigenen, kann es passieren.

Eine Datei oder ein Bild in Base64 kodieren (Data URLs)

Für Dateien ist der Browserweg FileReader mit readAsDataURL(), was eine vollständige Data URL erzeugt; danach entfernst du das Präfix, um das reine Base64 zu erhalten. Für Bilder, die bereits in einem Canvas gerendert wurden, erledigt toDataURL('image/png') das in einem einzigen Aufruf.

FileReader.readAsDataURL() → Data URL → reines Base64

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() für Bilder

Wenn du ein Bild auf einen Canvas gezeichnet hast (Resize, Crop, was auch immer), überspringe FileReader komplett:

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

Die gleiche Entfernung des Präfixes gilt, wenn du nur den nackten Base64-String benötigst.

Data URL zurück in Bytes wandeln

Der umgekehrte Weg ist ein Fetch plus ein Array-Buffer. MDNs Beispiel: Eine Data URL, die aus new Uint8Array([0,1,2]) erstellt wurde, ist 'data:application/octet-stream;base64,AAEC', und so stellst du die Bytes wieder her:

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

Und noch die Erinnerung, wofür dieser ganze Artikel existiert: Rufe btoa() nicht auf beliebigen Text oder HTML auf, um Data URLs zu erstellen. Es ist ein Byte-zu-Byte-Encoder, und Eingaben außerhalb von Latin-1 führen zu Fehlern.

Node.js: Buffer.from(str, 'base64') – und der eine gefährliche Unterschied

Node hat eigene Idiome, die den TextEncoder-Umweg komplett überflüssig machen: Buffer.from('Hello, world! 🌍').toString('base64') behandelt Unicode automatisch, weil Buffer von Anfang an auf Bytes arbeitet. Dekodieren tust du mit Buffer.from(encoded, 'base64').toString('utf8').

Weitere nützliche Formen: Buffer.from(str).toString('base64url') (Node 16+), und ganze Dateien über fs.readFileSync('image.png').toString('base64').

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

Node validiert dein Base64 nicht (stille Fehler)

Hier kommt der Punkt, der viele überrascht: Der Base64-Decoder von Node validiert die Eingabe nicht. Buffer.from('!!!!', 'base64').toString() gibt '' ohne Fehler zurück, und Buffer.from('aGVsbG8!!!', 'base64') verwirft Müll stillschweigend und gibt 'hello' zurück. Vergleiche das mit atob('aGVsbG8!!!'), das InvalidCharacterError wirft. Wenn das Base64 von Nutzern, einer API oder Drittanbietern stammt, validiere es vor dem Dekodieren in Node, oder verwende Uint8Array.fromBase64() mit lastChunkHandling: 'strict', wo die Laufzeitumgebungen es unterstützen.

Sind btoa/atob veraltet? Browser vs Node

Das ist wirklich widersprüchlich, also hier die ehrliche Einordnung. In Browsern markiert MDN btoa() und atob() als seit Juli 2015 breit verfügbar im Baseline-Status, nicht als veraltet. In Node markieren die Typdefinitionen sie als @deprecated mit dem Kommentar, dass sie „in neuem Code niemals verwendet werden sollten“, und empfehlen stattdessen Buffer.from(str, 'base64').

Eine gute Faustregel: Es ist eine Legacy-API, keine entfernte. In Browser-Code völlig in Ordnung, in Node-spezifischem Code lieber Buffer verwenden. Nach Konsens der Community (keine Spezifikationsgarantie) sind btoa/atob auch die praktische Wahl, wenn du einen Codepfad brauchst, der ohne Abhängigkeiten unverändert in Browsern, Deno und Node läuft.

Was du tatsächlich verwenden solltest

Wenn du nur eine Empfehlung aus diesem Artikel mitnimmst, dann die folgenden drei One-Liner. Behalte btoa/atob für reines ASCII und Legacy-Codebasen bei und nutze die bytesToBase64-Hilfsfunktion als Browser-Fallback, bis toBase64() deine gesamte Zielgruppe abdeckt.

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

Prüfe vor dem Release deine Ausgabe mit einem schnellen Roundtrip-Test in einem Browser-Tool. Füge 'Hello, world!' in Tool Clouds kostenlosem Base64 Encoder / Decoder ein, bestätige, dass du 'SGVsbG8sIHdvcmxkIQ==' erhältst, dekodiere es dann zurück und prüfe, ob es identisch ist. Es gibt auch einen eigenständigen Text-zu-Base64-Konverter und Base64-zu-Text-Konverter; Dateien werden automatisch gelöscht und nichts muss installiert werden.

Häufig gestellte Fragen

Warum schlägt btoa() bei Emojis fehl, funktioniert aber mit englischem Text?

btoa() behandelt jedes Zeichen als ein einzelnes Byte und erfordert, dass jeder Codepoint unter 256 liegt (zuverlässig nur bis 0x7F). Emojis, akzentierte Zeichen und CJK-Text benötigen mehrere Bytes, weshalb sie einen DOMException: InvalidCharacterError auslösen. Konvertiere zuerst mit TextEncoder in UTF-8-Bytes und kodiere dann diese Bytes.

Ist btoa() veraltet?

Nicht in Browsern – MDN stuft btoa() und atob() als „Baseline widely available“ seit Juli 2015 ein. In Node.js kennzeichnen die Typdefinitionen sie jedoch als veraltet zugunsten von Buffer.from(str, 'base64') in Node-spezifischem Code. Es handelt sich um eine Legacy-API, nicht um eine entfernte.

Wie kodiere ich Unicode-Text (Akzente, Chinesisch, Emojis) in Base64?

Verwende new TextEncoder().encode(str), um UTF-8-Bytes zu erhalten, und kodiere dann die Bytes (btoa() über einen Byte-String-Helfer oder Uint8Array.toBase64() in modernen Browsern). Zum Decodieren verwende Uint8Array.fromBase64() oder atob(), um die Bytes zurückzubekommen, und dann new TextDecoder().decode(bytes).

Was ist der Unterschied zwischen base64 und base64url?

base64url (RFC 4648 §5) verwendet '-' und '_' statt '+' und '/' und lässt das '='-Padding meist weg. Nutze base64url, wenn die Ausgabe in URLs oder JWTs gelangt; Standard-base64 ist für data:-URLs in Ordnung, da diese keine Pfadsegmente oder Query-Strings haben.

Warum wirft Node.js keinen Fehler bei ungültigem Base64?

Der Buffer.from(str, 'base64') von Node validiert die Eingabe nicht: Er ignoriert ungültige Zeichen stillschweigend, sodass Buffer.from('aGVsbG8!!!', 'base64').toString() 'hello' ohne Fehler zurückgibt. In Browsern löst atob() bei derselben Eingabe einen InvalidCharacterError aus. Validiere nicht vertrauenswürdiges Base64 in Node, bevor du es decodierst.