Home / Blog / How to Decode Base64 in JavaScript

How to Decode Base64 in JavaScript: Complete Guide 2026

Last updated: May 16, 2026 · 10 min read

Base64 encoding is everywhere — from embedding images in HTML to transmitting binary data in APIs. If you work with JavaScript, knowing how to encode and decode Base64 is essential. This guide covers everything from the basic atob/btoa functions to handling Unicode text and binary data in both browsers and Node.js.

Quick Tip: Need to decode Base64 right now? Use our free Online Base64 Encoder/Decoder — paste your text and decode instantly, no signup needed.

The Basics: atob and btoa

JavaScript provides two built-in functions for Base64 in browsers:

// Encoding
const encoded = btoa("Hello World");
console.log(encoded); // "SGVsbG8gV29ybGQ="

// Decoding
const decoded = atob("SGVsbG8gV29ybGQ=");
console.log(decoded); // "Hello World"

That's simple enough. But there's a catch — btoa() and atob() only work correctly with Latin1 (ASCII) characters. Try encoding Chinese or emoji, and you'll get an error.

The Unicode Problem

Here's what happens when you try to use btoa with non-ASCII text:

// This will throw an error!
try {
    btoa("你好世界");
} catch (e) {
    console.error(e.message);
    // "Failed to execute 'btoa': The string to be encoded contains characters outside of the Latin1 range."
}

The btoa/atob functions only handle characters in the 0x00–0xFF range. For full Unicode support, you need a different approach.

Base64 with Unicode: TextEncoder and TextDecoder

The modern approach uses TextEncoder and TextDecoder to convert between strings and byte arrays, combined with a Base64 conversion helper:

// Encode Unicode string to Base64
function encodeBase64(str) {
    const encoder = new TextEncoder();
    const bytes = encoder.encode(str);
    let binary = '';
    bytes.forEach(byte => binary += String.fromCharCode(byte));
    return btoa(binary);
}

// Decode Base64 to Unicode string
function decodeBase64(base64) {
    const binary = atob(base64);
    const bytes = new Uint8Array(binary.length);
    for (let i = 0; i < binary.length; i++) {
        bytes[i] = binary.charCodeAt(i);
    }
    const decoder = new TextDecoder();
    return decoder.decode(bytes);
}

// Now it works with Unicode!
const encoded = encodeBase64("你好世界 🌍");
console.log(encoded); // "5L2g5aW95LiW55WJIPCfjI0="

const decoded = decodeBase64("5L2g5aW95LiW55WJIPCfjI0=");
console.log(decoded); // "你好世界 🌍"

A Cleaner Approach with Uint8Array

You can also use a more concise method leveraging Uint8Array and the spread operator:

// Compact encode
function toBase64(str) {
    return btoa(String.fromCharCode(...new TextEncoder().encode(str)));
}

// Compact decode
function fromBase64(b64) {
    return new TextDecoder().decode(
        Uint8Array.from(atob(b64), c => c.charCodeAt(0))
    );
}

console.log(toBase64("Hello 你好")); // "SGVsbG8g5L2g5aW9"
console.log(fromBase64("SGVsbG8g5L2g5aW9")); // "Hello 你好"

Watch out: The spread operator approach (...new TextEncoder().encode(str)) can hit the maximum call stack size for very large strings (100K+ characters). Use the loop-based approach for large data.

Handling Binary Data

Encoding a File to Base64

When working with files in the browser, you often need to convert a File or Blob to Base64:

// Using FileReader (callback-based)
function fileToBase64(file) {
    return new Promise((resolve, reject) => {
        const reader = new FileReader();
        reader.onload = () => {
            // result is a data URL like "data:image/png;base64,iVBOR..."
            const base64 = reader.result.split(',')[1];
            resolve(base64);
        };
        reader.onerror = reject;
        reader.readAsDataURL(file);
    });
}

// Usage
const fileInput = document.querySelector('input[type="file"]');
fileInput.addEventListener('change', async (e) => {
    const file = e.target.files[0];
    const base64 = await fileToBase64(file);
    console.log(base64);
});

Modern Approach with arrayBuffer

// Using arrayBuffer (modern, promise-based)
async function fileToBase64Modern(file) {
    const buffer = await file.arrayBuffer();
    const bytes = new Uint8Array(buffer);
    let binary = '';
    bytes.forEach(byte => binary += String.fromCharCode(byte));
    return btoa(binary);
}

Converting Base64 Back to a Blob

function base64ToBlob(base64, mimeType = 'application/octet-stream') {
    const binary = atob(base64);
    const bytes = new Uint8Array(binary.length);
    for (let i = 0; i < binary.length; i++) {
        bytes[i] = binary.charCodeAt(i);
    }
    return new Blob([bytes], { type: mimeType });
}

// Create a downloadable link
const blob = base64ToBlob("SGVsbG8gV29ybGQ=", "text/plain");
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'hello.txt';
a.click();
URL.revokeObjectURL(url);

Base64 in Node.js

Node.js doesn't have atob/btoa globally (they were added in Node 16+). The idiomatic way is to use Buffer:

Encoding with Buffer

// Encode string to Base64
const str = "Hello World 你好";
const encoded = Buffer.from(str, 'utf-8').toString('base64');
console.log(encoded); // "SGVsbG8gV29ybGQg5L2g5aW9"

// Decode Base64 to string
const decoded = Buffer.from(encoded, 'base64').toString('utf-8');
console.log(decoded); // "Hello World 你好"

Working with Files in Node.js

const fs = require('fs');

// Read file and encode to Base64
const fileBuffer = fs.readFileSync('image.png');
const base64 = fileBuffer.toString('base64');
console.log(base64);

// Decode Base64 and write to file
const decoded = Buffer.from(base64, 'base64');
fs.writeFileSync('output.png', decoded);

Node.js 16+ with atob/btoa

// Node 16+ has global atob/btoa
const encoded = btoa("Hello World");
console.log(encoded); // "SGVsbG8gV29ybGQ="

const decoded = atob("SGVsbG8gV29ybGQ=");
console.log(decoded); // "Hello World"

Note: Node.js atob/btoa have the same Latin1 limitation as browser versions. For Unicode, stick with Buffer.

Browser vs Node.js: Key Differences

// Universal (works in browser and Node.js 18+)
function universalEncode(str) {
    const bytes = new TextEncoder().encode(str);
    let binary = '';
    bytes.forEach(byte => binary += String.fromCharCode(byte));
    return btoa(binary);
}

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

URL-Safe Base64

Standard Base64 uses + and / which have special meanings in URLs. URL-safe Base64 replaces these:

// Convert standard Base64 to URL-safe
function base64ToUrlSafe(base64) {
    return base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
}

// Convert URL-safe Base64 back to standard
function urlSafeToBase64(urlSafe) {
    let base64 = urlSafe.replace(/-/g, '+').replace(/_/g, '/');
    // Add padding if needed
    while (base64.length % 4) base64 += '=';
    return base64;
}

const encoded = btoa("Hello+World/Test");
const urlSafe = base64ToUrlSafe(encoded);
console.log(urlSafe); // "SGVsbG8rV29ybGQvVGVzdA"

const restored = urlSafeToBase64(urlSafe);
console.log(atob(restored)); // "Hello+World/Test"

Common Use Cases

Data URLs for Images

// Create a data URL from Base64
const base64Image = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==";
const dataUrl = `data:image/png;base64,${base64Image}`;

// Use in an img tag
const img = document.createElement('img');
img.src = dataUrl;
document.body.appendChild(img);

HTTP Basic Authentication

// Create Basic Auth header
const username = "admin";
const password = "secret";
const credentials = btoa(`${username}:${password}`);

fetch('/api/data', {
    headers: {
        'Authorization': `Basic ${credentials}`
    }
});

Storing Data in localStorage

// Store complex data as Base64
const data = { theme: "dark", lang: "zh-CN" };
const json = JSON.stringify(data);
const encoded = btoa(unescape(encodeURIComponent(json)));
localStorage.setItem('settings', encoded);

// Retrieve
const stored = localStorage.getItem('settings');
const decoded = decodeURIComponent(escape(atob(stored)));
const settings = JSON.parse(decoded);

Error Handling

// Safe Base64 decoding
function safeDecodeBase64(base64) {
    try {
        // Validate Base64 format
        if (!/^[A-Za-z0-9+/]*={0,2}$/.test(base64)) {
            throw new Error('Invalid Base64 format');
        }
        return atob(base64);
    } catch (e) {
        console.error('Base64 decode failed:', e.message);
        return null;
    }
}

// Test
console.log(safeDecodeBase64("SGVsbG8=")); // "Hello"
console.log(safeDecodeBase64("not-valid!")); // null

Frequently Asked Questions

What is the difference between atob and btoa?

btoa() encodes a string to Base64 (binary to ASCII). atob() decodes a Base64 string back to the original (ASCII to binary). They are inverse operations.

Why does btoa throw an error with Chinese characters?

btoa() only supports Latin1 characters (0x00–0xFF). For Unicode text, you need to convert to bytes first using TextEncoder, then encode. See the Unicode section above.

How do I encode a file to Base64 in JavaScript?

In the browser, use FileReader.readAsDataURL() or file.arrayBuffer() combined with btoa(). In Node.js, use fs.readFileSync('file').toString('base64').

Is Base64 the same in browser and Node.js?

The encoding algorithm is the same, but the APIs differ. Browsers use atob/btoa, Node.js uses Buffer. For isomorphic code, use TextEncoder/TextDecoder.

What is URL-safe Base64?

Standard Base64 uses + and / which are reserved in URLs. URL-safe Base64 replaces + with - and / with _, and removes padding =.

How do I decode Base64 in Node.js?

Use Buffer.from(base64String, 'base64').toString('utf-8') for strings, or Buffer.from(base64String, 'base64') for raw binary data.

Try It Online

Want to encode or decode Base64 without writing code? Use our free online Base64 Encoder/Decoder — supports text, files, and Unicode. Works in your browser, no server upload needed.