Base64 to Text JS: Decoding in JavaScript Correctly

Decoding Base64 in JavaScript is one function call — and that function predates Unicode support in the language, which is why so much JavaScript Base64 code is subtly broken.

atob and its blind spot

atob(b64) returns a binary string where each character is one byte. For pure ASCII content that is already the text you want. For anything else it is not:

atob('aGVsbG8=');    // 'hello'      — fine
atob('Y2Fmw6k=');    // 'café'      — wrong, bytes shown as Latin-1

The mangled output is the signature of this bug: correct bytes, wrong interpretation.

The correct decode

Convert the binary string to a byte array, then decode those bytes as UTF-8:

const fromBase64 = (b64) =>
  new TextDecoder().decode(
    Uint8Array.from(atob(b64), (c) => c.charCodeAt(0))
  );

fromBase64('Y2Fmw6k=');   // 'café'

atob throws InvalidCharacterError on malformed input, so wrap it in try/catch whenever the string comes from a user, a URL or an API.

base64url

JWT segments and URL-safe payloads use a different alphabet and usually no padding. Translate before decoding:

const fromBase64Url = (s) => {
  const b64 = s.replace(/-/g, '+').replace(/_/g, '/');
  return fromBase64(b64.padEnd(b64.length + ((4 - (b64.length % 4)) % 4), '='));
};

Node.js

In Node.js, Buffer replaces the whole atob routine: it handles both directions and both alphabets, and you name the text encoding explicitly:

Buffer.from('Y2Fmw6k=', 'base64').toString('utf8');   // 'café'
Buffer.from('café', 'utf8').toString('base64');       // 'Y2Fmw6k='
Buffer.from(token, 'base64url').toString('utf8');     // Node 16+

Frequently asked questions

Why does my decoded text show "é"?

atob returned bytes that were displayed as Latin-1. Decode them as UTF-8 with TextDecoder.

Is atob deprecated?

Not in browsers, where it remains part of the HTML standard. Node.js does mark its global `atob` as legacy and recommends `Buffer.from(data, 'base64')` instead.

What is the Node equivalent?

`Buffer.from(str, 'base64').toString('utf8')`. The `base64url` encoding was added in Node.js 15.7.0 and 14.18.0.