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.
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.
| Environment | Encode | Decode |
|---|---|---|
| Browser (ASCII) | btoa(str) | atob(b64) |
| Browser (Unicode) | encodeBase64(str)* | decodeBase64(b64)* |
| Node.js | Buffer.from(str,'utf8').toString('base64') | Buffer.from(b64,'base64').toString('utf8') |
| Deno | btoa / 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 characterUTF-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.
Related guides
Try it in your browser
Encode and decode Base64 instantly — 100% client-side, nothing uploaded.