You can use btoa() and atob() to convert to and from base64 encoding.

There appears to be some confusion in the comments regarding what these functions accept/return, so…

  • btoa() accepts a “string” where each character represents an 8-bit byte – if you pass a string containing characters that can’t be represented in 8 bits, it will probably break. This isn’t a problem if you’re actually treating the string as a byte array, but if you’re trying to do something else then you’ll have to encode it first.

  • atob() returns a “string” where each character represents an 8-bit byte – that is, its value will be between 0 and 0xff. This does not mean it’s ASCII – presumably if you’re using this function at all, you expect to be working with binary data and not text.

See also:

  • How do I load binary image data using Javascript and XMLHttpRequest?

Most comments here are outdated. You can probably use both btoa() and atob(), unless you support really outdated browsers.

Check here:

  • https://caniuse.com/?search=atob
  • https://caniuse.com/?search=btoa

In 2025, all "evergreen" browsers offer toBase64 and fromBase64 to convert a Uint8Array to and from Base64 string. If your input is a Unicode string, not raw bytes, you can convert it to and from Uint8Array (of UTF-8 bytes) by TextEncoder and TextDecoder. See also another answer about this.

Answer from Shog9 on Stack Overflow
🌐
MDN Web Docs
developer.mozilla.org › en-US › docs › Glossary › Base64
Base64 - Glossary | MDN
This has led to a common misconception that btoa can be used to encode arbitrary text data — for example, creating a Base64 data: URL of a text or HTML document. However, the byte-to-code-point correspondence only reliably holds true for code points up to 0x7f. Furthermore, code points over 0xff will cause btoa to throw an error due to exceeding the maximum value for 1 byte. The Window.btoa() "Unicode strings" section details how to work around this limitation when encoding arbitrary Unicode text. JavaScript APIs: Window.atob() (also available in workers) Window.btoa() (also available in workers) Uint8Array ·
🌐
DigitalOcean
digitalocean.com › community › tutorials › how-to-encode-and-decode-strings-with-base64-in-javascript
How To Encode and Decode Strings with Base64 in JavaScript | DigitalOcean
Learn how to encode and decode strings with Base64 in JavaScript. This guide covers btoa(), atob(), Buffer, modern APIs, and practical examples.
Top answer
1 of 16
1415

You can use btoa() and atob() to convert to and from base64 encoding.

There appears to be some confusion in the comments regarding what these functions accept/return, so…

  • btoa() accepts a “string” where each character represents an 8-bit byte – if you pass a string containing characters that can’t be represented in 8 bits, it will probably break. This isn’t a problem if you’re actually treating the string as a byte array, but if you’re trying to do something else then you’ll have to encode it first.

  • atob() returns a “string” where each character represents an 8-bit byte – that is, its value will be between 0 and 0xff. This does not mean it’s ASCII – presumably if you’re using this function at all, you expect to be working with binary data and not text.

See also:

  • How do I load binary image data using Javascript and XMLHttpRequest?

Most comments here are outdated. You can probably use both btoa() and atob(), unless you support really outdated browsers.

Check here:

  • https://caniuse.com/?search=atob
  • https://caniuse.com/?search=btoa

In 2025, all "evergreen" browsers offer toBase64 and fromBase64 to convert a Uint8Array to and from Base64 string. If your input is a Unicode string, not raw bytes, you can convert it to and from Uint8Array (of UTF-8 bytes) by TextEncoder and TextDecoder. See also another answer about this.

2 of 16
330

From here:

/**
*
*  Base64 encode / decode
*  http://www.webtoolkit.info/
*
**/
var Base64 = {

    // private property
    _keyStr : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",

    // public method for encoding
    encode : function (input) {
        var output = "";
        var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
        var i = 0;

        input = Base64._utf8_encode(input);

        while (i < input.length) {

            chr1 = input.charCodeAt(i++);
            chr2 = input.charCodeAt(i++);
            chr3 = input.charCodeAt(i++);

            enc1 = chr1 >> 2;
            enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
            enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
            enc4 = chr3 & 63;

            if (isNaN(chr2)) {
                enc3 = enc4 = 64;
            } else if (isNaN(chr3)) {
                enc4 = 64;
            }

            output = output +
            this._keyStr.charAt(enc1) + this._keyStr.charAt(enc2) +
            this._keyStr.charAt(enc3) + this._keyStr.charAt(enc4);
        }
        return output;
    },

    // public method for decoding
    decode : function (input) {
        var output = "";
        var chr1, chr2, chr3;
        var enc1, enc2, enc3, enc4;
        var i = 0;

        input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");

        while (i < input.length) {

            enc1 = this._keyStr.indexOf(input.charAt(i++));
            enc2 = this._keyStr.indexOf(input.charAt(i++));
            enc3 = this._keyStr.indexOf(input.charAt(i++));
            enc4 = this._keyStr.indexOf(input.charAt(i++));

            chr1 = (enc1 << 2) | (enc2 >> 4);
            chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
            chr3 = ((enc3 & 3) << 6) | enc4;

            output = output + String.fromCharCode(chr1);

            if (enc3 != 64) {
                output = output + String.fromCharCode(chr2);
            }
            if (enc4 != 64) {
                output = output + String.fromCharCode(chr3);
            }
        }

        output = Base64._utf8_decode(output);

        return output;
    },

    // private method for UTF-8 encoding
    _utf8_encode : function (string) {
        string = string.replace(/\r\n/g,"\n");
        var utftext = "";

        for (var n = 0; n < string.length; n++) {

            var c = string.charCodeAt(n);

            if (c < 128) {
                utftext += String.fromCharCode(c);
            }
            else if((c > 127) && (c < 2048)) {
                utftext += String.fromCharCode((c >> 6) | 192);
                utftext += String.fromCharCode((c & 63) | 128);
            }
            else {
                utftext += String.fromCharCode((c >> 12) | 224);
                utftext += String.fromCharCode(((c >> 6) & 63) | 128);
                utftext += String.fromCharCode((c & 63) | 128);
            }
        }
        return utftext;
    },

    // private method for UTF-8 decoding
    _utf8_decode : function (utftext) {
        var string = "";
        var i = 0;
        var c = c1 = c2 = 0;

        while ( i < utftext.length ) {

            c = utftext.charCodeAt(i);

            if (c < 128) {
                string += String.fromCharCode(c);
                i++;
            }
            else if((c > 191) && (c < 224)) {
                c2 = utftext.charCodeAt(i+1);
                string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
                i += 2;
            }
            else {
                c2 = utftext.charCodeAt(i+1);
                c3 = utftext.charCodeAt(i+2);
                string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
                i += 3;
            }
        }
        return string;
    }
}

Also, search for "JavaScript base64 encoding" turns up a lot of other options, and the above was the first one.

🌐
W3Schools
w3schools.com › tools › tool_base64.php
Base64 Encode/Decode - W3Schools
Base64 is a binary-to-text encoding scheme that represents binary data as an ASCII string. It's commonly used for: Embedding images in HTML/CSS using Data URIs · Encoding binary data in JSON or XML · Email attachments (MIME) Storing complex ...
🌐
GitHub
github.com › dankogai › js-base64
GitHub - dankogai/js-base64: Base64 implementation for JavaScript · GitHub
// or if you prefer no Base64 namespace import { encode, decode } from 'js-base64'; or even remotely.
Author: dankogai
🌐
Base64 Encode
base64encode.org
Base64 Encode and Decode - Online
Enable this option to encode into an URL- and filename- friendly Base64 variant (RFC 4648 / Base64URL) where the "+" and "/" characters are respectively replaced by "-" and "_", as well as the padding "=" signs are omitted. Live mode: When you turn on this option the entered data is encoded immediately with your browser's built-in JavaScript functions, without sending any information to our servers.
Find elsewhere
🌐
Jam
jam.dev › utilities › base-64-encoder
Base64 Encoder/Decoder | Free, Open Source & Ad-free
In JavaScript, Base64 encoding and decoding can be done using the built-in btoa and atob functions.
🌐
Human Who Codes
humanwhocodes.com › blog › 2009 › 12 › 08 › computer-science-in-javascript-base64-encoding
Computer science in JavaScript: Base64 encoding - Human Who Codes
Base64 encoding in many languages deal directly with bytes and byte arrays. Since JavaScript doesn’t have native data types for either, the bitwise operators become very important to this process. Bitwise operators act directly on the underlying bit representation of numbers.
🌐
Akamai
akamai.com › cloud › guides › javascript-base-64-decode
How to Use JavaScript Base 64 to Decode and Encode | Linode Docs
March 29, 2023 - Although translating data to or from Base64 manually is quite complicated, it is very easy in JavaScript. Developers can use the built-in function btoa, standing for “binary to ASCII”, to encode data into Base64. Data can be decoded using ...
🌐
DEV Community
dev.to › migsarnavarro › how-to-base64-encode-an-image-in-javascript-4k8e
How to base64 encode an image in javascript - DEV Community
May 1, 2020 - You will learn how you can encode an image as a base64 string in client side js, it can even be used in the browser console. Tagged with base64, image, tip, javascript.
🌐
GitHub
gist.github.com › mzabriskie › 5304726
Base64 encode/decode for JavaScript · GitHub
Base64 encode/decode for JavaScript. GitHub Gist: instantly share code, notes, and snippets.
🌐
npm
npmjs.com › package › js-base64
js-base64 - npm
August 17, 2026 - let latin = 'dankogai'; let utf8 = '小飼弾' let u8s = new Uint8Array([100,97,110,107,111,103,97,105]); Base64.encode(latin); // ZGFua29nYWk= Base64.encode(latin, true); // ZGFua29nYWk skips padding Base64.encodeURI(latin); // ZGFua29nYWk Base64.btoa(latin); // ZGFua29nYWk= Base64.btoa(utf8); // raises exception Base64.fromUint8Array(u8s); // ZGFua29nYWk= Base64.fromUint8Array(u8s, true); // ZGFua29nYW which is URI safe Base64.encode(utf8); // 5bCP6aO85by+ Base64.encode(utf8, true) // 5bCP6aO85by- Base64.encodeURI(utf8); // 5bCP6aO85by-
      » npm install js-base64
    
Published: Sep 19, 2026
Version: 3.9.4
🌐
Base64 Decode
base64decode.org
Base64 Decode and Encode - Online
Decode each line separately: The encoded data usually consists of continuous text, so even newline characters are converted into their Base64-encoded forms. Prior to decoding, all non-encoded whitespaces are stripped from the input to safeguard the input's integrity. This option is useful if you intend to decode multiple independent data entries that are separated by line breaks. Live mode: When you turn on this option the entered data is decoded immediately with your browser's built-in JavaScript functions, without sending any information to our servers.
Top answer
1 of 3
20

TL;DR In principle escape()/unescape() are not necessary, and your second version without the deprecated functions is safe, yet it generates longer base64 encoded output:

  • console.log(decodeURIComponent(atob(btoa(encodeURIComponent("€uro")))))
  • console.log(decodeURIComponent(escape(atob(btoa(unescape(encodeURIComponent("€uro")))))))

both create the output "€uro" yet the version without escape()/unescape() with a longer base64 representation

  • btoa(encodeURIComponent("€uro")).length // = 16
  • btoa(unescape(encodeURIComponent("€uro"))).length // = 8

The escape()/unescape() step can only become necessary if the counterpart (e.g. an unadjustable php-Script expecting the base64 to be done in the specific way.).

Long version:

First, to better understand the differences in between the two versions of toBase64() and fromBase64() that you suggest above, let us have a look to the btoa() which is at the core of the issue. Documentation says, that the naming of btoa is mnemonic so that

"b" can be considered to stand for "binary", and the "a" for "ASCII".

which is somewhat misleading, as the documentation hastens to add, that

in practice, though, for primarily historical reasons, both the input and output of these functions are Unicode strings.

Even less perfect, btoa() is indeed only accepting

characters in the range U+0000 to U+00FF

plainly spoking only only English alpha-numeric-text works with btoa().

The purpose of encodeURIComponent(), which you have in both of your versions, is to help out with strings having character outside the range U+0000 to U+00FF. An example would be the string "uü€" having three characters

  • a (U+0061)
  • ä (U+00E4)
  • € (U+20AC)

Here only the two first characters are in range. The third character, the Euro sign, is outside and window.btoa("€") raises an out of range error. To avoid such an error a solution is needed to represent "€" within the set of U+0000 to U+00FF. This is what window.encodeURIComponent does:

window.encodeURIComponent("uü€")
creates the following string:
"a%C3%A4%E2%82%AC" in which some characters have been encoded

  • a = a (stayed the same)
  • ä = %C3%A4 (changed to its utf8 representation)
  • € = %E2%82%AC (changed to its utf8 representation)

The (changed to its utf8 representation) works by using the character "%" and a two digit number for each byte of the character's utf8 representation. The "%" is U+0025 and hence allowed inside the btoa()-range. The result of window.encodeURIComponent("uü€") can then be fed to btoa() as it has no out of range characters anymore:

btoa("a%C3%A4%E2%82%AC") \\ = "YSVDMyVBNCVFMiU4MiVBQw=="

The crux of using an unescape() in between the btoa() and the encodeURIComponent() is that all bytes of the utf8 representation use up 3 characters %xx to store all potential values of a byte 0x00 to 0xFF. Here is where unescape() can play an optional role. This is because unescape() takes all such %xx bytes and creates in its place a single Unicode character in the allowed U+0000 to 0+00FF range.

To check :

  • btoa(encodeURIComponent("uü€"))).length // = 24
  • btoa(unescape(encodeURIComponent("uü€"))).length // = 8

the main difference is a length reduction of the base64 representation of the text, at the cost of additional parsing via the optional escape()/unescape(), which in case of mainly ASCII character set text is minimal anyway.

The main lesson to understand is that btoa() is misleadingly named and requires Unicode U+0000 to U+00FF characters which encodeURIComponent() by itself generates. The deprecated escape()/unescape() only has a space saving feature, which is maybe desirable but not necessary. The problem of Unicode symbols > U+00FF is addressed here as the btoa/atob Unicode problem, which mentions even ways to improve "all UTF8 Unicode" to base64 encoding possible in modern browsers.

2 of 3
15

TL;DR / Short Summary

Don't use btoa(encodeURIComponent(str)) and decodeURIComponent(atob(str)) - that's “nonsense”.

“convert string to Base64” usually means “encode string as UTF-8 and encode the bytes as Base64”, and that's exactly what btoa(unescape(encodeURIComponent(str))) does. btoa(encodeURIComponent(str)) is doing something else that isn't useful for any case I can imagine, even though it never throws an error as explained in humanityANDpeaces detailed answer.



What does “convert string to Base64” mean?

Base64 is a binary-to-text encoding, a sequence of bytes is encoded as a sequence of ASCII characters.1 It is therefore not possible to directly encode text as Base64. It is conceptually always a two step procedure:

  1. convert string to bytes (using some character encoding)
  2. encode bytes as Base64

You can principally use any character encoding (also called charset2 or Encoding Scheme) you want, it just needs to be able to represent all needed characters and it has to be the same for both directions (text to Base64 and Base64 to text). As there are many different character encodings, the protocol or API should define which one is used. If an API expects a "string encoded via Base64" and doesn't mention the character encoding, you can nowadays usually assume, that UTF-8 encoding is expected.3

Base64-encoding the bytes from step 1 is pretty straightforward:
a) Take three input bytes to get 24 bits.
b) Split into four chunks of 6 bits each, to get four numbers in range 0...63.
c) Translate numbers to ASCII chars via table and add these chars to the output
d) Goto a)
More details about Base64 itself are out of the scope of this answer.

What does btoa do?

By now you might think: “This answer can't possibly be correct. It claims, that it is not possible to directly encode text as Base64, even though this is exactly what btoa does - it takes text and spits out Base64.”

No. It does not take text and returns Base64, it takes an argument of type string and returns Base64. But that string argument doesn't represent text, it is just a strange way to store a sequence of bytes. Each byte is represented by a character whose numerical code point value is equal to the value of the byte.4

A Note in the HTML standard says, that “the "b" can be considered to stand for "binary", and the "a" for "ASCII". ” Contrary to popular opinion, I don't think, that btoa is named badly. It does not take text, it takes binary data and produces an ASCII string using Base64, so a short form of “binary to ascii” is an absolutely correct name. It's the argument type, that is misleading.

The definition of btoa in the HTML standard simply says:

[...] the user agent must convert that argument to a sequence of octets whose nth octet is the eight-bit representation of the code point of the nth character of the argument, and then must apply the base64 algorithm to that sequence of octets, and return the result.

I don't know and probably will never know, why they didn't chose a different argument type e.g. an array of numbers. Maybe the performance wasn't as good at the time when btoa was first specified?

What does unescape(encodeURIComponent(str)) do?

By now you could think: “If the first step in converting text to Base64 is encoding the text to bytes, then how is btoa(unescape(encodeURIComponent(str))) achieving that? btoa doesn't do that, but neither unescape nor encodeURIComponent seem to be in any way related to character encoding?”

Actually, encodeURIComponent is related to character encoding. The standard says:

The encodeURIComponent function computes a new [...] URI in which each instance of certain code points is replaced by [...] escape sequences representing the UTF-8 encoding of the code point.

So now we have the percent-encoded UTF-8 bytes. To convert the percent-encoded bytes to a binary string suitable for btoa, one can use unescape, because the behavior description states among other things:

  • If c is the code unit 0x0025 (PERCENT SIGN), then
    • [... how to decode %uXXXX ...]
    • Else if k ≤ length - 3 and [... two hexdigits follow ...] then
      • Set c to the code unit whose value is the integer represented by [...] the two hexadecimal digits at indices k + 1 and k + 2 within string.

Therefore after encodeURIComponent stored the UTF-8 bytes as %XX, unescape turns them into single codepoints exactly as required by btoa. So all in all btoa(unescape(encodeURIComponent(str))) encodes text to UTF-8 bytes which are then encoded to Base64.

Back to the original question

In case you forgot, the question was:

(1) Why did the originally proposed solution include calls to escape() and unescape()? The solution was proposed prior to deprecation but presumably these functions added some kind of value at the time.

(2) Are there certain edge cases where my removal of these deprecated calls will cause my wrapper functions to fail?

Without unescape you don't get a Base64 representation of a UTF-8 encoded string. btoa(encodeURIComponent(str)) encodes text to some strange bytes (not a standardized Unicode Encoding Scheme, but the bytes one can get by storing an URI-encoded string as ASCII) which are then encoded as Base64. So unescape is necessary for standard conformance -- OK, encodeURIComponent and ASCII are also standardized, but nobody will expect that strange combination.

If only you yourself are converting to and from Base64, then yes you could use btoa(encodeURIComponent(str)) and it will never throw an error as explained in humanityANDpeaces detailed answer (Question (2) is sufficiently answered I think).

But in that case you could much better just use the result of encodeURIComponent directly. It already is pure ASCII and is always shorter than btoa(encodeURIComponent(str)). If you want smaller size than encodeURIComponent(str) you can use btoa(unescape(encodeURIComponent(str))) (smaller if input string contains more non-ASCII chars).

If you convert to Base64, because some other party, API or protocol expects Base64, then you simply can not use btoa(encodeURIComponent(str)), because nobody understands the result.

Oh, and btoa(unescape(encodeURIComponent(str))) couldn't really be “proposed prior to deprecation” of unescape:
unescape was removed from the standard in version 3, the same version that added encodeURIComponent. unescape was still explained in the document, but was moved to Annex B.2, whose introduction stated, that it “suggests uniform semantics [...] without making the properties or their semantics part of this standard.” But as browsers have to be backwards compatible, it probably won't be removed any time soon.


Try for yourself:

Show code snippet

function run(){
    let Base64Function=new Function("str", $("#algorithm").val());
    let base64=Base64Function($("#input").val());
    $("#Base64Text").text("Output: "+base64);
    let charset=$('#charset').val();
    let uri="data:text/plain"
           +(charset?";charset="+charset:'')
           +($("#interpret").prop('checked')?";base64":'')
           +","+base64;
    $("#dataURI").text(uri);
    $("#dataURI").attr('href', uri);
    $("#Base64iframe").attr('src',uri);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

<label for="input">Text to encode:</label>
<input type="text" id="input" value="abc€😀"/><br />

<label for="algorithm">Encode function:</label>
<input type="text" id="algorithm" size="50"/><br />

<button type="button" onclick="run();">Run</button>
Defaults:
<button type="button" onclick='
    $("#algorithm").val("return btoa(unescape(encodeURIComponent(str)))");
    $("#charset").val("UTF-8");
    $("#interpret").prop("checked",true);
'>UTF-8 Base64</button>
<button type="button" onclick='
    $("#algorithm").val("return btoa(encodeURIComponent(str))");
    $("#charset").val(""); //I don't know - it's not UTF-8
    $("#interpret").prop("checked",true);
'>wrong</button>
<button type="button" onclick='
    $("#algorithm").val("return encodeURIComponent(str)");
    $("#charset").val("UTF-8");
    $("#interpret").prop("checked",false);
'>without btoa (not Base64)</button>
<br />

<div id="Base64Text">Output:</div>

<label for="charset">Interpret as this character encoding:</label>
<input type="text" id="charset" /><br />

<label for="interpret">Interpret as Base64:</label>
<input type="checkbox" id="interpret" /><br />

<div><a id="dataURI"></a></div>
<iframe id="Base64iframe"></iframe>
Run code snippetEdit code snippet Hide Results Copy to answer Expand

This snippet tests the Base64 result by creating a dataURI, but the concept applies to other applications of Base64 as well.


Note:

In some quotations I use [ and ] to leave out or shorten things that are unimportant in my opinion.
[... some text ...] is obviously not part of the source.

Footnotes:

1 The standard says that Base64 “is designed to represent arbitrary sequences of octets” (octet means byte consisting of eight bits)

2 A character set is not exactly the same as a character encoding. However a coded character set can always be considered to implicitly define a character encoding, therefore "character set" and "character encoding" are often used as synonyms. Maybe it once was the same? Sometimes the term charset is explicitly used as a short term for character encoding and not for character set.

3 At least UTF-8 is very dominant for websites. Also see UTF-8 Everywhere

4 This is effectively the ISO_8859-1 encoding, but I wouldn't think of it this way. Better think bytes[i]==str.charCodeAt(i).

🌐
Medium
medium.com › @olenkadark › how-to-encode-and-decode-strings-with-base64-in-javascript-100ec741873d
Master Base64 Encoding in JavaScript: A Comprehensive Developer's Guide | Medium
May 31, 2024 - Let’s get to the exciting part: how do you encode strings to Base64 in JavaScript? The language provides a built-in function btoa() that you can use to encode data.
🌐
Wikipedia
en.wikipedia.org › wiki › Base64
Base64 - Wikipedia
July 30, 2026 - The checksum is calculated on the input data before encoding; the checksum is then encoded with the same Base64 algorithm and, prefixed by the "=" symbol as the separator, appended to the encoded output data. The atob() and btoa() JavaScript methods, defined in the HTML5 draft specification, ...
🌐
Medium
medium.com › @jawaragordon › unleash-the-power-of-javascript-base-64-encoding-78c5258bc8ac
Unleash the Power of JavaScript Base 64 Encoding | by Jawara Gordon | Medium
January 7, 2023 - The base-64 encoded message is then saved in a URL hash/fragment which can then be copied and sent to another user. The recipient can see the “secret message” when this process is reversed in their browser. What makes this app special is the encoding process going on behind the scenes. The secret sauce in this recipe is a JavaScript function called “btoa().”