base-64, btoa, and js-base64 are utilities for encoding and decoding Base64 strings in JavaScript, but they serve different roles depending on the runtime environment and character set requirements. base-64 is a strict, spec-compliant library often used as a polyfill or standalone module. btoa is a legacy polyfill package designed to bring browser-like Base64 functions to older Node.js environments. js-base64 is a comprehensive library that handles UTF-8 encoding natively and works across both Node.js and browsers without polyfilling globals. Choosing the right one depends on whether you need UTF-8 support, global pollution, or strict spec adherence.
Handling Base64 encoding in JavaScript seems simple until you run into character encoding issues or environment mismatches. The packages base-64, btoa, and js-base64 all solve the same core problem but take very different approaches to compatibility, UTF-8 support, and global scope. Let's break down how they behave in real engineering scenarios.
The first decision is where your code runs. Browsers have had btoa and atob globally for a long time. Node.js only added global support recently (v16+). Before that, developers needed packages.
base-64 is a standalone module.
// base-64: Explicit import
import base64 from 'base-64';
const encoded = base64.encode('hello');
const decoded = base64.decode(encoded);
btoa is a polyfill package.
btoa and atob to the global object.// btoa: Global polyfill
import 'btoa'; // Side-effect import
// Now available globally
const encoded = btoa('hello');
const decoded = atob(encoded);
js-base64 is a cross-platform library.
Base64).// js-base64: Namespace import
import { Base64 } from 'js-base64';
const encoded = Base64.encode('hello');
const decoded = Base64.decode(encoded);
This is the most critical technical difference. Standard Base64 operates on bytes, but JavaScript strings are UTF-16. If you pass emoji or non-ASCII characters to a standard encoder, it will corrupt the data.
base-64 expects binary strings or byte arrays.
TextEncoder or similar before encoding.// base-64: Manual UTF-8 handling required
import base64 from 'base-64';
const text = '👋 Hello';
const bytes = new TextEncoder().encode(text);
// Convert bytes to binary string for this library
const binary = String.fromCharCode(...bytes);
const encoded = base64.encode(binary);
btoa fails on non-ASCII characters by default.
InvalidCharacterError for high Unicode.TextEncoder workaround as base-64.// btoa: Will throw on emoji without workaround
try {
btoa('👋'); // Throws InvalidCharacterError
} catch (e) {
// Must encode to UTF-8 bytes first
const bytes = new TextEncoder().encode('👋');
// Convert to binary string for btoa
const binary = String.fromCharCode(...bytes);
const encoded = btoa(binary);
}
js-base64 handles UTF-8 natively.
TextEncoder steps for standard text.// js-base64: Native UTF-8 support
import { Base64 } from 'js-base64';
const text = '👋 Hello';
// encodeURI handles the UTF-8 conversion internally
const encoded = Base64.encodeURI(text);
const decoded = Base64.decodeURI(encoded);
Beyond basic encoding, these packages offer different levels of utility.
base-64 keeps it minimal.
encode, decode, toUint8Array, fromUint8Array.// base-64: Binary focus
import base64 from 'base-64';
const buffer = new Uint8Array([72, 105]);
const encoded = base64.fromUint8Array(buffer);
btoa mimics the browser API.
btoa and atob.// btoa: Minimal API
import 'btoa';
// Only global functions available
const str = btoa('data');
js-base64 offers extended utilities.
encode, decode, encodeURI, decodeURI.Buffer types directly.// js-base64: Extended API
import { Base64 } from 'js-base64';
// Works directly with Node Buffer
const buffer = Buffer.from('data');
const encoded = Base64.fromBuffer(buffer);
When selecting a dependency, you must consider if it is still necessary.
base-64 is stable and maintained.
btoa is largely obsolete for new code.
btoa built-in.js-base64 is actively used in full-stack apps.
| Feature | base-64 | btoa | js-base64 |
|---|---|---|---|
| Scope | Module Export | Global Polyfill | Module Export |
| UTF-8 Support | ❌ Manual Required | ❌ Manual Required | ✅ Built-in (encodeURI) |
| Node Buffer | ✅ (fromUint8Array) | ❌ | ✅ (fromBuffer) |
| Best Use Case | Strict Compliance | Legacy Node Polyfill | Modern Full-Stack Apps |
| Global Pollution | None | Yes | None |
btoa is a legacy tool.
Unless you are stuck on Node.js version 14 or lower, you likely do not need this package. Modern runtimes provide the global functions natively. Relying on it in new projects creates technical debt.
base-64 is a specialist tool.
It is excellent if you need a guaranteed, spec-compliant implementation that behaves exactly the same everywhere without relying on runtime globals. However, the lack of built-in UTF-8 support means you will write more boilerplate code to handle text safely.
js-base64 is the practical choice.
For most frontend and full-stack developers, this is the winner. It abstracts away the painful parts of JavaScript string encoding (UTF-16 vs UTF-8) and provides a consistent API across server and client. It saves you from debugging corrupted characters in your Base64 strings.
Final Thought: If you are handling user input or international text, skip the native polyfills and use js-base64. If you are manipulating raw binary data in a controlled environment, base-64 offers strict control. Avoid btoa in new architectures.
Choose base-64 if you need a strict, spec-compliant implementation that does not modify global objects. It is ideal for environments where you want explicit control over imports and need a reliable polyfill for older browsers that lack native atob or btoa. However, be prepared to handle UTF-8 string conversion manually before encoding.
Choose btoa only if you are maintaining legacy Node.js code (pre-16) that relies on global btoa functions and cannot be refactored. For new projects, avoid this package because modern Node.js versions include native Base64 support, making this polyfill redundant and potentially confusing.
Choose js-base64 if your application handles international text (UTF-8) and you want a single library that works seamlessly in both Node.js and browsers. It provides built-in methods to safely encode Unicode characters without extra conversion steps, making it the most practical choice for modern full-stack JavaScript development.
base64 is a robust base64 encoder/decoder that is fully compatible with atob() and btoa(), written in JavaScript. The base64-encoding and -decoding algorithms it uses are fully RFC 4648 compliant.
Via npm:
npm install base-64
In a browser:
<script src="base64.js"></script>
In Narwhal, Node.js, and RingoJS:
var base64 = require('base-64');
In Rhino:
load('base64.js');
Using an AMD loader like RequireJS:
require(
{
'paths': {
'base64': 'path/to/base64'
}
},
['base64'],
function(base64) {
console.log(base64);
}
);
base64.versionA string representing the semantic version number.
base64.encode(input)This function takes a byte string (the input parameter) and encodes it according to base64. The input data must be in the form of a string containing only characters in the range from U+0000 to U+00FF, each representing a binary byte with values 0x00 to 0xFF. The base64.encode() function is designed to be fully compatible with btoa() as described in the HTML Standard.
var encodedData = base64.encode(input);
To base64-encode any Unicode string, encode it as UTF-8 first:
var base64 = require('base-64');
var utf8 = require('utf8');
var text = 'foo © bar 𝌆 baz';
var bytes = utf8.encode(text);
var encoded = base64.encode(bytes);
console.log(encoded);
// → 'Zm9vIMKpIGJhciDwnYyGIGJheg=='
base64.decode(input)This function takes a base64-encoded string (the input parameter) and decodes it. The return value is in the form of a string containing only characters in the range from U+0000 to U+00FF, each representing a binary byte with values 0x00 to 0xFF. The base64.decode() function is designed to be fully compatible with atob() as described in the HTML Standard.
var decodedData = base64.decode(encodedData);
To base64-decode UTF-8-encoded data back into a Unicode string, UTF-8-decode it after base64-decoding it:
var encoded = 'Zm9vIMKpIGJhciDwnYyGIGJheg==';
var bytes = base64.decode(encoded);
var text = utf8.decode(bytes);
console.log(text);
// → 'foo © bar 𝌆 baz'
base64 is designed to work in at least Node.js v0.10.0, Narwhal 0.3.2, RingoJS 0.8-0.9, PhantomJS 1.9.0, Rhino 1.7RC4, as well as old and modern versions of Chrome, Firefox, Safari, Opera, and Internet Explorer.
After cloning this repository, run npm install to install the dependencies needed for development and testing. You may want to install Istanbul globally using npm install istanbul -g.
Once that’s done, you can run the unit tests in Node using npm test or node tests/tests.js. To run the tests in Rhino, Ringo, Narwhal, and web browsers as well, use grunt test.
To generate the code coverage report, use grunt cover.
| Mathias Bynens |
base64 is available under the MIT license.