Base64 in JavaScript

Published

Browsers have had btoa() and atob() since the early days, and modern engines add Uint8Array.prototype.toBase64() and Uint8Array.fromBase64(). The old functions work on “binary strings” where each character is one byte, which is the source of the most common JavaScript Base64 bug.

Encode a string to Base64

btoa('Hello, World!'); // "SGVsbG8sIFdvcmxkIQ=="

This only works for characters with code points up to 255. Anything else throws InvalidCharacterError:

btoa('€'); // ❌ DOMException: The string contains characters outside of the Latin1 range

Unicode-safe encoding

Convert the string to UTF-8 bytes first:

function encodeUtf8(str) {
  const bytes = new TextEncoder().encode(str);
  let bin = '';
  for (const b of bytes) bin += String.fromCharCode(b);
  return btoa(bin);
}

encodeUtf8('Grüße, 世界 👋'); // "R3LDvMOfZSwg5LiW55WMIPCfkYs="

In engines that support it (Chrome 140+, Firefox 133+, Safari 18.2+), it is simply:

new TextEncoder().encode('Grüße, 世界 👋').toBase64();

Decode Base64 to a string

function decodeUtf8(b64) {
  const bin = atob(b64);
  const bytes = Uint8Array.from(bin, (c) => c.charCodeAt(0));
  return new TextDecoder().decode(bytes);
}

atob ignores whitespace but throws on any other invalid character. Use new TextDecoder('utf-8', { fatal: true }) if you want an exception for invalid UTF-8 instead of replacement characters (�).

Base64URL

const toUrl = (b64) => b64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
const fromUrl = (s) => s.replace(/-/g, '+').replace(/_/g, '/').padEnd(Math.ceil(s.length / 4) * 4, '=');

// Native, where available:
bytes.toBase64({ alphabet: 'base64url', omitPadding: true });
Uint8Array.fromBase64(s, { alphabet: 'base64url' });

Encode a file or image (FileReader)

input.addEventListener('change', () => {
  const reader = new FileReader();
  reader.onload = () => console.log(reader.result); // "data:image/png;base64,iVBOR..."
  reader.readAsDataURL(input.files[0]);
});

To get only the Base64 part, split on the first comma: reader.result.split(',')[1].

Decode Base64 to a downloadable file

function downloadBase64(b64, name, type = 'application/octet-stream') {
  const bytes = Uint8Array.from(atob(b64), (c) => c.charCodeAt(0));
  const url = URL.createObjectURL(new Blob([bytes], { type }));
  const a = Object.assign(document.createElement('a'), { href: url, download: name });
  a.click();
  setTimeout(() => URL.revokeObjectURL(url), 10_000);
}

Prefer Blob URLs over long data: URLs for anything larger than a few hundred kilobytes: they are faster and browsers block navigation to most data: URLs.

Fetch an image and convert it to Base64

const res = await fetch('/logo.png');
const bytes = new Uint8Array(await res.arrayBuffer());
const b64 = bytes.toBase64 ? bytes.toBase64() : btoa(String.fromCharCode(...bytes)); // spread is fine for small files only

For large arrays, build the binary string in chunks (as in encodeUtf8 above) to avoid “Maximum call stack size exceeded”.

Common errors

Error Cause Fix
InvalidCharacterError in btoa Non-Latin1 characters Encode to UTF-8 first
InvalidCharacterError in atob -, _, %3D, or a data URI prefix Convert Base64URL, URL-decode, strip data:...,
Garbled text like é Decoded bytes were treated as Latin1 Decode with TextDecoder

For Node.js-specific APIs (Buffer, streams), see Base64 in Node.js. To check your results, use the online Base64 decoder.

Comments (0)

Questions, ideas or a bug in “Base64 in JavaScript”? Leave a comment, rate the tool, or reply to others.

No comments yet. Be the first to share your feedback!

Leave a comment

Rate this tool:
Esc
↑ ↓ move↵ openEsc close