Who this is for: JavaScript beginners who need to represent text as Base64 in a browser or Node.js application.
What you will learn: why btoa() is not a direct Unicode text encoder, how UTF-8 bytes fit into the conversion, and what Base64 does—and does not—provide.
Prerequisites: Basic JavaScript strings, typed arrays, and the difference between text and bytes are helpful.
Tested environment: Modern browsers with TextEncoder, TextDecoder, btoa(), and atob(). Node.js provides related APIs, but its Buffer methods and runtime support should be checked for the Node version in use.
Base64 encodes bytes, not characters
Base64 maps groups of binary bytes to characters from a defined alphabet. It is often used when a text-based format needs to carry bytes. The encoding is reversible and does not provide secrecy.
ASCII characters happen to fit the byte-oriented contract of btoa(). Many Unicode characters do not: a character such as é is represented by multiple UTF-8 bytes, while an emoji may use four. Passing such characters directly to btoa() can throw an InvalidCharacterError.
Convert JavaScript text through UTF-8
First encode the string as UTF-8 bytes. Then map each byte to a one-byte character so the browser's btoa() can encode those bytes:
function encodeBase64Text(text) {
const bytes = new TextEncoder().encode(text);
let binary = "";
for (let offset = 0; offset < bytes.length; offset += 0x8000) {
binary += String.fromCharCode(
...bytes.subarray(offset, offset + 0x8000)
);
}
return btoa(binary);
}
The chunks avoid passing an arbitrarily large byte array as one function call. For example, the UTF-8 text Hello, TeachyLeaf! becomes SGVsbG8sIFRlYWNoeUxlYWYh.
Decode Base64 back to text
atob() returns a binary string whose character codes represent bytes. Convert those codes into a byte array and then decode the bytes as UTF-8:
function decodeBase64Text(base64) {
const binary = atob(base64);
const bytes = Uint8Array.from(
binary,
character => character.charCodeAt(0)
);
return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
}
The fatal option makes invalid UTF-8 an explicit decoding error instead of silently inserting replacement characters. A production-facing function should also validate the input alphabet and padding, and decide whether to accept whitespace or URL-safe Base64. The Base64 text tool validates standard Base64 and reports invalid UTF-8.
Standard and URL-safe Base64 are different variants
Standard Base64 uses + and /, and may use = padding. URL-safe Base64 uses - and _ in place of those two characters and often omits padding. Do not silently treat one variant as the other: check the protocol or API contract and transform the alphabet and padding only when required.
Base64 is also unrelated to percent-encoding. Use the URL component encoder for a value inserted into a URL component; do not use Base64 as a substitute for URL encoding.
Node.js uses Buffer for byte conversion
In Node.js, Buffer can convert UTF-8 text directly:
const base64 = Buffer.from("café 🌿", "utf8").toString("base64");
const text = Buffer.from(base64, "base64").toString("utf8");
The browser APIs and Node's Buffer are runtime-specific interfaces. Follow the runtime and input requirements of the code you are writing rather than assuming the same API is available everywhere.
Security and data limits
- Base64 is not encryption, hashing, or access control. Anyone who receives the encoded value can decode it.
- Do not put secrets in Base64 and assume they are protected.
- These examples are for UTF-8 text. Arbitrary binary files need a deliberate byte-oriented workflow, not an assumption that their contents are text.
- Validate the input format and size expected by your application; successful decoding alone does not validate the data's meaning or safety.
Try a Unicode round trip
Use the browser-based Base64 encoder and decoder with text such as café 🌿. Encode it, decode the result, and compare the returned text with the original. The tool runs locally and is limited to text, standard Base64, and valid UTF-8 output.