How to Decode and Encode Base64 in TypeScript
A type-safe guide to Base64 in TypeScript. Learn how to securely handle Base64 strings with strict type checking and proper error boundaries.
Working with Base64 in TypeScript is similar to JavaScript, but with the added benefit of static type checking. When decoding Base64 strings—especially from external sources like APIs or user input—you need to handle potential runtime errors safely.
In this guide, we'll explore how to build type-safe Base64 encoding and decoding functions for both the browser and Node.js environments.
Type Safety First
btoa() and atob() can throw a DOMException if the input contains invalid characters. In TypeScript, a catch (error) block defaults to unknown. We'll show you how to properly type-check these errors.1. Browser: Type-Safe Base64 Encoding
In the browser, we use the global btoa() function. To ensure we safely handle Unicode characters (which normally break btoa()), we'll wrap it in a typed function using TextEncoder.
/**
* Safely encodes a string (including UTF-8/Emojis) to Base64 in the browser.
*/
export function encodeBase64(text: string): string {
try {
const bytes = new TextEncoder().encode(text);
const binString = Array.from(bytes, (byte) =>
String.fromCodePoint(byte)
).join("");
return btoa(binString);
} catch (error: unknown) {
if (error instanceof Error) {
throw new Error(`Failed to encode Base64: ${error.message}`);
}
throw new Error("An unknown error occurred during Base64 encoding");
}
}
// Usage:
const safeEncoded = encodeBase64("Hello TypeScript! 🚀");
console.log(safeEncoded); // SGVsbG8gVHlwZVNjcmlwdCEg8J+agA==2. Browser: Type-Safe Base64 Decoding
When decoding, we use atob(). If the input is not a valid Base64 string, atob() will throw. Our typed wrapper handles this gracefully.
/**
* Result type for safe decoding operations
*/
type DecodeResult =
| { success: true; data: string }
| { success: false; error: string };
/**
* Safely decodes a Base64 string (including UTF-8) without throwing unhandled exceptions.
*/
export function safeDecodeBase64(base64: string): DecodeResult {
try {
const binString = atob(base64);
const bytes = Uint8Array.from(binString, (m) => m.codePointAt(0) ?? 0);
const text = new TextDecoder().decode(bytes);
return { success: true, data: text };
} catch (error: unknown) {
if (error instanceof DOMException) {
return { success: false, error: "Invalid Base64 string format." };
}
return { success: false, error: "Unknown decoding error." };
}
}
// Usage:
const result = safeDecodeBase64("SGVsbG8gVHlwZVNjcmlwdCEg8J+agA==");
if (result.success) {
console.log("Decoded:", result.data);
} else {
console.error("Failed:", result.error);
}3. Node.js: Strict Buffer Types
If you are working in Node.js (or Next.js API Routes / Server Actions), you should use the Buffer class. TypeScript provides excellent type definitions for Buffer out of the box via @types/node.
import { Buffer } from "node:buffer";
/**
* Encodes a string to Base64 in Node.js
*/
export function encodeNodeBase64(text: string): string {
// Buffer.from takes the string and its encoding
const buffer: Buffer = Buffer.from(text, "utf-8");
return buffer.toString("base64");
}
/**
* Decodes a Base64 string in Node.js
*/
export function decodeNodeBase64(base64: string): string {
// We can validate if a string is valid Base64 (roughly) using regex
const base64Regex = /^[A-Za-z0-9+/]*={0,2}$/;
if (!base64Regex.test(base64)) {
throw new Error("Input string is not valid Base64");
}
const buffer: Buffer = Buffer.from(base64, "base64");
return buffer.toString("utf-8");
}4. Node.js: Base64URL and Custom Types
When dealing with URLs or JWTs, it is a good practice to use Opaque Types (also known as Branded Types) to distinguish between standard Base64 and Base64URL strings at compile time.
// Branded types to prevent mixing different string encodings
type Base64String = string & { readonly __brand: unique symbol };
type Base64UrlString = string & { readonly __brand: unique symbol };
function toBase64Url(data: string): Base64UrlString {
const encoded = Buffer.from(data, "utf-8").toString("base64url");
// Cast the result to our branded type
return encoded as Base64UrlString;
}
// This function explicitly requires a Base64UrlString
function processUrlToken(token: Base64UrlString) {
console.log("Processing safe token:", token);
}
const urlSafeToken = toBase64Url("subjects?dir=/usr/local");
// ✅ Works perfectly
processUrlToken(urlSafeToken);
// ❌ TypeScript Error: Argument of type 'string' is not assignable to type 'Base64UrlString'.
// processUrlToken("c3ViamVjdHM/ZGlyPS91c3IvbG9jYWw=");Validate Base64 Strings Instantly
Need to check if a string is valid Base64 before parsing it in your app? Use our interactive Base64 Validator.
Go to the Base64 Validator Tool