I got a webSocket comunication, I recieve base64 encoded string, convert it to uint8 and work on it, but now I need to send back, I got the uint8 array, and need to convert it to base64 string, so I can send it. How can I make this conversion?
-
2MDN implementations for Uint8array/ArrayBuffer <-> base64.JLRishe– JLRishe2014年08月22日 11:14:42 +00:00Commented Aug 22, 2014 at 11:14
-
The question "ArrayBuffer to base64 encoded string" contains a better solution which handles all characters. stackoverflow.com/questions/9267899/…Steve Hanov– Steve Hanov2021年01月04日 20:52:41 +00:00Commented Jan 4, 2021 at 20:52
-
This question is similar to: How can I convert an ArrayBuffer to a base64-encoded string?. If you believe it’s different, please edit the question, make it clear how it’s different and/or how the answers on that question are not helpful for your problem.double-beep– double-beep2024年09月03日 15:35:28 +00:00Commented Sep 3, 2024 at 15:35
17 Answers 17
If your data may contain multi-byte sequences (not a plain ASCII sequence) and your browser has TextDecoder, then you should use that to decode your data (specify the required encoding for the TextDecoder):
var u8 = new Uint8Array([65, 66, 67, 68]);
var decoder = new TextDecoder('utf8');
var b64encoded = btoa(decoder.decode(u8));
If you need to support browsers that do not have TextDecoder (currently just IE and Edge), then the best option is to use a TextDecoder polyfill.
If your data contains plain ASCII (not multibyte Unicode/UTF-8) then there is a simple alternative using String.fromCharCode
that should be fairly universally supported:
var ascii = new Uint8Array([65, 66, 67, 68]);
var b64encoded = btoa(String.fromCharCode.apply(null, ascii));
And to decode the base64 string back to a Uint8Array:
var u8_2 = new Uint8Array(atob(b64encoded).split("").map(function(c) {
return c.charCodeAt(0); }));
If you have very large array buffers then the apply may fail with Maximum call stack size exceeded
and you may need to chunk the buffer (based on the one posted by @RohitSengar). Again, note that this is only correct if your buffer only contains non-multibyte ASCII characters:
function Uint8ToString(u8a){
var CHUNK_SZ = 0x8000;
var c = [];
for (var i=0; i < u8a.length; i+=CHUNK_SZ) {
c.push(String.fromCharCode.apply(null, u8a.subarray(i, i+CHUNK_SZ)));
}
return c.join("");
}
// Usage
var u8 = new Uint8Array([65, 66, 67, 68]);
var b64encoded = btoa(Uint8ToString(u8));
19 Comments
btoa(String.fromCharCode.apply(null, myArray))
Uint8Array
. TextDecoder
is absolutely the wrong thing to use here, because if your Uint8Array
has bytes in range 128..255, text decoder will erroneously convert them into unicode characters, which will break base64 converter.If you are using Node.js then you can use this code to convert Uint8Array to base64
var u8 = new Uint8Array([65, 66, 67, 68]);
var b64 = Buffer.from(u8).toString('base64');
4 Comments
var u8 = new Uint8Array(Buffer.from(b64, 'base64'))
Buffer.from()
part, when you compare it with the manual JS implementation of base64 encoding.UInt8Array
there's a 99% chance that is not a node thing. Still, is way better to create a Buffer from the ArrayBuffer inside the UInt8Array instead of creating a competely new Buffer, which is really slow.Native browser solution (fast!)
To base64-encode a Uint8Array
with arbitrary data (not necessarily UTF-8) using native browser functionality:
// note: `buffer` arg can be an ArrayBuffer or a Uint8Array
async function bufferToBase64(buffer) {
// use a FileReader to generate a base64 data URI:
const base64url = await new Promise(r => {
const reader = new FileReader()
reader.onload = () => r(reader.result)
reader.readAsDataURL(new Blob([buffer]))
});
// remove the `data:...;base64,` part from the start
return base64url.slice(base64url.indexOf(',') + 1);
}
// example use:
await bufferToBase64(new Uint8Array([1,2,3,100,200]))
Because this is using native browser features, the performance is optimal. It can convert 250 MB per second on my computer (benchmark script), making it about 60x faster than the accepted answer.
12 Comments
base64url.substring(base64url.indexOf(',')+1)
All solutions already proposed have severe problems. Some solutions fail to work on large arrays, some provide wrong output, some throw an error on btoa call if an intermediate string contains multibyte characters, some consume more memory than needed.
So I implemented a direct conversion function which just works regardless of the input. It converts about 5 million bytes per second on my machine.
https://gist.github.com/enepomnyaschih/72c423f727d395eeaa09697058238727
/*
MIT License
Copyright (c) 2020 Egor Nepomnyaschih
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
/*
// This constant can also be computed with the following algorithm:
const base64abc = [],
A = "A".charCodeAt(0),
a = "a".charCodeAt(0),
n = "0".charCodeAt(0);
for (let i = 0; i < 26; ++i) {
base64abc.push(String.fromCharCode(A + i));
}
for (let i = 0; i < 26; ++i) {
base64abc.push(String.fromCharCode(a + i));
}
for (let i = 0; i < 10; ++i) {
base64abc.push(String.fromCharCode(n + i));
}
base64abc.push("+");
base64abc.push("/");
*/
const base64abc = [
"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M",
"N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z",
"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m",
"n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z",
"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "+", "/"
];
/*
// This constant can also be computed with the following algorithm:
const l = 256, base64codes = new Uint8Array(l);
for (let i = 0; i < l; ++i) {
base64codes[i] = 255; // invalid character
}
base64abc.forEach((char, index) => {
base64codes[char.charCodeAt(0)] = index;
});
base64codes["=".charCodeAt(0)] = 0; // ignored anyway, so we just need to prevent an error
*/
const base64codes = [
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 62, 255, 255, 255, 63,
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 255, 255, 255, 0, 255, 255,
255, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 255, 255, 255, 255, 255,
255, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51
];
function getBase64Code(charCode) {
if (charCode >= base64codes.length) {
throw new Error("Unable to parse base64 string.");
}
const code = base64codes[charCode];
if (code === 255) {
throw new Error("Unable to parse base64 string.");
}
return code;
}
export function bytesToBase64(bytes) {
let result = '', i, l = bytes.length;
for (i = 2; i < l; i += 3) {
result += base64abc[bytes[i - 2] >> 2];
result += base64abc[((bytes[i - 2] & 0x03) << 4) | (bytes[i - 1] >> 4)];
result += base64abc[((bytes[i - 1] & 0x0F) << 2) | (bytes[i] >> 6)];
result += base64abc[bytes[i] & 0x3F];
}
if (i === l + 1) { // 1 octet yet to write
result += base64abc[bytes[i - 2] >> 2];
result += base64abc[(bytes[i - 2] & 0x03) << 4];
result += "==";
}
if (i === l) { // 2 octets yet to write
result += base64abc[bytes[i - 2] >> 2];
result += base64abc[((bytes[i - 2] & 0x03) << 4) | (bytes[i - 1] >> 4)];
result += base64abc[(bytes[i - 1] & 0x0F) << 2];
result += "=";
}
return result;
}
export function base64ToBytes(str) {
if (str.length % 4 !== 0) {
throw new Error("Unable to parse base64 string.");
}
const index = str.indexOf("=");
if (index !== -1 && index < str.length - 2) {
throw new Error("Unable to parse base64 string.");
}
let missingOctets = str.endsWith("==") ? 2 : str.endsWith("=") ? 1 : 0,
n = str.length,
result = new Uint8Array(3 * (n / 4)),
buffer;
for (let i = 0, j = 0; i < n; i += 4, j += 3) {
buffer =
getBase64Code(str.charCodeAt(i)) << 18 |
getBase64Code(str.charCodeAt(i + 1)) << 12 |
getBase64Code(str.charCodeAt(i + 2)) << 6 |
getBase64Code(str.charCodeAt(i + 3));
result[j] = buffer >> 16;
result[j + 1] = (buffer >> 8) & 0xFF;
result[j + 2] = buffer & 0xFF;
}
return result.subarray(0, result.length - missingOctets);
}
export function base64encode(str, encoder = new TextEncoder()) {
return bytesToBase64(encoder.encode(str));
}
export function base64decode(str, decoder = new TextDecoder()) {
return decoder.decode(base64ToBytes(str));
}
6 Comments
"ABCDEFG..."
?TextDecoder
is for here? I too agree with @rominator007, it's not needed for a strict Uint8 -> Base64
conversion in my tests, but I want to make sure I'm not making a mistake by taking it out. Is this just a URL sanitization measure?TextDecoder
/TextEncoder
issue entirely. As far as I can tell, base64encode
and base64decode
are just convenience functions, and the only use I can think of for them is sending ASCII-safe SMTP messages (but why?) or interacting with extremely brittle legacy code that can only tolerate ASCII. Except for very narrow circumstances, and this goes for the currently accepted answer as well, converting from text strings to base64 and back again is a major code smell.Very simple solution and test for JavaScript!
ToBase64 = function (u8) {
return btoa(String.fromCharCode.apply(null, u8));
}
FromBase64 = function (str) {
return atob(str).split('').map(function (c) { return c.charCodeAt(0); });
}
var u8 = new Uint8Array(256);
for (var i = 0; i < 256; i++)
u8[i] = i;
var b64 = ToBase64(u8);
console.debug(b64);
console.debug(FromBase64(b64));
6 Comments
RangeError: Maximum call stack size exceeded
eJyLjjYy1CEXxeqM6h5Wui1giFzdhngMIEo3BS4fomE qpsWumMB4VPulQ==
+
with {space}
. This works once you replace it back (..E q..
-> ..E+q..
): FromBase64("eJyLjjYy1CEXxeqM6h5Wui1giFzdhngMIEo3BS4fomE+qpsWumMB4VPulQ==")
function Uint8ToBase64(u8Arr){
var CHUNK_SIZE = 0x8000; //arbitrary number
var index = 0;
var length = u8Arr.length;
var result = '';
var slice;
while (index < length) {
slice = u8Arr.subarray(index, Math.min(index + CHUNK_SIZE, length));
result += String.fromCharCode.apply(null, slice);
index += CHUNK_SIZE;
}
return btoa(result);
}
You can use this function if you have a very large Uint8Array. This is for Javascript, can be useful in case of FileReader readAsArrayBuffer.
5 Comments
String.fromCharCode.apply()
methods cannot reproduce UTF-8: UTF-8 characters may vary in length from one byte to four bytes, yet String.fromCharCode.apply()
examines a UInt8Array in segments of UInt8, so it erroneously assumes each character to be exactly one byte long and independent of the neighbouring ones. If the characters encoded in the input UInt8Array all happen to be in the ASCII (single-byte) range, it will work by chance, but it cannot reproduce full UTF-8. You need TextDecoder or a similar algorithm for that.Pure JS - no string middlestep (no btoa)
In below solution I omit conversion to string. IDEA is following:
- join 3 bytes (3 array elements) and you get 24-bits
- split 24bits to four 6-bit numbers (which take values from 0 to 63)
- use that numbers as index in base64 alphabet
- corner case: when input byte array
the length is not divided by 3 then add
=
or==
to result
Solution below works on 3-bytes chunks so it is good for large arrays. Similar solution to convert base64 to binary array (without atob
) is HERE
function bytesArrToBase64(arr) {
const abc = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; // base64 alphabet
const bin = n => n.toString(2).padStart(8,0); // convert num to 8-bit binary string
const l = arr.length
let result = '';
for(let i=0; i<=(l-1)/3; i++) {
let c1 = i*3+1>=l; // case when "=" is on end
let c2 = i*3+2>=l; // case when "=" is on end
let chunk = bin(arr[3*i]) + bin(c1? 0:arr[3*i+1]) + bin(c2? 0:arr[3*i+2]);
let r = chunk.match(/.{1,6}/g).map((x,j)=> j==3&&c2 ? '=' :(j==2&&c1 ? '=':abc[+('0b'+x)]));
result += r.join('');
}
return result;
}
// ----------
// TEST
// ----------
let test = "Alice's Adventure in Wondeland.";
let testBytes = [...test].map(c=> c.charCodeAt(0) );
console.log('test string:', test);
console.log('bytes:', JSON.stringify(testBytes));
console.log('btoa ', btoa(test));
console.log('bytesArrToBase64', bytesArrToBase64(testBytes));
Caution!
If you want to convert STRING (not bytes array) be aware that btoa
in general will fails on utf8 strings like btoa("💩")
(one character may be encoded by more than one byte). In this case you must first convert such string to bytes in proper way and then use above solution e.g. :
function bytesArrToBase64(arr) {
const abc = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; // base64 alphabet
const bin = n => n.toString(2).padStart(8,0); // convert num to 8-bit binary string
const l = arr.length
let result = '';
for(let i=0; i<=(l-1)/3; i++) {
let c1 = i*3+1>=l; // case when "=" is on end
let c2 = i*3+2>=l; // case when "=" is on end
let chunk = bin(arr[3*i]) + bin(c1? 0:arr[3*i+1]) + bin(c2? 0:arr[3*i+2]);
let r = chunk.match(/.{1,6}/g).map((x,j)=> j==3&&c2 ? '=' :(j==2&&c1 ? '=':abc[+('0b'+x)]));
result += r.join('');
}
return result;
}
// ----------
// TEST
// ----------
let test = "💩"; // base64: 8J+SqQ==
let testBytes = new TextEncoder().encode(test);
console.log('test string :', test);
console.log('bytes :', JSON.stringify([...testBytes]));
console.log('bytesArrToBase64 :', bytesArrToBase64(testBytes));
try {
console.log('test btoa :', btoa(test));
} catch (e) {
console.error('btoa fails during conversion!', e.message)
}
Snippets tested 2022年08月04日 on: chrome 103.0.5060.134 (arm64), safari 15.2, firefox 103.0.1 (64 bit), edge 103.0.1264.77 (arm64), and node-js v12.16.1
MDN's docs cover btoa well.
Because you already have binary data, you can convert your Uint8Array into an ASCII string and invoke btoa
on that string.
function encodeBase64Bytes(bytes: Uint8Array): string {
return btoa(
bytes.reduce((acc, current) => acc + String.fromCharCode(current), "")
);
}
Complexity with btoa
arises when you need to encode arbitrary JS strings, which may occupy more than a single byte, such as "👍"
. To handle arbitrary JS strings (which are UTF-16), you must first convert the string to a single byte representation. This is not applicable for this use case because you already have binary data.
The linked MDN documentation covers what that conversion looks like for encoding (and the reciprocal steps for decoding).
Comments
Use the following to convert uint8 array to base64 encoded string
function arrayBufferToBase64(buffer) {
var binary = '';
var bytes = [].slice.call(new Uint8Array(buffer));
bytes.forEach((b) => binary += String.fromCharCode(b));
return window.btoa(binary);
};
1 Comment
[].slice.call
.Since btoa
only works with strings, we can stringify the Uint8Array with String.fromCharCode
:
const toBase64 = uInt8Array => btoa(String.fromCharCode(...uInt8Array));
Comments
Native support for arrayBuffer to uint8array conversion has been recently added to the language.
Example usage:
const arr = new Uint8Array([72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100]);
console.log(arr.toBase64()); // => 'SGVsbG8gV29ybGQ='
See the MDN docs for more details on the available parameters, and for browser support details.
There are polyfils available, such as this polyfill from core-js.
Comments
A one liner for the browser:
Uint8Array --> Base64
btoa(String.fromCharCode.apply(null,new Uint8Array([1,2,3,255])))
Base64 --> Uint8Array
new Uint8Array([...atob('AQID/w==')].map(c=>c.charCodeAt()))
Several people were asking how it works, so I thought I'd finally break it down for everyone:
// Uint8Array --> Base64
//
// btoa(String.fromCharCode.apply(null,new Uint8Array([1,2,3,255])))
//
// breaks down to:
input = new Uint8Array([1,2,3,255])
// convert input to string (by applying the function 'fromCharCode' to each element of the input array)
inputString = String.fromCharCode.apply(null, input)
// now we have a string we can use the 'btoa' function to do all the hard work
base64String = btoa(inputString)
// Base64 --> Uint8Array
//
// new Uint8Array([...atob('AQID/w==')].map(c=>c.charCodeAt()))
//
// breaks down to:
// use the built in function to decode the base64
base64String = atob('AQID/w==')
// convert string to array of characters
base64Array = [...base64String]
// convert characters to bytes
bytes = base64Array.map(c=>c.charCodeAt())
// convert array of bytes to an Uint8Array
bytesArray = new Uint8Array(b)
5 Comments
c.charCodeAt(0)
should be instead c.charCodeAt(c)
.[190, 160, 173, 152, 53, 234]
c.charCodeAt(0)
is correct. You get a single character, say, "6", and get the charcode of its zeroth character, again 6, which gives 0x36. If you use c.charCodeAt(c), it will seem to work because "a".charCodeAt("a") is "a".charCodeAt(0), as "a" is not a number. But with 53 - that is, "5" - as Wolfgang Kuehn noticed, you will get "5".charCodeAt("5"), which is the fifth character of "5", which does not exist, and you will get NaN and break the code. charCodeAt() works because the missing argument is considered 0, but is still not correct.Here's a solution that works for strings longer than 255 elements, because it doesn't use the "splat operator" (Spread syntax):
function uint8ArrayFromBase64(s) {
// 1. Call atob()
var b = atob(s);
// 2. Construct Uint8Array from String
return Uint8Array.from(b, c => c.charCodeAt(0));
}
function uint8ArrayToBase64(arr) {
// 1. Preprocess Uint8Array into String
// (TODO: fix RAM usage from intermediate array creation)
var b = arr.map(c => String.fromCharCode(c)).join(String());
// 2. Call btoa()
return btoa(b);
}
Demo:
<form action="javascript:" onsubmit="(({target:form,submitter:{value:action}})=>{eval(action)(form)})(event)">
<input name="b64" value="AAAAB3NzaC1yc2E=">
<button type="submit" value="({b64,u8a})=>{u8a.value=`[${uint8ArrayFromBase64(b64.value)}]`;}">Convert to Uint8Array</button>
<br />
<input name="u8a" value="">
<button type="submit" value="({u8a,b64})=>{b64.value=(uint8ArrayToBase64(u8a.value.replace(/(?:^\[|\]$)/g, '').split(',')));}">Convert to Base64</button>
</form>
Comments
As of July 2025 Firefox and Safari support .toBase64()
on Uint8Array
.
The
toBase64()
method ofUint8Array
instances returns a base64-encoded string based on the data in this Uint8Array object.
const myBase64String = myUint8Array.toBase64();
To decode from base64 to a Uint8Array
use Uint8Array.fromBase64()
.
The
Uint8Array.fromBase64()
static method creates a newUint8Array
object from a base64-encoded string.
const myUint8Array = Uint8Array.fromBase64(myBase64String);
// This creates a Uint8Array from a string.
const bytes = new TextEncoder().encode("Hello, unicode world 😋");
// Encode to base64.
const base64 = bytes.toBase64();
console.log(base64);
// To decode from base64. First get a Uint8Array.
const decodedBytes = Uint8Array.fromBase64(base64);
// And now convert it back to a string.
const decoded = new TextDecoder().decode(decodedBytes);
console.log(decoded);
Polyfills are available at https://www.npmjs.com/package/es-arraybuffer-base64
Comments
Here is a JS Function to this:
This function is needed because Chrome doesn't accept a base64 encoded string as value for applicationServerKey in pushManager.subscribe yet https://bugs.chromium.org/p/chromium/issues/detail?id=802280
function urlBase64ToUint8Array(base64String) {
var padding = '='.repeat((4 - base64String.length % 4) % 4);
var base64 = (base64String + padding)
.replace(/\-/g, '+')
.replace(/_/g, '/');
var rawData = window.atob(base64);
var outputArray = new Uint8Array(rawData.length);
for (var i = 0; i < rawData.length; ++i) {
outputArray[i] = rawData.charCodeAt(i);
}
return outputArray;
}
1 Comment
If all you want is a JS implementation of a base64-encoder, so that you can send data back, you can try the btoa
function.
b64enc = btoa(uint);
A couple of quick notes on btoa - it's non-standard, so browsers aren't forced to support it.
However, most browsers do. The big ones, at least. atob
is the opposite conversion.
If you need a different implementation, or you find an edge-case where the browser has no idea what you're talking about, searching for a base64 encoder for JS wouldn't be too hard.
I think there are 3 of them hanging around on my company's website, for some reason...
3 Comments
npm install google-closure-library --save
require("google-closure-library");
goog.require('goog.crypt.base64');
var result =goog.crypt.base64.encodeByteArray(Uint8Array.of(1,83,27,99,102,66));
console.log(result);
$node index.js
would write AVMbY2Y= to the console.
1 Comment
-ve
voted answer is accepted rather than a highly +ve
one.