Encode and decode Base64 in Node.js
Use Buffer for text or binary data, choose Base64URL when the protocol requires it, and validate external input separately.
Use Buffer.from(text, 'utf8').toString('base64') to encode text. Decode with Buffer.from(encoded, 'base64'), then convert to UTF-8 only if the decoded bytes represent UTF-8 text.
Round-trip text
const original = 'Hello, café!';
const encoded = Buffer.from(original, 'utf8').toString('base64');
const decoded = Buffer.from(encoded, 'base64').toString('utf8');
console.log(encoded);
console.log(decoded);
Buffer.from(string) and buffer.toString() both default to UTF-8. Specifying it makes the intended text encoding explicit; it is not required to prevent a special non-ASCII decoding bug. Binary data should remain a Buffer unless you intentionally decode it as text.
Encode a file
For a small existing PNG file:
const fs = require('node:fs');
const bytes = fs.readFileSync('photo.png');
const encoded = bytes.toString('base64');
const dataUri = `data:image/png;base64,${encoded}`;
fs.writeFileSync('copy.png', Buffer.from(encoded, 'base64'));
The MIME type in a data URI must match the content; a filename does not prove the bytes are a PNG. The example reads the entire file and creates another encoded representation. For large data, consider whether Base64 is necessary or use an encoder that correctly preserves three-byte grouping across streamed chunks.
Use the URL-safe variant
const encoded = Buffer.from('hello', 'utf8').toString('base64url');
const decoded = Buffer.from(encoded, 'base64url').toString('utf8');
console.log(encoded, decoded);
Node added the base64url encoding in 14.18.0 and 15.7.0; it is not exclusive to Node 20. It substitutes - and _ for + and / and omits padding when encoding. Older code commonly performs those substitutions and removes trailing = manually. Use a supported runtime and the built-in encoding when possible.
Standard Base64 can also appear in a URL if appropriately escaped. Choose the variant specified by the protocol, not a blanket rule that one is always invalid in a URL.
Understand validation and size
Base64 is reversible encoding, not encryption. It does not hide credentials or verify authenticity.
For n bytes, padded Base64 has 4 * Math.ceil(n / 3) characters. The overhead approaches one third for large inputs but is proportionally larger for tiny inputs. That does not include a data-URI prefix or in-memory string overhead.
Node's Base64 decoder is permissive, including acceptance of whitespace and alternate alphabet characters in documented cases. Do not use a successful decode as proof that input conforms to a strict protocol. Validate the required alphabet, padding, length, and size limits separately.
FAQ
Can I use btoa or atob? Node provides them for web compatibility, but their binary-string model is awkward for Unicode text. btoa() requires byte-valued characters; atob() returns a binary string, not UTF-8-decoded text. Buffer makes the byte/text conversion explicit.
Why does a JWT look different? JWT segments use Base64URL without padding. Decoding a segment does not validate the token's signature, issuer, audience, or expiry.
What if decoded text contains replacement characters? The bytes may not be valid UTF-8. Use a strict text decoder when invalid byte sequences must be rejected, or keep binary content as bytes.
References
Contact us
Join the community
to join the community
Try Guance
Start online and pay only for what you use.
Get startedChoose a Guance plan