Blob
ArrayBuffer and its views live in the ECMAScript standard. They’re part of the language itself, available anywhere JavaScript runs.
Blob is different. It comes from the browser, defined by the File API specification. Where an ArrayBuffer is raw bytes with no label, a Blob is raw bytes plus a label describing what kind of data they are.
That label is a type — usually a MIME-type like image/png or text/plain. The bytes themselves come from blobParts: a sequence of other Blob objects, strings, and BufferSource values (ArrayBuffer or a typed array). The browser flattens all of those parts into one continuous chunk of binary data.
The constructor takes those two things:
new Blob(blobParts, options);
blobParts— an array ofBlob/BufferSource/Stringvalues.options— an optional object:type— theBlobtype, usually a MIME-type, e.g.image/png.endings— whether to rewrite line endings to match the current OS (\r\non Windows,\nelsewhere). The default is"transparent"(leave the bytes untouched);"native"performs the rewrite.
A blob from a single string:
// create a Blob from a string
let blob = new Blob(["<p>Saved page</p>"], {type: 'text/html'});
// note: the first argument must be an array [...]
That array requirement catches people constantly. new Blob("hello") does not do what you want — a string is iterable, so the browser would treat each character as a separate part. Always pass ["hello"].
You can mix part types freely in the same array:
// create a Blob from a typed array and strings
let pixel = new Uint8Array([80, 105, 120, 101, 108]); // "Pixel" in binary form
let blob = new Blob([pixel, ' ', 'data'], {type: 'text/plain'});
Here the five bytes 80, 105, 120, 101, 108 are the ASCII codes for P, i, x, e, l. The browser concatenates the typed array, the space string, and the 'data' string into one blob whose contents read Pixel data.
Slicing a blob
You can pull a piece out of a blob with slice:
blob.slice([byteStart], [byteEnd], [contentType]);
byteStart— the first byte to include, default0.byteEnd— one past the last byte to include (exclusive), default the end of the blob.contentType— thetypeof the new blob, default the same as the source.
The arguments mirror array.slice, and negative numbers count from the end just like there.
Blob as URL
A blob can act as a URL for <a>, <img>, or any other tag that expects one, so the browser can display or link to its contents.
Because a blob carries a type, it also works cleanly for downloads and uploads — the type becomes the Content-Type header in network requests.
Start with a download link. Clicking it saves a blob generated on the fly, with Report ready. inside, as a file:
<!-- the download attribute forces a save instead of navigation -->
<a download="report.txt" href='#' id="link">Download</a>
<script>
let blob = new Blob(["Report ready."], {type: 'text/plain'});
link.href = URL.createObjectURL(blob);
</script>
You can also skip the HTML entirely: build the link in JavaScript and trigger a click with link.click(). The download starts on its own:
let link = document.createElement('a');
link.download = 'report.txt';
let blob = new Blob(['Report ready.'], {type: 'text/plain'});
link.href = URL.createObjectURL(blob);
link.click();
URL.revokeObjectURL(link.href);
URL.createObjectURL takes a Blob and hands back a unique URL of the form blob:<origin>/<uuid>.
link.href ends up looking like this:
blob:https://example.com/cdf8fd21-15c9-4015-b8e8-d5bac0ddf982
For every URL it creates this way, the browser keeps an internal URL → Blob mapping. That’s why the URL can be so short while still pointing at arbitrarily large binary data — the actual bytes live in the browser’s memory, and the URL is just a key into that table.
Such a URL (and any link using it) only works inside the current document while it stays open. Within that document it can reference the blob anywhere a URL is expected — <img>, <a>, and so on.
There’s a cost. As long as the mapping exists, the browser has to keep the blob in memory. It can’t be freed.
The mapping is cleared automatically when the document unloads, so the blob is released then. For a long-lived single-page app, though, “when the document unloads” might be hours away, or never during a session.
So creating an object URL pins that blob in memory, even after you’re done with it.
URL.revokeObjectURL(url) deletes the entry from the internal mapping. Once nothing else references the blob, it becomes eligible for garbage collection and the memory is reclaimed.
In the click-and-download example above, the blob only needs to exist long enough for that one download, so URL.revokeObjectURL(link.href) runs immediately after the click.
In the earlier HTML-link example, revoking would be a mistake. Removing the mapping makes the URL dead, so the link would stop working. There we leave the mapping in place and let the document unload clean it up.
Blob to base64
Instead of URL.createObjectURL, you can turn a blob into a base64-encoded string.
Base64 represents binary data using a small set of safe, printable ASCII characters. Every three bytes of input become four characters drawn from A–Z, a–z, 0–9, +, and /. The payoff: base64 slots into a “data URL”, which you can use anywhere a normal URL goes.
A data URL has the shape data:[<mediatype>][;base64],<data>. The whole resource is inlined into the string itself — no separate network request, no mapping to clean up.
Here’s a tiny image encoded that way:
<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAIAAADZF8uwAAAASUlEQVR42mOosXoLQW9f/sCFGOCK8KhDUYRLHQMQE1THAKHwq2OAs/CoY0Dm4FLHgGYyVnUMmM7EVMeA1c9o6hhwBSBRipDVAQCB9VTeCs4OmwAAAABJRU5ErkJggg==">
The browser decodes the string and renders the picture: <img src=“data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAIAAADZF8uwAAAASUlEQVR42mOosXoLQW9f/sCFGOCK8KhDUYRLHQMQE1THAKHwq2OAs/CoY0Dm4FLHgGYyVnUMmM7EVMeA1c9o6hhwBSBRipDVAQCB9VTeCs4OmwAAAABJRU5ErkJggg==”>
To convert a Blob into base64, use the built-in FileReader. It reads blob data in several formats, and one of them is a data URL. The next chapter covers FileReader in more depth.
Here’s the download demo again, now routed through base64:
let link = document.createElement('a');
link.download = 'report.txt';
let blob = new Blob(['Report ready.'], {type: 'text/plain'});
let reader = new FileReader();
reader.readAsDataURL(blob); // convert the blob to base64 and call onload
reader.onload = function() {
link.href = reader.result; // data url
link.click();
};
readAsDataURL is asynchronous, which is why the click waits inside the onload handler — reader.result isn’t ready until the read finishes.
Both approaches produce a usable URL from a blob. Usually URL.createObjectURL(blob) is the simpler, faster choice.
Image to blob
You can make a blob out of an image, a cropped piece of one, or even a rendered screenshot of the page. That’s convenient when you want to upload the result somewhere.
Image work goes through the <canvas> element:
- Draw an image (or part of it) onto a canvas with canvas.drawImage.
- Call canvas.toBlob(callback, format, quality), which builds a
Bloband passes it tocallbackwhen the encoding finishes.
The example below just copies the image straight across, but the same canvas step is where you’d crop, rotate, or otherwise transform the pixels before encoding:
// take any image
let img = document.querySelector('img');
// make a <canvas> of the same size
let canvas = document.createElement('canvas');
canvas.width = img.clientWidth;
canvas.height = img.clientHeight;
let context = canvas.getContext('2d');
// copy the image onto it (drawImage can also crop)
context.drawImage(img, 0, 0);
// we could context.rotate(), and do many other things on canvas
// toBlob is async — the callback runs when it's done
canvas.toBlob(function(blob) {
// blob ready, download it
let link = document.createElement('a');
link.download = 'example.png';
link.href = URL.createObjectURL(blob);
link.click();
// drop the internal reference so the browser can free the memory
URL.revokeObjectURL(link.href);
}, 'image/png');
If you’d rather use async/await than a callback, wrap toBlob in a promise:
let blob = await new Promise(resolve => canvasElem.toBlob(resolve, 'image/png'));
For a full-page screenshot, a library like html2canvas does the heavy lifting. It walks the DOM and paints it onto a <canvas>. From there you get a blob exactly as shown above.
From Blob to ArrayBuffer
The Blob constructor accepts almost anything as input, including any BufferSource. Going the other direction — from a blob down to raw bytes — is just as easy when you need low-level processing.
blob.arrayBuffer() returns a promise that resolves to an ArrayBuffer:
// get an ArrayBuffer from a blob
const bufferPromise = await blob.arrayBuffer();
// or, with .then
blob.arrayBuffer().then(buffer => /* process the ArrayBuffer */);
Once you have the ArrayBuffer, wrap a view over it — a Uint8Array, DataView, or similar — and read or write individual bytes as needed.
From Blob to stream
Reading a blob into an ArrayBuffer pulls the entire thing into memory at once. For a blob larger than about 2 GB that gets expensive fast, and on some platforms it may not fit at all. The alternative is to read the blob as a stream.
A stream is an object you consume piece by piece instead of all at once. Streams are a topic of their own — see the Streams API — but the idea is simple: pull one chunk, handle it, release it, repeat. Memory stays flat no matter how large the source is.
blob.stream() returns a ReadableStream. Reading from it yields the blob’s data one fragment at a time:
// get a ReadableStream from the blob
const readableStream = blob.stream();
const stream = readableStream.getReader();
while (true) {
// each iteration: value is the next blob fragment
let { done, value } = await stream.read();
if (done) {
// no more data in the stream
console.log('all blob processed.');
break;
}
// do something with the chunk we just read
console.log(value);
}
Summary
ArrayBuffer, Uint8Array, and the other BufferSource types are “binary data”. A Blob is “binary data with a type” — the same bytes, tagged with a MIME-type.
That tag is what makes blobs the natural currency for uploads and downloads, which are everywhere in browser code. Request APIs like XMLHttpRequest and fetch accept a Blob directly, right alongside the other binary types.
Converting between a blob and the low-level types is straightforward in both directions:
- Build a
Blobfrom a typed array (or strings, or other blobs) with thenew Blob(...)constructor. - Get an
ArrayBufferback withblob.arrayBuffer(), then lay a view over it for byte-level work.
When a blob is large enough that loading it whole would strain memory, read it as a stream instead. blob.stream() returns a ReadableStream that hands you the contents chunk by chunk, keeping memory usage flat.