Library / SDK
dankogai/js-base64 avatar
dankogai/js-base64

js-base64 keeps two different decoders under one namespace

Base64 implementation for JavaScript

4,362 stars1,289 forksJavaScriptBSD-3-Clause

At a glance

What is it?
Dan Kogai's js-base64 is a TypeScript Base64 transcoder that ships both a UTF-8 string decoder and a byte-level atob-compatible decoder. That split is the whole design, and it is also where most misuse happens.
Who is it for?
Adopt js-base64 when the same Base64 code has to run in a browser page and in Node, when you want UTF-8 string handling alongside the byte-level atob and btoa pair, and when a dependency-free single file build matters more than the Buffer API.
Can I use it commercially?
Yes. BSD-3-Clause is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 13 days ago.
What is it written in?
Mainly JavaScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on November 13, 2026, and from our analysis. They are not legal advice.

From daily snapshots

Star history

  • dankogai/js-base644,362 ★ · +1
Star history
Repository2026-09-232026-11-13
dankogai/js-base644.4k4.4k

1 Stars in the last 51 days, measured from the daily GitHub snapshot. Compare with other projects

Editorial analysis

Two encoders and two decoders share the Base64 object

The library's central decision is that encoding to text and decoding to bytes are not the same operation, so it publishes both under one namespace. Base64.encode and Base64.decode work on UTF-8 strings. Base64.btoa and Base64.atob mirror the browser built-ins and work on bytes. For the same input string 'dankogai', Base64.encode and Base64.btoa both give ZGFua29nYWk=. The fork appears on non-ASCII input: Base64.btoa('小飼弾') raises an exception, while Base64.encode on the same literal gives 5bCP6aO85by+. Passing a second argument of true produces the URL-safe form, which uses - and _ and omits padding; the README calls this URI safe.

isValid accepts unpadded input and rejects a mixed alphabet

Base64.isValid is looser than most people expect, and the documentation's own examples show it. It returns false for 0 because the input is not a string, and true for the empty string, since that encodes a valid empty byte. 'Z A=' is valid because whitespace is tolerated, and 'ZA' is valid because padding can be omitted. Both '++' and '--' pass, so a URL-safe payload verifies. '+-' fails, because the two alphabets cannot be mixed in one string. Note the consequence for input validation: a payload that passes isValid can arrive unpadded or in either alphabet, and Base64.decode accepts both 5bCP6aO85by+ and 5bCP6aO85by- and returns 小飼弾 for either.

Installing js-base64 and importing it without a bundler

The install is one command, and the package then resolves differently depending on which field your toolchain reads. package.json points main at base64.js, module at base64.mjs, and types at base64.d.ts, with an exports map that hands ESM loaders the .mjs build and require() the .js build.

shell
$ npm install --save js-base64

In Node with CommonJS, the documented form is a destructure from require, and the README notes that unlike the browser case this does not modify the global context.

javascript
const {Base64} = require('js-base64');

In ESM you can take the namespace or the bare functions.

javascript
import { Base64 } from 'js-base64';
javascript
// or if you prefer no Base64 namespace
import { encode, decode } from 'js-base64';

For a browser page with no build step, the CDN snippet pins a version, so a reader following it today is loading 3.9.6 rather than whatever the latest tag happens to be.

html
<script src="https://cdn.jsdelivr.net/npm/[email protected]/base64.min.js"></script>

base64.js taints window, base64.mjs does not

The script-tag build is the classic global pattern: loading base64.js puts Base64 on window. The README offers Base64.noConflict() to hand the global back, but its actual advice is to avoid the situation by using an ES module instead. That is why the package ships both base64.js and base64.mjs from the same base64.ts source, compiled through Rollup. The module build can also be pulled straight from the CDN, and the README flags a detail that trips people up: jsdelivr.net does not minify a .mjs file automatically, so a module loaded that way is readable source in production.

html
<script type="module">
// note jsdelivr.net does not automatically minify .mjs
import { Base64 } from 'https://cdn.jsdelivr.net/npm/[email protected]/base64.mjs';
</script>

Package version is 3.9.6 in package.json, and sideEffects is false, so a bundler can drop the import if you never call it.

Prototype extensions are opt-in and irreversible for the page

By default the library leaves built-in prototypes alone, which is the right default for a shared dependency. Turning the sugar on is a separate, explicit step. Base64.extendString() adds toBase64, toBase64URI, toBase64URL, fromBase64 and toUint8Array to String.prototype. Base64.extendUint8Array() adds toBase64, toBase64URI and toBase64URL to Uint8Array.prototype. Base64.extendBuiltins() does both in one call. There is no matching unextend call, so once a page has run extendBuiltins() every string in that page answers to toBase64, which is a namespace collision waiting to happen in a bundle that mixes this library with another string extension package.

Base64.decode on a PNG is the documented mistake

The failure mode the README bothers to spell out in capitals is decoding binary with the string decoder. For a 1x1 transparent PNG the README says in caps not to use Base64.decode, because decode() produces a UTF-8 string and the bytes are not UTF-8 text. Use Base64.atob for the bytes, or better Base64.toUint8Array(pngBase64). The same rule applies in reverse: Base64.btoa is the byte direction, Base64.encode is the string direction. A second, quieter trap is the same in the other direction. Base64.atob('5bCP6aO85by+') returns the mojibake the README calls nonsense, while Base64.decode on the identical string returns 小飼弾. Nothing throws in either case, so the mistake surfaces later as corrupted data.

Where Buffer and the native built-ins take a different road

On the server, Node's Buffer already covers most of this ground without a dependency: Buffer.from(string).toString('base64') and Buffer.from(text, 'base64') work on bytes, and the encoding is chosen per call rather than fixed by a namespace. In the browser, window.btoa and window.atob exist for binary strings but throw on characters above U+00FF. The difference in approach is that js-base64 moves the choice to the method name and keeps a UTF-8 string path, so the same import works unchanged on both runtimes. Base64.toUint8Array and Base64.fromUint8Array are the bridge functions when you want bytes, and fromUint8Array on the Uint8Array [100,97,110,107,111,103,97,105] gives ZGFua29nYWk=.

TypeScript source, Rollup build, mocha tests

Since version 3.3 the library has been written in TypeScript, and base64.js plus base64.mjs are both compiled from base64.ts. Version 3.7 restored an ES5-compatible base64.js, so the classic script-tag build works on IE11. The package.json scripts show the maintenance routine: npm test runs the Rollup build, then mocha, then a type check with tsc --noEmit --strict over test/types.cts and test/types.mts, and the version script rebuilds, runs util/sync-version.js and stages the generated files. The repository is licensed BSD-3-Clause, and its last push was on 2026-11-01.

Editorial conclusion

Adopt js-base64 when the same Base64 code has to run in a browser page and in Node, when you want UTF-8 string handling alongside the byte-level atob and btoa pair, and when a dependency-free single file build matters more than the Buffer API. Leave it out if your work is server-side text and bytes only, where Buffer.from already covers the ground without a package, and leave it out if you plan to lean on extendBuiltins(), because the added String.prototype methods stay installed for the life of the page with no call to remove them. Before you commit, round trip one real non-ASCII string such as 小飼弾 through Base64.encode and Base64.decode and confirm you get the characters back rather than a mojibake string, since the wrong one of those two calls fails quietly instead of throwing.

Frequently asked questions

How do I decode Base64 data in JavaScript with js-base64?

Use Base64.decode for UTF-8 text, and Base64.atob or Base64.toUint8Array when the payload is bytes. Base64.decode accepts both the standard and URL-safe flavors, with or without padding, so 5bCP6aO85by+ and 5bCP6aO85by- both give 小飼弾.

How do I Base64 encode in Node.js with js-base64?

Install with npm install --save js-base64 and import it with const {Base64} = require('js-base64');, which is the CommonJS form the README gives. Unlike the browser script-tag case, this does not modify the global context.

Why does Base64.atob return nonsense for a Base64-encoded UTF-8 string?

Base64.atob decodes to bytes the way the browser built-in does, so a UTF-8 payload comes back as a mojibake binary string. Base64.decode is the call that decodes to a UTF-8 string, and the README marks the mismatch with DO NOT USE on the binary direction.

What does the true argument in Base64.encode do?

It produces the URL-safe form, which uses - and _ instead of + and / and omits padding. Base64.encode('dankogai', true) gives ZGFua29nYWk and Base64.encode('小飼弾', true) gives 5bCP6aO85by-, which is the URI safe form.

How do I add toBase64 to strings with js-base64?

Call Base64.extendString() to extend String.prototype, or Base64.extendUint8Array() for the typed array, or Base64.extendBuiltins() for both. Nothing is extended by default, and the library offers no matching call to remove the added methods.

Official sources

  1. dankogai/js-base64 on GitHub
  2. Issues
  3. License: BSD-3-Clause
  4. README
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/dankogai-js-base64.svg)](https://hysenlabs.com/projects/dankogai-js-base64)

Star history chart for your README

markdown
[![Star history](https://hysenlabs.com/chart/dankogai-js-base64.svg)](https://hysenlabs.com/projects/dankogai-js-base64)

Was this useful?