btoa()

A byte-to-base64 converter that takes a string, which is the source of all its trouble. Feeding it real Unicode text throws, and the fix is to encode to UTF-8 bytes first.

Global functionHTML standardLive demo
Common call
btoa(binaryString)
Returns
a base64 string
Replaces
a hand-written base64 encoder
Watch out
not Unicode-safe — encode to UTF-8 bytes first
btoa(stringstring — A "binary string" — one where every character is in the range U+0000 to U+00FF and stands for one byte. Anything above that throws.type: string · required)
→ string

Demo

Live evaluation
Try:
Inputs
sstringtext to base64-encode
Output
btoa('hello')
'aGVsbG8='

ASCII encodes cleanly, and the = padding appears whenever the input length is not a multiple of three. The é case works because U+00E9 fits in one byte — but note that it encodes the LATIN-1 byte, not the UTF-8 pair, so decoding it elsewhere as UTF-8 gives the wrong character. The fourth case is the hard limit: 中 is U+4E2D, above 255, and btoa throws rather than encoding it. Any text that might contain non-Latin-1 characters must be converted to UTF-8 bytes first.

Parameters

NameTypeRequiredDescription
stringstringyesA "binary string" — one where every character is in the range U+0000 to U+00FF and stands for one byte. Anything above that throws.

Return value

string — The base64 encoding of the input, treating each character as ONE BYTE. Throws InvalidCharacterError for any code point above 255.

Common patterns

Encode Unicode text safely
UTF-8 bytes first, then base64.
const bytes = new TextEncoder().encode(text);
const b64 = btoa(String.fromCharCode(...bytes));
Decode back to Unicode
The mirror image.
const bytes = Uint8Array.from(atob(b64), c => c.charCodeAt(0));
const text = new TextDecoder().decode(bytes);
A data URL
The common real use.
const src = `data:image/svg+xml;base64,${btoa(svg)}`;

Examples

1. ASCII
btoa('hello')
Returns
'aGVsbG8='
2. And back
atob('aGVsbG8=')
Returns
'hello'
3. Latin-1 works
btoa('é')
Returns
'6Q=='
4. Beyond Latin-1 throws
btoa('中')
Returns
InvalidCharacterError: Invalid character
5. UTF-8 first
btoa(String.fromCharCode(...new TextEncoder().encode('中')))
Returns
'5Lit'
6. Bad base64 throws
atob('!!!')
Returns
InvalidCharacterError: Invalid character

Pitfalls

1. It is not Unicode-safe
The single thing to know. btoa treats each character as a byte, so anything above U+00FF throws — emoji, CJK, Cyrillic, Greek. Encoding user text without a UTF-8 step works right up until someone types a character outside Latin-1.
Throws
btoa('中文')
InvalidCharacterError: Invalid character
Encode to bytes first
btoa(String.fromCharCode(...new TextEncoder().encode('中文')))
'5Lit5paH'
2. Latin-1 characters encode as the WRONG bytes
Worse than throwing, because it is silent. é encodes as the single Latin-1 byte 0xE9, not the UTF-8 pair 0xC3 0xA9 — so anything decoding the result as UTF-8 gets a replacement character. The data looks fine until it crosses a boundary.
One Latin-1 byte
btoa('é')
'6Q==' — decodes as invalid UTF-8
UTF-8 bytes
btoa(String.fromCharCode(...new TextEncoder().encode('é')))
'w6k='
3. Base64 is not encryption
It is a reversible encoding with no key. Putting a token or a password through btoa hides it from nobody — atob is one call away. It exists to make binary data safe to put in text, not to protect anything.
Trivially reversed
atob(btoa('my-secret'))
'my-secret'
Actually encrypt
await crypto.subtle.encrypt(alg, key, data)
ciphertext
4. Standard base64 is not URL-safe
The output can contain + and /, which are reserved in URLs, and the = padding needs escaping too. For a query parameter or a path, translate to the URL-safe alphabet or percent-encode the result.
Not URL-safe
btoa('\xff\xfe')
'//4=' — slashes and padding
URL-safe form
btoa(s).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '')
safe in a URL

When to use

Use it
  • Data URLs for inline images and SVG
  • Basic auth headers, which the spec defines in base64
  • Putting binary data into JSON or a text field
  • Round-tripping bytes through something that only accepts text
Reach for something else
  • Unicode text → encode to UTF-8 bytes first
  • Hiding secrets → it is not encryption
  • URLs → translate to the URL-safe alphabet
  • Large binary data → work with ArrayBuffer and streams instead

Notes

Complexity
O(n); the result is about 4/3 the input size
Return
A new string; the input is unchanged
CPython impl
Not V8 — btoa and atob come from the HTML standard, not ECMAScript
Memory
Allocates the result, roughly a third larger than the input
Thread-safe
Single-threaded; available in workers as well as the main thread

FAQ

Because it contains a character above U+00FF and btoa only handles bytes. Convert to UTF-8 with TextEncoder first, then turn those bytes into a binary string.

const b64 = btoa(String.fromCharCode(...new TextEncoder().encode(text)));

History

Netscape
btoa and atob shipped as browser extensions, taking their names from Unix utilities.
HTML5
Standardised in the HTML specification rather than ECMAScript, with the Latin-1 restriction preserved.