B6
100% Client-SideAbout
Back to tool
Guide8 min read

Base64 in JavaScript

A complete guide to encoding and decoding Base64 in the browser, Node.js, and Deno — with copy-paste examples using btoa, atob, TextEncoder, and Node.js Buffer.

Need to encode something right now?

Use the free online tool — no code needed, instant results.

Open Tool

The short version

JavaScript has two built-in global functions for Base64: btoa(str) to encode and atob(b64) to decode. Both are available in every modern browser and in Node.js 16+.

The catch: btoa only handles Latin-1 (characters ≤ U+00FF). For Unicode strings — emoji, CJK characters, non-Latin scripts — you need to encode to UTF-8 bytes first using TextEncoder, or use Buffer.from(str, 'utf8') in Node.js.

EnvironmentEncodeDecode
Browser (ASCII)btoa(str)atob(b64)
Browser (Unicode)encodeBase64(str)*decodeBase64(b64)*
Node.jsBuffer.from(str,'utf8').toString('base64')Buffer.from(b64,'base64').toString('utf8')
Denobtoa / encodeBase64()*atob / decodeBase64()*

* Custom UTF-8 helper — see examples below.

Browser — btoa, atob, and TextEncoder

btoa() — encode Latin-1 text (built-in)

btoa() is the native browser function for Base64 encoding. It only works with Latin-1 strings (characters 0–255). It will throw a RangeError on emoji or non-Latin characters.

// Encode a simple ASCII string
const encoded = btoa("Hello, World!");
console.log(encoded); // → "SGVsbG8sIFdvcmxkIQ=="

// Decode it back
const decoded = atob("SGVsbG8sIFdvcmxkIQ==");
console.log(decoded); // → "Hello, World!"

// ⚠ This throws RangeError for emoji or Unicode:
// btoa("Hello 😀"); // ❌ RangeError: Invalid character

UTF-8 safe encode with TextEncoder (browser + Deno)

For full Unicode support (emoji, Arabic, Chinese, etc.), use TextEncoder to convert the string to bytes first, then encode to Base64.

function encodeBase64(str) {
  const bytes = new TextEncoder().encode(str);
  const binStr = Array.from(bytes, (b) => String.fromCharCode(b)).join("");
  return btoa(binStr);
}

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

encodeBase64("Hello 😀"); // → "SGVsbG8g8J+YgA=="
decodeBase64("SGVsbG8g8J+YgA=="); // → "Hello 😀"

Modern: btoa/atob via fromCharCode + Uint8Array (all browsers)

A slightly more concise version of the UTF-8 approach using spread syntax:

const encodeBase64 = (str) =>
  btoa(String.fromCharCode(...new TextEncoder().encode(str)));

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

console.log(encodeBase64("日本語")); // → "5pel5pys6Kqe"
console.log(decodeBase64("5pel5pys6Kqe")); // → "日本語"

Node.js — Buffer and built-in btoa/atob

Node.js Buffer (v10+) — encode string

Node.js Buffer natively handles Base64 encoding and decoding. No imports needed.

// Encode a UTF-8 string to Base64
const text = "Hello, World!";
const encoded = Buffer.from(text, "utf8").toString("base64");
console.log(encoded); // → "SGVsbG8sIFdvcmxkIQ=="

// Decode Base64 back to a UTF-8 string
const decoded = Buffer.from(encoded, "base64").toString("utf8");
console.log(decoded); // → "Hello, World!"

Node.js Buffer — encode binary / file content

Buffer is especially useful for encoding binary files (images, PDFs) to Base64 for API payloads or storage.

const fs = require("fs");

// Encode a file to Base64
const fileBuffer = fs.readFileSync("./image.png");
const base64String = fileBuffer.toString("base64");

// Decode Base64 back and write to file
const decodedBuffer = Buffer.from(base64String, "base64");
fs.writeFileSync("./output.png", decodedBuffer);

Node.js 16+ — built-in atob / btoa

Node.js 16 added global atob() and btoa() matching the browser API. Buffer is still preferred for binary data.

// Available globally in Node.js 16+
const encoded = btoa("Hello, World!");
console.log(encoded); // → "SGVsbG8sIFdvcmxkIQ=="

const decoded = atob("SGVsbG8sIFdvcmxkIQ==");
console.log(decoded); // → "Hello, World!"

// Note: btoa/atob only handle Latin-1, just like in browsers.
// Use Buffer for binary data or full Unicode strings.

URL-safe Base64 in Node.js

Replace + and / with - and _ (and strip = padding) to create Base64 strings safe for use in URLs, filenames, and JWTs.

function toBase64Url(str) {
  return Buffer.from(str, "utf8")
    .toString("base64")
    .replace(/+/g, "-")
    .replace(///g, "_")
    .replace(/=+$/, "");
}

function fromBase64Url(b64url) {
  const base64 = b64url.replace(/-/g, "+").replace(/_/g, "/");
  const padded = base64.padEnd(base64.length + (4 - (base64.length % 4)) % 4, "=");
  return Buffer.from(padded, "base64").toString("utf8");
}

toBase64Url("Hello+World/Test"); // → "SGVsbG8rV29ybGQvVGVzdA"

Common use cases

HTTP Basic Authentication header

The HTTP Basic Auth scheme sends credentials as Base64 in the Authorization header.

const username = "admin";
const password = "secret123";
const token = btoa(`${username}:${password}`);

fetch("/api/protected", {
  headers: {
    Authorization: `Basic ${token}`,
  },
});

Inline images as data URIs

Embed small images directly in HTML or CSS, avoiding extra HTTP requests.

// In a React / JSX component
const logoBase64 = "iVBORw0KGgoAAAANSUhEUgAA...";
const src = `data:image/png;base64,${logoBase64}`;

return <img src={src} alt="Logo" />;

// Or in CSS
// background-image: url("data:image/svg+xml;base64,...");

JSON API with binary payload

JSON doesn't support raw binary. Encode binary data to Base64 before including it in a JSON body.

const fs = require("fs");

const pdfBytes = fs.readFileSync("./report.pdf");
const payload = {
  filename: "report.pdf",
  mimeType: "application/pdf",
  content: pdfBytes.toString("base64"),
};

await fetch("https://api.example.com/upload", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify(payload),
});

JWT payload inspection

JWT tokens are Base64url-encoded. Decode the middle segment to read the payload without verification.

function decodeJwtPayload(token) {
  const [, payload] = token.split(".");
  // Add padding if needed
  const padded = payload.padEnd(payload.length + (4 - (payload.length % 4)) % 4, "=");
  const json = atob(padded.replace(/-/g, "+").replace(/_/g, "/"));
  return JSON.parse(json);
}

const token = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1c2VyMSIsImV4cCI6MTcwMDAwMH0.xxx";
console.log(decodeJwtPayload(token));
// → { sub: "user1", exp: 1700000 }

Common pitfalls

btoa() throws on non-Latin-1 characters

Problem: btoa() only accepts strings where every character has a code point ≤ 255. Emoji, Chinese, Arabic, etc. will throw a RangeError.

Fix: Use the TextEncoder-based helper shown above, or Buffer.from(str, 'utf8') in Node.js.

atob() returns a binary string, not text

Problem: atob() returns a sequence of characters where each character represents a byte. Treating it directly as UTF-8 text will corrupt multi-byte characters.

Fix: Pass the result through TextDecoder to get proper Unicode text back: new TextDecoder().decode(Uint8Array.from(atob(b64), c => c.charCodeAt(0))).

Base64 output size is ~33% larger

Problem: Every 3 bytes of input become 4 Base64 characters. A 1 MB file encodes to ~1.33 MB. Keep this in mind for API payloads and storage.

Fix: Consider compressing data before encoding (e.g., with CompressionStream) for large payloads.

URL-safe vs standard Base64

Problem: Standard Base64 uses + and / which are special characters in URLs. Passing a standard Base64 string in a URL query parameter will break it.

Fix: Use URL-safe Base64 (- and _ instead of + and /) for URLs, JWTs, and filenames. Or percent-encode: encodeURIComponent(base64String).

Test it without writing any code

The Base64 Tool on this site runs the same TextEncoder-based approach directly in your browser. Use it to quickly verify encoded/decoded output before wiring it into your JavaScript code — no data ever leaves your device.

Try it in your browser

Encode and decode Base64 instantly — 100% client-side, nothing uploaded.

Open Base64 Tool