How to Use Uint8Array Base64 Helpers in JavaScript

How to Use Uint8Array Base64 Helpers in JavaScript

JavaScript now has native Uint8Array helpers for converting bytes to and from Base64 and hex. Use them when your data is actually bytes: file chunks, image payloads, cryptographic output, compact URL tokens, or binary API responses. They are cleaner than atob() and btoa() because they work with Uint8Array directly.

The short version looks like this:

const bytes = Uint8Array.fromBase64("SGVsbG8=");
console.log(new TextDecoder().decode(bytes)); // "Hello"

const encoded = new TextEncoder().encode("Hello").toBase64();
console.log(encoded); // "SGVsbG8="

That is the main point. Decode Base64 into bytes, encode bytes into Base64, and only use TextEncoder or TextDecoder when those bytes represent text.

Why This Is Better Than atob() And btoa()

atob() and btoa() deal in strings. That is fine when the data is text-like, but it gets awkward when the real thing you need is a byte array.

A common old decode pattern looked like this:

const binary = atob(base64);
const bytes = Uint8Array.from(binary, (char) => char.charCodeAt(0));

That works often enough to spread through codebases, but it is not a great API for binary data. It makes you hold a string where each character is being treated as one byte.

The native version says what you mean:

const bytes = Uint8Array.fromBase64(base64);

The same idea applies in the other direction. Instead of building a binary string just so btoa() can encode it, start with bytes and encode those bytes:

const bytes = new Uint8Array([72, 101, 108, 108, 111]);
const base64 = bytes.toBase64();

Decode Base64 Into Bytes

Use Uint8Array.fromBase64() when you want a new byte array:

const avatarBytes = Uint8Array.fromBase64(response.avatar);

const blob = new Blob([avatarBytes], {
  type: "image/png"
});

If the Base64 string contains UTF-8 text, decode the bytes after the Base64 step:

const bytes = Uint8Array.fromBase64("eyJyb2xlIjoiYWRtaW4ifQ==");
const json = new TextDecoder().decode(bytes);
const payload = JSON.parse(json);

console.log(payload.role); // "admin"

Do not skip the byte step and assume every Base64 string is text. Base64 is an encoding for bytes. Sometimes those bytes are UTF-8 JSON. Sometimes they are a PNG, a signature, a compressed payload, or a binary protocol message.

Encode Bytes As Base64

Use toBase64() on a Uint8Array:

const bytes = await crypto.subtle.digest(
  "SHA-256",
  new TextEncoder().encode("hello")
);

const hashBase64 = new Uint8Array(bytes).toBase64();
console.log(hashBase64);

That example wraps the ArrayBuffer returned from crypto.subtle.digest() in a Uint8Array, then encodes the bytes.

If your starting value is a string, convert it to bytes first:

const message = "user:42";
const bytes = new TextEncoder().encode(message);
const token = bytes.toBase64();

That keeps the text encoding explicit. It also avoids the classic Unicode trap where btoa("✓") fails because btoa() expects a binary string, not arbitrary JavaScript text.

Use base64url For URLs And Tokens

Standard Base64 uses +, /, and sometimes trailing = padding. That is not ideal inside URLs.

For URL-safe values, use alphabet: "base64url":

const bytes = crypto.getRandomValues(new Uint8Array(16));

const token = bytes.toBase64({
  alphabet: "base64url",
  omitPadding: true
});

Decode it with the same alphabet:

const bytes = Uint8Array.fromBase64(token, {
  alphabet: "base64url"
});

This is a good fit for small random tokens, state parameters, and other compact URL values. It does not make the content secret. It only changes how the bytes are represented.

Write Into A Preallocated Array

Most app code can use Uint8Array.fromBase64(). Reach for setFromBase64() when you already have a target buffer:

const target = new Uint8Array(1024);
const result = target.setFromBase64(chunk, {
  lastChunkHandling: "stop-before-partial"
});

console.log(result.read);
console.log(result.written);

The return object tells you how many Base64 characters were read and how many bytes were written. That matters in stream-style code where the final chunk may not be complete yet.

If you are not writing chunked decoding logic, do not start here. fromBase64() is easier to read.

Hex Helpers Are Part Of The Same Change

The same proposal adds native hex conversion helpers:

const bytes = new Uint8Array([222, 173, 190, 239]);

console.log(bytes.toHex()); // "deadbeef"
console.log(Uint8Array.fromHex("deadbeef"));

Hex is still useful for hashes, IDs, debug output, and small protocol values. Base64 is more compact. Pick the format your API, database, or user-facing output expects.

Add A Compatibility Check

MDN marks the Base64 helpers as Baseline 2025 and newly available since September 2025. Chrome lists the Base64 and hex helpers in Chrome 140 release notes, and Firefox added support in Firefox 133.

That is good enough for newer browser targets, but it is not enough for every production app. Older browsers, older embedded WebViews, and older server runtimes may not have these methods yet.

Use feature detection before deleting a fallback:

function hasNativeBase64Bytes() {
  return typeof Uint8Array.fromBase64 === "function" &&
    typeof Uint8Array.prototype.toBase64 === "function";
}

If you ship to older targets, keep your existing helper or use a tested polyfill until your support matrix catches up.

Sources

Stay Sharp. Weekly Insights.
New posts, framework updates and weekly software conversations.

No spam. Unsubscribe anytime.
Walt is a software engineer, startup founder and previous mentor for a coding bootcamp. He has been creating software for the past 20+ years.
No comments posted yet
// Add a comment
// Color Theme

Custom accent
Pick any color
for the accent