318

I need an efficient (read native) way to convert an ArrayBuffer to a base64 string which needs to be used on a multipart post.

Yves M.
  • 29,855
  • 23
  • 108
  • 144
zaheer
  • 3,221
  • 2
  • 16
  • 4

19 Answers19

335
function _arrayBufferToBase64( buffer ) {
    var binary = '';
    var bytes = new Uint8Array( buffer );
    var len = bytes.byteLength;
    for (var i = 0; i < len; i++) {
        binary += String.fromCharCode( bytes[ i ] );
    }
    return window.btoa( binary );
}

but, non-native implementations are faster e.g. https://gist.github.com/958841 see http://jsperf.com/encoding-xhr-image-data/6

Updated benchmarks: https://jsben.ch/wnaZC

Emmanuel
  • 4,933
  • 5
  • 46
  • 71
mobz
  • 3,407
  • 1
  • 12
  • 2
  • 17
    I tried the non-native implementation from the link and it took 1min and half to convert a 1M size buffer while the loop code above only took 1sec. – cshu Jun 28 '13 at 18:12
  • 4
    I like the simplicity of this approach, but all that string concatenation can be costly. It looks like building an array of the characters and `join()`ing them at the end is significantly faster on Firefox, IE, and Safari (but quite a lot slower on Chrome): http://jsperf.com/tobase64-implementations – JLRishe May 14 '14 at 09:30
  • I am using this function for array buffer to base64 conversion but I am not able to get back the array buffer. I have wrriten a _base64ToArrayBuffer() function here: http://codeshare.io/PT4pb but that gives me an error as: `Failed to execute 'atob' on 'Window': The string to be decoded is not correctly encoded.` – bawejakunal Jul 11 '15 at 13:27
  • this is not worked this JSZip. i find another way https://github.com/michael/github/issues/137 – RouR Jul 23 '15 at 17:09
  • 1
    I am trying 50mb pdf file upload using angualrjs and webapi2. I am using above line code , after upload file, the page got crashed and hanged . Below line of code ,I was used but getting null value in webapi method. "var base64String = btoa(String.fromCharCode.apply(null, new Uint8Array(arrayBuffer)));" please suggest any idea ... – prabhakaran S Nov 14 '16 at 09:14
  • 1
    This is not binary safe. Does anyone know about a binary safe option? – hg. Nov 27 '16 at 11:37
  • 1
    Not safe. See @chemoish answer – Kugel Aug 24 '17 at 04:56
  • Unfortunately won't work in node.js. Because of node.js' Utf-8 string encoding `String.fromCharCode()` behave different for byte values >= 0x80. – Joe Oct 07 '17 at 17:49
  • @bawejakunal Seems to work find for me: https://jsfiddle.net/5cf7bpdu/ Open devtools console and you'll see it's working. – David Callanan Feb 19 '20 at 10:13
  • 4
    I'm wondering why everyone is avoiding the native buffer `toString('base64')` method. – João Eduardo Feb 20 '20 at 13:56
  • 16
    @JoãoEduardoSoareseSilva because not everyone is using Node - Node's `Buffer` doesn't exist in the browser. – Andrew Apr 05 '21 at 23:03
  • @Andrew After I got better context to this question I learned that, I provided my own answer for Node users on this question, the issue is that this question is so popular that it overshadows the results for people that are using Node. – João Eduardo Apr 06 '21 at 04:49
  • I tried with "_arrayBufferToBase64" method and first time its uploading fine for 600MB and again i tried to upload 150MB file, browser is throwing "out of memory" page. Please let me now how can we avoid this or is there any limit to size or above method is storing in browser cache. – AMDI Feb 23 '22 at 00:35
  • Hello from 2022 in Firefox. So it seems like Alex and @cuixiping's solutions are the fastest and most preferred as of 2022? – Xunnamius May 26 '22 at 09:38
  • btoa converts array to string before converting it to base64 – Nikolay Makhonin Jun 08 '22 at 06:54
178

This works fine for me:

var base64String = btoa(String.fromCharCode.apply(null, new Uint8Array(arrayBuffer)));

In ES6, the syntax is a little simpler:

const base64String = btoa(String.fromCharCode(...new Uint8Array(arrayBuffer)));

As pointed out in the comments, this method may result in a runtime error in some browsers when the ArrayBuffer is large. The exact size limit is implementation dependent in any case.

GOTO 0
  • 42,323
  • 22
  • 125
  • 158
  • 60
    I like this method better for conciseness, but get a "maximum call stack size exceeded error". The loop technique above gets around that. – Jason Sep 07 '12 at 12:26
  • 15
    I'm also getting a stack size error, so I used mobz's answer and it worked great. – David Jones Sep 03 '13 at 01:34
  • 26
    It didn't work for large buffers. Slight modification to make it work: `btoa([].reduce.call(new Uint8Array(bufferArray),function(p,c){return p+String.fromCharCode(c)},''))` – laggingreflex Nov 12 '15 at 22:06
  • 2
    I am trying 50mb pdf file upload using angualrjs and webapi2. I am using above line code , after upload file, the page got crashed and hanged . Below line of code ,I was used but getting null value in webapi method. "var base64String = btoa(String.fromCharCode.apply(null, new Uint8Array(arrayBuffer)));" please suggest any idea ... – prabhakaran S Nov 14 '16 at 11:53
  • could you add the ultimate markup/image string to your answer as well by any chance? I have BMP images stored in SQL that I'm trying to display but this code isn't working: 'data:image/bmp;base64,' + btoa(String.fromCharCode.apply(null, new Uint8Array(arrayBuffer))); – BelgoCanadian Mar 23 '17 at 15:48
  • @BelgoCanadian I don't see what's wrong with that expression. See [here](http://stackoverflow.com/questions/1207190/embedding-base64-images) for your question. If this doesn't help then you should ask a new question and provide some more information. – GOTO 0 Mar 23 '17 at 17:25
  • Not safe. See @chemoish answer – Kugel Aug 24 '17 at 04:57
  • 9
    @Kugel `btoa` is safe for characters in the code range 0-255, as this is here the case (Think about the 8 in `Uint8Array`). – GOTO 0 Aug 24 '17 at 06:59
  • 1
    how do you reverse this back to bytes array? – Ganesh umashankar Sep 29 '20 at 13:21
  • 2
    FWIW, on Chrome 104 using `...` is much slower than using `apply`. – JP Sugarbroad Aug 19 '22 at 06:40
  • a very good answer in many cases (small buffers like crypto keys, signatures...) – Supersharp May 11 '23 at 14:27
59

For those who like it short, here's an other one using Array.reduce which will not cause stack overflow:

var base64 = btoa(
  new Uint8Array(arrayBuffer)
    .reduce((data, byte) => data + String.fromCharCode(byte), '')
);
gkunz
  • 1,375
  • 10
  • 7
44

There is another asynchronous way use Blob and FileReader.

I didn't test the performance. But it is a different way of thinking.

function arrayBufferToBase64( buffer, callback ) {
    var blob = new Blob([buffer],{type:'application/octet-binary'});
    var reader = new FileReader();
    reader.onload = function(evt){
        var dataurl = evt.target.result;
        callback(dataurl.substr(dataurl.indexOf(',')+1));
    };
    reader.readAsDataURL(blob);
}

//example:
var buf = new Uint8Array([11,22,33]);
arrayBufferToBase64(buf, console.log.bind(console)); //"CxYh"
cuixiping
  • 24,167
  • 8
  • 82
  • 93
  • Use `dataurl.split(',', 2)[1]`instead of `dataurl.substr(dataurl.indexOf(',')+1)`. – Carter Medlin Oct 31 '18 at 21:40
  • 3
    This doesn't seem to be guaranteed to work. According to https://w3c.github.io/FileAPI/#issue-f80bda5b `readAsDataURL` could theoretically return a percent encoded dataURI (And it seems it is actually the case in [jsdom](https://github.com/jsdom/jsdom/issues/2269)) – T S Aug 22 '19 at 19:00
  • @CarterMedlin Why would `split` be better than `substring`? – T S Aug 22 '19 at 19:02
  • split is shorter. but dataurl may contains one or more commas(,), split is not safe. – cuixiping Aug 28 '19 at 09:55
36

This example uses the built-in FileReader readDataURL() to do the conversion to base64 encoding. Data URLs are structured data:[<mediatype>][;base64],<data>, so we split that url at the comma and return only the base64 encoded characters.

const blob = new Blob([array]);        
const reader = new FileReader();

reader.onload = (event) => {
  const dataUrl = event.target.result;
  const [_, base64] = dataUrl.split(','); 
  // do something with base64
};
   
reader.readAsDataURL(blob);

Or as a promisified utility:

async function encode(array) {
  return new Promise((resolve) => {
    const blob = new Blob([array]);
    const reader = new FileReader();
    
    reader.onload = (event) => {
      const dataUrl = event.target.result;
      const [_, base64] = dataUrl.split(',');
      
      resolve(base64);
    };
    
    reader.readAsDataURL(blob);
  });
}

const encoded = await encode(typedArray);
Eric Dobbs
  • 3,844
  • 3
  • 19
  • 16
Alex
  • 388
  • 3
  • 4
  • 4
    Add some explanation to your answer please. What does this code mean? – Oscar Oct 11 '19 at 10:38
  • Similar [helpful MDN example](https://developer.mozilla.org/en-US/docs/Web/API/Blob#Extracting_data_from_a_Blob). – spenceryue Dec 16 '19 at 11:53
  • 1
    this is by far the fastest approach - tens of times faster than the other ones in my limited testing – jitin Dec 28 '19 at 15:55
  • i wish i'd found this solution like 8 hours again. my day would not have been wasted ;( thank you – Kaiser Shahid Jan 20 '20 at 16:02
  • 5
    I think you also need to remove the DataURL header (`data:*/*;base64,`) to obtain just the Base64 string. See [MDN docs](https://developer.mozilla.org/en-US/docs/Web/API/FileReader/readAsDataURL) – Larry K Feb 23 '21 at 11:17
  • How do you convert the base64 back to the array (after using this method to encode to base64) ? – Metric Rat Mar 06 '23 at 10:41
  • Ah, may have found the answer here: https://stackoverflow.com/a/54123275/2030549 – Metric Rat Mar 06 '23 at 10:48
36

The OP did not specify the Running Environment but if you are using Node.JS there is a very simple way to do such thing.

According with the official Node.JS docs https://nodejs.org/api/buffer.html#buffer_buffers_and_character_encodings

// This step is only necessary if you don't already have a Buffer Object
const buffer = Buffer.from(yourArrayBuffer);

const base64String = buffer.toString('base64');

Also, If you are running under Angular for example, the Buffer Class will also be made available in a Browser Environment.

Lee Taylor
  • 7,761
  • 16
  • 33
  • 49
João Eduardo
  • 738
  • 7
  • 21
  • Your answer only applies to NodeJS and will not work in the browser. – jvatic Mar 09 '20 at 18:50
  • 5
    @jvatic I see, the OP did not clearly specify the Running Environment, so my answer is not incorrect, he only tagged `Javascript`. So I updated my answer to make it more concise. I think this is an important answer because I was searching how to do this and could not get to the best answer to the problem. – João Eduardo Mar 10 '20 at 17:50
  • I came to realize recently that the Question date precedes NodeJS itself, which is more reason that the Mods should add an addendum to the question, because these days most people are looking for a solution on nodeJS and get mislead by the popularity of the old answers. – João Eduardo Nov 24 '22 at 08:39
23

I used this and works for me.

function arrayBufferToBase64( buffer ) {
    var binary = '';
    var bytes = new Uint8Array( buffer );
    var len = bytes.byteLength;
    for (var i = 0; i < len; i++) {
        binary += String.fromCharCode( bytes[ i ] );
    }
    return window.btoa( binary );
}



function base64ToArrayBuffer(base64) {
    var binary_string =  window.atob(base64);
    var len = binary_string.length;
    var bytes = new Uint8Array( len );
    for (var i = 0; i < len; i++)        {
        bytes[i] = binary_string.charCodeAt(i);
    }
    return bytes.buffer;
}
Elias Vargas
  • 1,126
  • 1
  • 18
  • 32
18

My recommendation for this is to NOT use native btoa strategies—as they don't correctly encode all ArrayBuffer's…

rewrite the DOMs atob() and btoa()

Since DOMStrings are 16-bit-encoded strings, in most browsers calling window.btoa on a Unicode string will cause a Character Out Of Range exception if a character exceeds the range of a 8-bit ASCII-encoded character.

While I have never encountered this exact error, I have found that many of the ArrayBuffer's I have tried to encode have encoded incorrectly.

I would either use MDN recommendation or gist.

Thilo
  • 257,207
  • 101
  • 511
  • 656
chemoish
  • 1,210
  • 2
  • 13
  • 23
  • `btoa` not works on String, but OP is asking `ArrayBuffer`. – tsh Jun 17 '17 at 03:18
  • 3
    Very much this, so many snippets here that recommend the wrong thing! I've seen this error multiple times, where people blindly use atob and btoa. – Kugel Aug 24 '17 at 04:53
  • 14
    All array buffers should be encoded fine using the strategies in other answers, atob/btoa is only a problem for text that contains characters greater than 0xFF (which byte arrays by definition do not). The MDN warning doesn't apply because when using the strategy in the other answers you are guaranteed to have a string that only consists of ASCII characters as any value from a Uint8Array is guaranteed to be between 0 and 255 which means String.fromCharCode is guaranteed to return a character that is not out of range. – Jamesernator Apr 29 '19 at 06:44
  • This is the correct answer when btoa or Buffer are not available (react-native) – cancerbero May 14 '21 at 18:42
16

Below are 2 simple functions for converting Uint8Array to Base64 String and back again

arrayToBase64String(a) {
    return btoa(String.fromCharCode(...a));
}

base64StringToArray(s) {
    let asciiString = atob(s);
    return new Uint8Array([...asciiString].map(char => char.charCodeAt(0)));
}
Steve Dixon
  • 323
  • 2
  • 2
5

If you're okay with adding a library, base64-arraybuffer:

yarn add base64-arraybuffer

then:

  • encode(buffer) - Encodes ArrayBuffer into base64 string
  • decode(str) - Decodes base64 string to ArrayBuffer
Hendy Irawan
  • 20,498
  • 11
  • 103
  • 114
3

This worked for me:

Buffer.from(myArrayBuffer).toString("base64");
1
ABtoB64(ab) {
    return new Promise(res => {
        const fr = new FileReader();
        fr.onload = ({target: {result: s}}) => res(s.slice(s.indexOf(';base64,') + 8));
        fr.readAsDataURL(new Blob([ab]));
    });
}

asynchronous method using file reader.

Ali
  • 21,572
  • 15
  • 83
  • 95
0

You can derive a normal array from the ArrayBuffer by using Array.prototype.slice. Use a function like Array.prototype.map to convert bytes in to characters and join them together to forma string.

function arrayBufferToBase64(ab){

    var dView = new Uint8Array(ab);   //Get a byte view        

    var arr = Array.prototype.slice.call(dView); //Create a normal array        

    var arr1 = arr.map(function(item){        
      return String.fromCharCode(item);    //Convert
    });

    return window.btoa(arr1.join(''));   //Form a string

}

This method is faster since there are no string concatenations running in it.

Charlie
  • 22,886
  • 11
  • 59
  • 90
0

In the Browser suggested solutions with btoa seem fine. But in Node.js btoa is deprecated

It is recommended to use buffer.toString(encoding)

like

const myString = buffer.toString("base64")

Laurenz Honauer
  • 254
  • 1
  • 12
0

i use TextDecode api to convert it to normal text and then convert it to Base64

const uint =  new Uint8Array([ 73, 32, 108, 111, 118, 101, 32, 121, 111, 117 ]).buffer
const decoder = new TextDecoder()
const decodedText = decoder.decode(uint)
const base64Code = btoa(decodedText)
Rman__
  • 135
  • 1
  • 12
0
 var uint8Array = new Uint8Array(BytesArray);

 var base64Str = btoa(String.fromCharCode(...uint8Array));

 Or,

 var base64Str = btoa(uint8Array.reduce((x, y) => x + String.fromCharCode(y), ''));
Md Shahriar
  • 2,072
  • 22
  • 11
-1

Use uint8-to-b64 package to do encoding/decoding in browser and Node.js

-4

By my side, using Chrome navigator, I had to use DataView() to read an arrayBuffer

function _arrayBufferToBase64( tabU8A ) {
var binary = '';
let lecteur_de_donnees = new DataView(tabU8A);
var len = lecteur_de_donnees.byteLength;
var chaine = '';
var pos1;
for (var i = 0; i < len; i++) {
    binary += String.fromCharCode( lecteur_de_donnees.getUint8( i ) );
}
chaine = window.btoa( binary )
return chaine;}
-6
function _arrayBufferToBase64(uarr) {
    var strings = [], chunksize = 0xffff;
    var len = uarr.length;

    for (var i = 0; i * chunksize < len; i++){
        strings.push(String.fromCharCode.apply(null, uarr.subarray(i * chunksize, (i + 1) * chunksize)));
    }

    return strings.join("");
}

This is better, if you use JSZip for unpack archive from string

RouR
  • 6,136
  • 3
  • 34
  • 25