Base64 Encode in JavaScript: btoa, Buffer and Uint8Array (2026)
JavaScript gives you multiple ways to encode and decode Base64 - but each has traps. btoa silently breaks on Unicode. Buffer only exists in Node.js. This guide covers every method with real examples so you always pick the right one.
Why Base64 Encoding Matters in JavaScript
Base64 encoding converts binary data into a safe ASCII string representation. In JavaScript, you encounter it constantly:
- Data URIs: Embedding images inline as
data:image/png;base64,... - HTTP Basic Auth: The
Authorization: Basicheader isbtoa(username + ':' + password) - JWTs: All three parts of a JSON Web Token are Base64url-encoded
- Binary file transfers: Sending files through JSON APIs that cannot handle raw binary
- Cookie values: Encoding structured data to avoid special-character issues
The problem is that JavaScript has three completely different Base64 APIs depending on your runtime environment, and they behave differently for Unicode input. Getting this wrong produces garbled output or silent data corruption.
The Problem: btoa Fails on Unicode
Here is the pitfall every developer hits at least once. You write seemingly correct code:
btoa('Hello, World!');
// "SGVsbG8sIFdvcmxkIQ==" - works fine
btoa('Hello 🌍');
// Uncaught DOMException: Failed to execute 'btoa' on 'Window':
// The string to be encoded contains characters outside of the Latin1 range.
Why does this happen? The btoa function was designed to encode binary strings, not Unicode text. It only handles characters in the Latin-1 range (code points 0–255). The globe emoji has a code point of U+1F30D, which is well outside that range, so btoa throws.
This is one of the most common sources of silent bugs in production JavaScript: code that works for English text but crashes for users with non-Latin characters in their names, messages, or passwords.
Browser: btoa() and atob()
btoa (binary-to-ASCII) and atob (ASCII-to-binary) are globally available in every modern browser and in Node.js 16+. For pure ASCII or Latin-1 strings they are simple and fast:
// Encode ASCII string
const encoded = btoa('Hello, World!');
console.log(encoded); // "SGVsbG8sIFdvcmxkIQ=="
// Decode
const decoded = atob('SGVsbG8sIFdvcmxkIQ==');
console.log(decoded); // "Hello, World!"
// HTTP Basic Auth header (ASCII usernames only)
const credentials = btoa('admin:secret123');
fetch('/api/data', {
headers: { 'Authorization': 'Basic ' + credentials }
});
Unicode-Safe btoa: The TextEncoder Approach
To handle any Unicode string correctly in the browser, use TextEncoder to convert to UTF-8 bytes first, then encode those bytes:
// Unicode-safe Base64 encode (browser)
function toBase64(str) {
const bytes = new TextEncoder().encode(str);
const binString = Array.from(bytes, b => String.fromCodePoint(b)).join('');
return btoa(binString);
}
// Unicode-safe Base64 decode (browser)
function fromBase64(base64) {
const binString = atob(base64);
const bytes = Uint8Array.from(binString, c => c.codePointAt(0));
return new TextDecoder().decode(bytes);
}
// Test with emoji
const encoded = toBase64('Hello 🌍');
console.log(encoded); // "SGVsbG8g8J+QjQ=="
console.log(fromBase64(encoded)); // "Hello 🌍"
This approach works in all modern browsers and avoids the deprecated unescape/encodeURIComponent hack that was commonly used before TextEncoder was universally available.
The older pattern
btoa(unescape(encodeURIComponent(str)))still works but relies on the deprecatedunescape()function. Use the TextEncoder approach for new code.
Node.js: Buffer-Based Encoding
In Node.js, the Buffer class is the idiomatic way to handle Base64. It correctly handles all Unicode input by default because it works at the byte level:
// Encode string to Base64 (Node.js)
const encoded = Buffer.from('Hello 🌍').toString('base64');
console.log(encoded); // "SGVsbG8g8J+QjQ=="
// Decode Base64 to string (Node.js)
const decoded = Buffer.from('SGVsbG8g8J+QjQ==', 'base64').toString('utf-8');
console.log(decoded); // "Hello 🌍"
// URL safe Base64 (Node.js 14.18+)
const urlSafe = Buffer.from('Hello 🌍').toString('base64url');
console.log(urlSafe); // "SGVsbG8g8J-QjQ" (no padding, - instead of +)
Buffer.from(str) uses UTF-8 encoding by default, which means it correctly handles the full Unicode character set including emoji, CJK characters, Arabic, and every other script.
Step-by-Step: Encoding Binary Files to Base64
A common real-world use case is reading a file and encoding it as Base64 for transmission through a JSON API. Here is how to do it in Node.js:
const fs = require('fs');
// Step 1: Read the file as a Buffer
const imageBuffer = fs.readFileSync('./photo.jpg');
// Step 2: Convert to Base64
const base64Image = imageBuffer.toString('base64');
// Step 3: Create a data URI for use in HTML or JSON
const dataUri = `data:image/jpeg;base64,${base64Image}`;
// Step 4: Decode back when needed
const decodedBuffer = Buffer.from(base64Image, 'base64');
fs.writeFileSync('./photo-restored.jpg', decodedBuffer);
Step-by-Step: Base64 in the Browser with File API
To encode a user-selected file in the browser:
// HTML: <input type="file" id="fileInput">
document.getElementById('fileInput').addEventListener('change', function(e) {
const file = e.target.files[0];
const reader = new FileReader();
// Step 1: Use readAsDataURL for a full data URI
reader.readAsDataURL(file);
reader.onload = function() {
// reader.result is: "data:image/jpeg;base64,/9j/4AAQ..."
const dataUri = reader.result;
// Step 2: Strip the prefix if you only want the raw Base64
const base64Only = dataUri.split(',')[1];
console.log('Base64:', base64Only.substring(0, 50) + '...');
};
});
Encode and Decode Base64 Instantly
Paste any text or upload a file to get the Base64 output in one click. 100% client side - your data never leaves the browser.
Open Base64 Tool →URL-Safe Base64 in JavaScript
Standard Base64 uses +, /, and = padding characters that have special meanings in URLs. For tokens, cookies, and URL parameters, use Base64url which replaces them:
// Convert standard Base64 to URL safe Base64
function toBase64Url(base64) {
return base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
}
// Convert URL safe Base64 back to standard Base64
function fromBase64Url(base64url) {
const padded = base64url + '==='.slice((base64url.length + 3) % 4);
return padded.replace(/-/g, '+').replace(/_/g, '/');
}
// In Node.js 14.18+, use the built-in base64url encoding:
const urlSafe = Buffer.from('my data').toString('base64url');
const restored = Buffer.from(urlSafe, 'base64url').toString();
JWTs use Base64url (without padding) for all three parts: header, payload, and signature. If you are decoding a JWT manually, always use the URL safe decoder.
Performance Considerations
For small strings (under a few kilobytes), all methods are equivalent in performance. For large binary data:
- Node.js Buffer: Most efficient - implemented in C++, minimal overhead
- TextEncoder + btoa: Pure JS for the final step, slightly slower for very large inputs
- FileReader.readAsDataURL: Asynchronous and browser-optimized, best for user-selected files
- Streaming: For files over 10MB, process in chunks rather than loading the entire file into memory
Common Pitfalls and Fixes
- btoa throws on emoji or non-Latin characters: Use TextEncoder or Node.js Buffer instead
- atob produces garbled text for UTF-8 strings: The output of
atobis a binary string. Decode it with TextDecoder to get back a proper UTF-8 string - Missing padding: Some Base64 strings omit the trailing
=padding. Add it back before decoding:str.padEnd(Math.ceil(str.length/4)*4, '=') - Line breaks in Base64: MIME Base64 inserts a
\r\nevery 76 characters. Strip whitespace before decoding:atob(str.replace(/\s/g, '')) - Confusing btoa with Buffer in universal (isomorphic) code: Check
typeof Buffer !== 'undefined'before using it, asBufferdoes not exist in browser environments without a polyfill
Quick Reference: Which Method to Use
- Browser, ASCII-only input:
btoa()/atob() - Browser, any Unicode input:
TextEncoder+btoa() - Browser, file upload:
FileReader.readAsDataURL() - Node.js, any input:
Buffer.from(str).toString('base64') - Node.js or browser, URL safe:
Buffer.from(str).toString('base64url')or thereplacehelper above - JWTs: Use a dedicated library like
jsonwebtoken- do not decode manually in production
FAQ
Is Base64 encoding the same as encryption?
No. Base64 is an encoding scheme, not encryption. Anyone can decode a Base64 string without a key. It is used to represent binary data as text, not to protect it. If you need to protect data, use proper encryption via the Web Crypto API or a library like crypto.
Why does btoa exist if it only works for Latin-1?
btoa was designed to encode raw binary strings (byte strings), not Unicode text. Its name literally stands for "binary to ASCII." It predates the standardization of Unicode handling in the web platform. The modern replacement is TextEncoder + btoa, or simply Buffer in Node.js.
Does btoa work in Node.js?
Yes, as of Node.js 16.0. Before that, you needed to use Buffer.from(str).toString('base64') or import btoa from a polyfill package. For compatibility with older Node.js versions, always prefer the Buffer approach.
How do I encode an image to Base64 for use in CSS?
In Node.js: const b64 = fs.readFileSync('icon.svg').toString('base64'), then use it as background-image: url('data:image/svg+xml;base64,' + b64) in your CSS. Keep data URIs under 32KB to avoid performance issues in older browsers.
What is the size overhead of Base64?
Base64 encoding increases data size by approximately 33%. Every 3 bytes of input become 4 characters of output. For a 1MB image, the Base64 string will be approximately 1.37MB. This is worth considering for large file transfers where bandwidth matters.
Can I decode a JWT in JavaScript without a library?
Yes, for inspection purposes: JSON.parse(atob(token.split('.')[1].replace(/-/g,'+').replace(/_/g,'/'))). However, never skip signature verification in production. Use our JWT Decoder tool for quick inspection, and a library like jsonwebtoken for verified decoding in your backend.
Use our free tool here → Base64 Encoder / Decoder on SecureBin.ai
Usman has 10+ years of experience securing enterprise infrastructure, managing high-traffic servers, and building zero-knowledge security tools. Read more about the author.