> ## Content Index
> Fetch the complete content index at: https://www.tool-cloud.com/blog/nl/llms.txt
> Use this file to discover other available public pages before exploring further.

# Hoe converteer je tekst naar Base64 in JavaScript (en decodeer het)
- URL: https://www.tool-cloud.com/blog/nl/hoe-converteer-je-tekst-naar-base64-in-javascript-en-decodeer-het/
- Published: 2026-10-14T18:18:34.000Z
- Updated: 2026-10-14T18:28:59.000Z
- Description: De one-liner die werkt, degene die een fout geeft, en waarom — plus de UTF-8-veilige roundtrip, de native methoden van 2025 en de verschillen in Node.js.
- Author: Tool Cloud Team
- Tags: Developers, Gidsen

Om een string Base64-gecodeerd te maken in JavaScript, roep je `btoa('Hello, world!')` aan, wat `'SGVsbG8sIHdvcmxkIQ=='` retourneert. Deze one-liner werkt alleen voor ASCII- en Latin-1-tekst: zodra je een emoji, een accent of een Chinees karakter doorgeeft, gooit hij `InvalidCharacterError`. Deze gids toont waarom dit gebeurt en de UTF-8-veilige roundtrip die het oplost, inclusief de nieuwe native methoden van 2025.

## Base64 in één regel: btoa() en atob()

Het snelste antwoord: encodeer met `btoa('Hello, world!')`, decodeer met `atob('SGVsbG8sIHdvcmxkIQ==')`. Beide geverifieerd: `btoa('a')` retourneert `'YQ=='`, en `atob('SA==')` retourneert `'S'`.

Wat [btoa()](https://developer.mozilla.org/en-US/docs/Web/API/Window/btoa) eigenlijk doet, is je string behandelen als een "binary string", een string waarin elk teken staat voor precies één byte binaire data, en die bytes vervolgens Base64-encoderen. Het MDN-usecase is data die transmissieproblemen veroorzaakt, zoals ASCII-besturingskarakters (0–31), die je door een tekstkanaal wilt smokkelen.

Deze one-liner is voldoende als je tekst ASCII is: het codepunt van elk teken moet onder de 256 liggen, en volgens MDN geldt de byte-naar-codepunt-mapping alleen betrouwbaar tot 0x7F. Het bereik 0x80–0xFF werkt technisch gezien wel, maar het vertrouwt op Latin-1-encodinggedrag dat niet draagbaar is over omgevingen heen. Een goede vuistregel: alles wat niet-ASCII is, volgt het UTF-8-pad hieronder.

## Waarom btoa() InvalidCharacterError gooit op emoji, accenten en CJK

Base64 is een binaire encoding, geen tekstencoding. [MDN's glossarium](https://developer.mozilla.org/en-US/docs/Glossary/Base64) noemt de gewoonte om willekeurige tekst of HTML aan `btoa()` te geven een veelvoorkomend misverstand: `btoa`/`atob` zijn ouder dan getypte arrays, dus ze gebruiken strings om bytes voor te stellen. Het probleem begint wanneer je string niet één byte per teken is.

JavaScript-strings zijn UTF-16, dus een code-eenheid is altijd twee bytes, en tekens buiten het BMP (zoals emoji) nemen vier bytes in beslag als surrogate pair; UTF-8 gebruikt één tot vier bytes per codepoint. `btoa()` en `atob()` werken alleen met single-byte (Latin-1-bereik) karakters. Probeer iets breder te encoderen en je krijgt deze exacte fout:

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

```

Het brekende geval is triviaal te reproduceren: `btoa('✓')` gooit een fout, en `btoa('a Ā 𐀀 文 🦄')` doet hetzelfde. Emoji, letters met accenten buiten Latin-1 en CJK-karakters zitten allemaal boven 0xFF, dus geen van ze past binnen de aanname van één byte per teken. `'✓'` is U+2713, ver boven 0xFF, dus `btoa()` weigert in plaats van je data stilzwijgend te corrupteren. Die weigering is misschien wel de beste eigenschap van de API: hij faalt luidruchtig in plaats van mojibake te produceren die je weken later ontdekt.

Nog een ding dat de moeite waard is om te weten voordat we dit oplossen: Base64 maakt data groter, met ongeveer 33% ten opzichte van de ruwe bytes. Dat is prima voor korte strings, tokens en data-URI's, maar een echte reden om grote payloads niet te Base64-encoden — encodeer bij de grens van je systeem waar je het nodig hebt, niet overal.

![Een zacht blauw 3D-lint dat van links binnenstroomt en splitst in twee paden: één streng vormt nette gegroepeerde clusters van…](https://www.tool-cloud.com/blog/nl/content/images/2026/10/javascript-base64-encode-utf8-split.webp)

Één gegevensstroom, twee byte-voetafdrukken: multi-byte clusters versus single bytes.

### Het edge case van de lone surrogate (en String.isWellFormed())

De meeste artikelen slaan dit over, maar het kan pijn doen: een UTF-16-string die een lone surrogate bevat (de helft van een emoji-paar, meestal door een slicing-bug) is "malformed". Volgens [web.dev's gids](https://web.dev/articles/base64-encoding) substitueert `TextDecoder` standaard stilzwijgend U+FFFD. Controleer vóór het encoderen de welgevormdheid met `String.prototype.isWellFormed()`, of vang de `URIError` af die `encodeURIComponent()` gooit bij lone surrogates, en besluit dan expliciet: wijs de data af, schakel vervanging bewust in, of gooi een fout voor latere analyse.

## De juiste manier: TextEncoder → UTF-8-bytes → Base64

De juiste oplossing is om je string eerst met `TextEncoder` naar UTF-8-bytes te converteren, en daarna de bytes Base64-gecodeerd te maken in plaats van de string. `new TextEncoder().encode(str)` zet elke JavaScript-string om in een `Uint8Array` van UTF-8-bytes (TextEncoder spreekt alleen UTF-8). Op de terugweg gebruikt `new TextDecoder()` standaard UTF-8 en zet het exact terug. Let op wat `atob()` je hier geeft: een binary string, één karakter per byte, geen UTF-8-tekst. Daarom heeft de decodeerkant `TextDecoder` nodig om bytes terug te zetten in leesbare tekst.

### bytesToBase64() en base64ToBytes() helpers (copy-paste code)

Hier is [MDN's canonieke roundtrip](https://developer.mozilla.org/en-US/docs/Web/API/Window/btoa), die in elke browser werkt die `TextEncoder` ondersteunt (IE uitgezonderd):

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

```

### Verifieer met een volledige roundtrip

Een string encoderen wordt één regel:

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

```

Decoderen is het omgekeerde:

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

```

web.dev's geverifieerde roundtrip bevestigt hetzelfde patroon: `'hello⛳❤️🧀'` encodeert naar `'aGVsbG/im7PinaTvuI/wn6eA'` en decodeert exact terug, inclusief emoji.

### Sla de unescape(encodeURIComponent())-truc over

Je vindt `btoa(unescape(encodeURIComponent(str)))` overal gekopieerd. Het werkt, maar `unescape()` is Annex B-legacy, verouderd sinds ECMAScript 3\. Nieuwe code zou `TextEncoder` moeten gebruiken: het is expliciet, standaard en hangt niet af van een verouderde global.

## De manier van 2025: Uint8Array.toBase64() en Uint8Array.fromBase64()

Sinds september 2025 heeft JavaScript native Base64-methoden op `Uint8Array` zelf, dus de byte-string-helpers hierboven zijn niet langer de enige optie. Het TC39-[Base64-voorstel](https://github.com/tc39/proposal-arraybuffer-base64/blob/main/README.md) heeft Stage 4 bereikt en maakt deel uit van de taal; het TC39-argument is dat base64 geen tekstencoding is, dus zijn TextEncoder/TextDecoder conceptueel het verkeerde gereedschap ervoor.

Volgens [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global%5FObjects/Uint8Array/toBase64) zijn dit Baseline "Newly available" in alle moderne browsers. Controleer je doelomgevingen voordat je code uitrolt: als je oudere browsers of oudere Node-versies ondersteunt, behoud dan de `TextEncoder` \+ `btoa` fallback. MDN geeft de voorkeur aan deze methoden boven `btoa()`/`atob()` wanneer je al een `Uint8Array` hebt, omdat er geen ruwe byte-string hoeft te worden aangemaakt.

### Encoderen: bytes.toBase64()

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

```

Eén UTF-8-conversie, één methode-aanroep, klaar.

### Decoderen: Uint8Array.fromBase64()

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

```

MDN merkt op dat `fromBase64()` de voorkeur verdient boven `atob()` omdat het een echte byte-array retourneert, tenzij de gedecodeerde data bedoeld is als ASCII-tekst. Het accepteert alleen het base64-alphabet (A–Z, a–z, 0–9, plus `+/` of `-_`), optionele `=` padding, en negeert ASCII-witruimte; het gooit een `SyntaxError` bij tekens buiten het alphabet.

### Opties: alphabet, omitPadding, lastChunkHandling

De [opties van de specificatie](https://tc39.es/proposal-arraybuffer-base64/) geeft je precieze controle:

| Optie             | Waarden                                              | Wat het doet                                                                               |
| ----------------- | ---------------------------------------------------- | ------------------------------------------------------------------------------------------ |
| alphabet          | 'base64' (standaard), 'base64url'                    | +// vs \-/\_                                                                               |
| omitPadding       | boolean                                              | Verwijdert trailing \=: new Uint8Array(\[72\]).toBase64() → 'SA==', met omitPadding → 'SA' |
| lastChunkHandling | 'loose' (standaard), 'strict', 'stop-before-partial' | Decoder-tolerantie voor het laatste chunk                                                  |

`'loose'` tolereert ontbrekende padding en witruimte. `'strict'` vereist exacte chunks van 4 tekens met overflow-bits die op nul gezet zijn, wat je wilt bij het valideren van onbetrouwbare input. `'stop-before-partial'` is voor streaming. De standaard losse modus tolereert ook overflow-bits die niet op nul gezet zijn; de strikte modus wijst die af.

## Base64 vs base64url: welk alphabet heb je nodig?

Standaard base64 gebruikt `+` en `/` plus `=`\-padding, en dat is prima voor `data:`\-URL's omdat die geen padsegmenten of querystrings bevatten. base64url (RFC 4648 §5) vervangt ze door `-` en `_` en laat meestal padding weg, en het is verplicht zodra de output deel uitmaakt van een URL: JWT's gebruiken bijvoorbeeld base64url voor alle drie hun segmenten.

### Wanneer base64url gebruiken

Gebruik base64url wanneer de gecodeerde string deel gaat uitmaken van een URL-pad of queryparameter, waar `+` en `/` escaping nodig hebben (en `=`\-padding de querystring onoverzichtelijk maakt). Gebruik standaard base64 voor data-URI's, het insluiten van bytes in JSON-payloads, of alles wat geen URL is.

### Hoe base64url produceren

Drie manieren, afhankelijk van de omgeving:

- Moderne browsers: `bytes.toBase64({ alphabet: 'base64url' })`
- Node 16+: `Buffer.from(str).toString('base64url')`
- Post-processing: neem standaard output en vervang `+` → `-`, `/` → `_`, en verwijder vervolgens de `=`

Een subtiel detail om te weten: RFC 4648 vereist dat encoders de non-data overflow-bits in het laatste chunk op nul zetten, en strikte decoders kunnen een fout gooien als dat niet gebeurd is. Het glossarium van MDN geeft het voorbeeld van `'VR=='`, wat dezelfde byte is als `'VQ=='` maar met vuile padding-bits. Genereer base64 met een degelijke encoder en dit komt nooit voor; schrijf je er zelf één, dan kan het wel gebeuren.

## Een bestand of afbeelding encoderen naar Base64 (data-URL's)

Voor bestanden is de browser-route `FileReader` met `readAsDataURL()`, wat een volledige data-URL produceert, waarna je het prefix verwijdert om de ruwe base64 te krijgen. Voor afbeeldingen die al in een canvas zijn gerenderd, doet `toDataURL('image/png')` dit in één aanroep.

### FileReader.readAsDataURL() → data-URL → ruwe base64

```js
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() voor afbeeldingen

Als je een afbeelding op een canvas hebt getekend (resize, crop, wat dan ook), sla dan FileReader volledig over:

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

```

Dezelfde prefix-verwijdering geldt als je de kale base64-string nodig hebt.

### Data-URL terug naar bytes

De omgekeerde richting is een fetch plus een array buffer. MDN's voorbeeld: een data-URL gebouwd uit `new Uint8Array([0,1,2])` is `'data:application/octet-stream;base64,AAEC'`, en je herstelt de bytes zo:

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

```

En de herinnering waarvoor dit hele artikel bestaat: roep `btoa()` niet aan op willekeurige tekst of HTML om data-URL's te bouwen. Het is een byte-naar-byte encoder, en input die niet Latin-1 is zal een fout gooien.

## Node.js: Buffer.from(str, 'base64') — en het ene gevaarlijke verschil

Node heeft eigen idiomen die de TextEncoder-dans volledig overslaan: `Buffer.from('Hello, world! 🌍').toString('base64')` behandelt Unicode automatisch, omdat Buffer vanaf het begin op bytes werkt. Decodeer met `Buffer.from(encoded, 'base64').toString('utf8')`.

Andere handige vormen: `Buffer.from(str).toString('base64url')` (Node 16+), en hele bestanden via `fs.readFileSync('image.png').toString('base64')`.

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

```

### Node valideert je base64 niet (stille fouten)

Hier is het deel dat mensen verrast: de base64-decoder van Node valideert input niet. `Buffer.from('!!!!', 'base64').toString()` retourneert `''` zonder fout, en `Buffer.from('aGVsbG8!!!', 'base64')` gooit de rommel er stilzwijgend uit en retourneert `'hello'`. Vergelijk dat met `atob('aGVsbG8!!!')`, die een `InvalidCharacterError` gooit. Als de base64 van gebruikers, een API of een derde partij komt, valideer het dan voordat je decodeert in Node, of gebruik `Uint8Array.fromBase64()` met `lastChunkHandling: 'strict'` waar de runtimes dit ondersteunen.

### Zijn btoa/atob verouderd? Browsers vs Node

Dit is echt tegenstrijdig, dus hier is de eerlijke oplossing. In browsers [markeert MDN btoa() en atob()](https://developer.mozilla.org/en-US/docs/Web/API/Window/btoa) Baseline breed beschikbaar sinds juli 2015, niet verouderd. In Node markeren de [type definitions](https://github.com/DefinitelyTyped/DefinitelyTyped/discussions/58795) ze als `@deprecated` met de opmerking dat ze "nooit gebruikt zouden moeten worden in nieuwe code", en bevelen `Buffer.from(str, 'base64')` aan.

Een goede regel: het is een legacy API, geen verwijderde. Prima in browsercode, prefereer `Buffer` in Node-specifieke code. Op basis van consensus in de community (geen specificatie-garantie) zijn `btoa`/`atob` ook de praktische keuze voor cross-environment als je één codepad nodig hebt dat ongewijzigd draait in browsers, Deno en Node zonder dependencies.

## Wat je eigenlijk moet gebruiken

Als je één aanbeveling uit dit artikel meeneemt, zijn het de drie one-liners hieronder. Behoud `btoa`/`atob` voor gewone ASCII en legacy codebases, en gebruik de `bytesToBase64` helper als je browser-fallback totdat `toBase64()` je hele publiek dekt.

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

```

Voordat je uitrolt, verifieer je output met een snelle round trip in een browser-tool. Plak `'Hello, world!'` in [de gratis Base64 Encoder / Decoder van Tool Cloud](https://www.tool-cloud.com/nl/dev/base64%5Fencoder/), bevestig dat je `'SGVsbG8sIHdvcmxkIQ=='` krijgt, decodeer het daarna terug en check of het identiek is. Er is ook een standalone [Text to Base64 converter](https://www.tool-cloud.com/nl/general/text%5Fto%5Fbase64/) en [Base64 to Text converter](https://www.tool-cloud.com/nl/general/base64%5Fto%5Ftext/); bestanden worden automatisch verwijderd en er hoeft niets geïnstalleerd te worden.

## Veelgestelde vragen

### Waarom faalt btoa() op emoji maar werkt het wel op Engelse tekst?

btoa() behandelt elk teken als één byte en vereist dat elk codepunt onder de 256 ligt (betrouwbaar alleen tot 0x7F). Emoji, tekens met diakrieten en CJK-tekst hebben meerdere bytes nodig, waardoor ze een DOMException: InvalidCharacterError triggeren. Converteer eerst naar UTF-8-bytes met TextEncoder, encodeer daarna de bytes.

### Is btoa() verouderd?

Niet in browsers — MDN markeert btoa() en atob() als breed beschikbaar sinds juli 2015\. In Node markeren de type-definities ze echter als verouderd ten gunste van Buffer.from(str, 'base64') in Node-specifieke code. Het gaat om een legacy-API, geen verwijderde API.

### Hoe encodeer ik Unicode-tekst (accenten, Chinees, emoji) naar Base64?

Gebruik new TextEncoder().encode(str) om UTF-8-bytes te krijgen, encodeer daarna de bytes (btoa() via een byte-string helper, of Uint8Array.toBase64() in moderne browsers). Om te decoderen gebruik je Uint8Array.fromBase64() of atob() om de bytes terug te krijgen, gevolgd door new TextDecoder().decode(bytes).

### Wat is het verschil tussen base64 en base64url?

base64url (RFC 4648 §5) gebruikt '-' en '\_' in plaats van '+' en '/', en laat vaak de '=' padding weg. Gebruik base64url wanneer de uitvoer in URLs of JWTs terechtkomt; standaard base64 is prima voor data: URLs, omdat deze geen padsegmenten of querystrings bevatten.

### Waarom gooit Node.js geen fout bij ongeldige base64?

Node's Buffer.from(str, 'base64') valideert de invoer niet: het negeert ongeldige karakters stilzwijgend, dus Buffer.from('aGVsbG8!!!', 'base64').toString() retourneert 'hello' zonder foutmelding. In browsers gooit atob() een InvalidCharacterError bij dezelfde invoer. Valideer onbetrouwbare base64 voordat je decodeert in Node.