Skip to main content

How to Work with Blobs in JavaScript

In the previous guides, you learned about ArrayBuffer for working with raw binary data at the byte level and TextEncoder/TextDecoder for converting between binary and text. But there is another binary data type in JavaScript that serves a fundamentally different purpose: the Blob.

A Blob (Binary Large Object) represents a chunk of immutable raw data that typically comes from or goes to somewhere outside JavaScript: files selected by the user, images downloaded from a server, data generated for download, content pasted from the clipboard, or media streams captured from a camera. While ArrayBuffer is designed for low-level byte manipulation in memory, Blob is designed for transporting binary data between JavaScript and the outside world.

Blobs are everywhere in browser APIs. The File API returns Blobs. The Fetch API can send and receive Blobs. Canvas can export images as Blobs. The Clipboard API works with Blobs. Understanding how to create, transform, and use Blobs is essential for building applications that handle files, images, downloads, and binary data exchange.

What Is a Blob? (Immutable Raw Data)

A Blob is an immutable, opaque object that represents raw binary data with an associated MIME type. Let's break down what each of those characteristics means.

Immutable: Once created, a Blob's content cannot be changed. You cannot modify individual bytes. To "change" a Blob, you create a new one from the existing data (or portions of it) combined with new data.

Opaque: You cannot directly read a Blob's bytes through indexing or properties. To access the data inside a Blob, you must explicitly convert it using methods like arrayBuffer(), text(), stream(), or through a FileReader.

MIME type: Every Blob carries an optional type property that describes the format of its content (e.g., "image/png", "text/plain", "application/json"). This type is used by the browser when the Blob is downloaded, displayed, or sent over the network.

Blob vs. ArrayBuffer

These two binary types serve different purposes:

FeatureArrayBufferBlob
MutabilityMutable (via views)Immutable
Direct byte accessYes (via typed arrays/DataView)No (must convert first)
MIME typeNoYes (type property)
Size limitLimited by available memoryCan represent very large data
Primary useIn-memory byte manipulationFile I/O, downloads, uploads, transport
Can be sent via fetchYes (as body)Yes (as body)
Can create a URLNoYes (URL.createObjectURL)
Used by File APINoYes (File extends Blob)

Think of ArrayBuffer as a workbench where you directly manipulate bytes, and Blob as a sealed package labeled with its content type, ready to be sent, saved, or displayed.

The File Object Extends Blob

The File object (returned by <input type="file"> and drag-and-drop) is a subclass of Blob with additional properties like name, lastModified, and webkitRelativePath:

// <input type="file" id="fileInput">
let fileInput = document.getElementById("fileInput");

fileInput.addEventListener("change", (event) => {
let file = event.target.files[0];

console.log(file instanceof Blob); // true (File IS a Blob)
console.log(file instanceof File); // true

// Blob properties
console.log(file.size); // e.g., 524288 (bytes)
console.log(file.type); // e.g., "image/png"

// Additional File properties
console.log(file.name); // e.g., "photo.png"
console.log(file.lastModified); // e.g., 1710500000000 (timestamp)
});

Creating Blobs

The Blob constructor accepts two arguments: an array of data parts and an optional options object specifying the MIME type.

Constructor Syntax

let blob = new Blob(blobParts, options);
  • blobParts: An array of data pieces. Each piece can be a string, an ArrayBuffer, a typed array (Uint8Array, etc.), a DataView, or another Blob. The parts are concatenated in order.
  • options: An optional object with:
    • type: A string representing the MIME type (default: "")
    • endings: How to handle newline characters in strings ("transparent" or "native", default: "transparent")

Creating a Blob from Strings

// A simple text blob
let textBlob = new Blob(["Hello, World!"], { type: "text/plain" });
console.log(textBlob.size); // 13
console.log(textBlob.type); // "text/plain"

// Multiple string parts are concatenated
let multiPartBlob = new Blob(
["Hello, ", "World", "!"],
{ type: "text/plain" }
);
console.log(multiPartBlob.size); // 13 (same as above)

// HTML content
let htmlBlob = new Blob(
["<h1>Title</h1><p>Paragraph content</p>"],
{ type: "text/html" }
);

// JSON content
let data = { name: "Alice", age: 30, city: "Paris" };
let jsonBlob = new Blob(
[JSON.stringify(data, null, 2)],
{ type: "application/json" }
);

// CSV content
let csvContent = "Name,Age,City\nAlice,30,Paris\nBob,25,London\n";
let csvBlob = new Blob([csvContent], { type: "text/csv" });

Creating a Blob from Binary Data

// From a Uint8Array
let bytes = new Uint8Array([72, 101, 108, 108, 111]);
let binaryBlob = new Blob([bytes], { type: "application/octet-stream" });
console.log(binaryBlob.size); // 5

// From an ArrayBuffer
let buffer = new ArrayBuffer(8);
new Float64Array(buffer)[0] = 3.14159;
let bufferBlob = new Blob([buffer]);
console.log(bufferBlob.size); // 8

// Mixing types: strings, typed arrays, and other blobs
let header = new Uint8Array([0x89, 0x50, 0x4E, 0x47]); // PNG magic bytes
let body = new Blob(["image data here"]);
let footer = new Uint8Array([0x00, 0x00]);

let combinedBlob = new Blob([header, body, footer], { type: "image/png" });
console.log(combinedBlob.size); // 4 + 15 + 2 = 21

Creating a Blob from Other Blobs (Concatenation)

Since Blobs are immutable, combining or modifying them means creating new Blobs:

let part1 = new Blob(["First part. "]);
let part2 = new Blob(["Second part. "]);
let part3 = new Blob(["Third part."]);

// Concatenate blobs into a new blob
let combined = new Blob([part1, part2, part3], { type: "text/plain" });

// Verify
combined.text().then(text => {
console.log(text); // "First part. Second part. Third part."
});

Slicing a Blob

The slice method creates a new Blob containing a portion of the original. It does not copy the data. The new Blob references the same underlying data:

let blob = new Blob(["Hello, World!"], { type: "text/plain" });

// slice(start, end, contentType)
let hello = blob.slice(0, 5, "text/plain");
let world = blob.slice(7, 12, "text/plain");

hello.text().then(t => console.log(t)); // "Hello"
world.text().then(t => console.log(t)); // "World"

// Without specifying contentType, the slice has an empty type
let noType = blob.slice(0, 5);
console.log(noType.type); // "" (empty)

Slicing is particularly useful for chunked uploads where you need to send a large file in pieces:

async function uploadInChunks(blob, chunkSize = 1024 * 1024) {
let offset = 0;
let chunkIndex = 0;

while (offset < blob.size) {
let chunk = blob.slice(offset, offset + chunkSize);

await fetch("/api/upload", {
method: "POST",
headers: {
"Content-Range": `bytes ${offset}-${offset + chunk.size - 1}/${blob.size}`,
"X-Chunk-Index": chunkIndex.toString()
},
body: chunk
});

offset += chunkSize;
chunkIndex++;
console.log(`Uploaded ${Math.min(offset, blob.size)} / ${blob.size} bytes`);
}
}

Blob Properties

Blobs have only two properties, both read-only:

let blob = new Blob(["Hello, World!"], { type: "text/plain" });

console.log(blob.size); // 13 (size in bytes)
console.log(blob.type); // "text/plain" (MIME type)

// Empty blob
let emptyBlob = new Blob([]);
console.log(emptyBlob.size); // 0
console.log(emptyBlob.type); // "" (empty string)
info

The size property returns the size in bytes, not characters. For UTF-8 encoded strings, multi-byte characters take more than one byte:

let asciiBlob = new Blob(["Hello"]);
console.log(asciiBlob.size); // 5 (1 byte per char)

let emojiBlob = new Blob(["Hello 👋"]);
console.log(emojiBlob.size); // 10 (5 ASCII bytes + 1 space + 4 bytes for emoji)

let cyrillicBlob = new Blob(["Привет"]);
console.log(cyrillicBlob.size); // 12 (2 bytes per Cyrillic letter in UTF-8)

Blob URLs: URL.createObjectURL(blob)

One of the most powerful features of Blobs is the ability to create Blob URLs (also called Object URLs). A Blob URL is a temporary URL that points to the Blob's data in memory. This URL can be used anywhere a regular URL is accepted: as the src of an <img>, the href of an <a>, the source of a <video>, or in CSS url().

Creating a Blob URL

let blob = new Blob(["Hello, World!"], { type: "text/plain" });

let url = URL.createObjectURL(blob);
console.log(url); // "blob:http://localhost:3000/550e8400-e29b-41d4-a716-446655440000"

The URL has the format blob:<origin>/<uuid>. It is valid only within the current document and cannot be accessed from other pages or after the page is closed.

Triggering a File Download

The most common use of Blob URLs is creating downloadable files in the browser without a server:

function downloadBlob(blob, filename) {
let url = URL.createObjectURL(blob);

let a = document.createElement("a");
a.href = url;
a.download = filename; // Suggests a filename for the download
a.click();

// Clean up after a short delay
setTimeout(() => URL.revokeObjectURL(url), 1000);
}

// Download a text file
let textBlob = new Blob(["Hello, World!\nThis is line 2."], { type: "text/plain" });
downloadBlob(textBlob, "hello.txt");

// Download a JSON file
let data = { users: [{ name: "Alice" }, { name: "Bob" }] };
let jsonBlob = new Blob(
[JSON.stringify(data, null, 2)],
{ type: "application/json" }
);
downloadBlob(jsonBlob, "users.json");

// Download a CSV file
let csv = "Name,Email,Age\nAlice,alice@example.com,30\nBob,bob@example.com,25\n";
let csvBlob = new Blob([csv], { type: "text/csv" });
downloadBlob(csvBlob, "users.csv");

Displaying Images

// Create an image from canvas and display it
let canvas = document.getElementById("myCanvas");
canvas.toBlob((blob) => {
let url = URL.createObjectURL(blob);

let img = document.createElement("img");
img.src = url;
img.onload = () => {
// Revoke after the image loads to free memory
URL.revokeObjectURL(url);
};
document.body.append(img);
}, "image/png");

// Display a user-selected image
let fileInput = document.getElementById("imageInput");
fileInput.addEventListener("change", (event) => {
let file = event.target.files[0];
if (!file || !file.type.startsWith("image/")) return;

let url = URL.createObjectURL(file);
let img = document.getElementById("preview");
img.src = url;
img.onload = () => URL.revokeObjectURL(url);
});

Using Blob URLs in CSS

// Create a CSS file as a Blob and apply it
let css = `
body { background-color: #1a1a2e; color: #e0e0e0; }
h1 { color: #e94560; }
`;
let cssBlob = new Blob([css], { type: "text/css" });
let cssUrl = URL.createObjectURL(cssBlob);

let link = document.createElement("link");
link.rel = "stylesheet";
link.href = cssUrl;
document.head.append(link);

Revoking Blob URLs: Memory Management

Every Blob URL holds a reference to the Blob in memory, preventing it from being garbage collected. If you create many Blob URLs without revoking them, you will leak memory. Always revoke Blob URLs when they are no longer needed:

let url = URL.createObjectURL(blob);

// Use the URL...

// When done, revoke it to free memory
URL.revokeObjectURL(url);

// After revoking, the URL becomes invalid
// Trying to load it will fail (e.g., img shows broken image icon)
warning

Once you call URL.revokeObjectURL(url), the URL immediately becomes invalid. Any ongoing loads (images, stylesheets, iframes) that reference this URL will fail. Always revoke after the resource has been fully consumed.

// ❌ WRONG: Revoking too early
let url = URL.createObjectURL(imageBlob);
let img = document.createElement("img");
img.src = url;
URL.revokeObjectURL(url); // Image hasn't loaded yet! The load will fail!
document.body.append(img);

// ✅ CORRECT: Revoke after the image loads
let url2 = URL.createObjectURL(imageBlob);
let img2 = document.createElement("img");
img2.onload = () => URL.revokeObjectURL(url2); // Revoke after load completes
img2.onerror = () => URL.revokeObjectURL(url2); // Also revoke on error
img2.src = url2;
document.body.append(img2);

Blob URL vs. Data URL

There are two ways to represent binary data as a URL. Both work as src, href, etc., but they have significant differences:

FeatureBlob URLData URL (base64)
Formatblob:origin/uuiddata:type;base64,AAAB...
Size in memoryReference to the BlobThe entire data encoded in the string
TransmissionShort URL stringVery long string (33% larger than original)
Revocation neededYes (memory leak risk)No (garbage collected with the string)
PersistenceLost on page refreshCan be stored in localStorage, attributes
PerformanceBetter for large dataBetter for small data (< few KB)
Cross-pageSame origin onlyWorks anywhere (can be copied, stored)
// Blob URL: efficient, requires cleanup
let blobUrl = URL.createObjectURL(blob);
// "blob:http://localhost/550e8400-..."

// Data URL: self-contained, larger
let reader = new FileReader();
reader.onload = () => {
let dataUrl = reader.result;
// "data:text/plain;base64,SGVsbG8sIFdvcmxkIQ=="
};
reader.readAsDataURL(blob);

Blob to ArrayBuffer, Blob to Base64

Since Blobs are opaque, you need explicit conversion methods to access their contents in different formats.

blob.arrayBuffer(): Blob to ArrayBuffer

Returns a Promise that resolves with the Blob's contents as an ArrayBuffer. Use this when you need to process the raw bytes:

let blob = new Blob(["Hello, World!"], { type: "text/plain" });

let buffer = await blob.arrayBuffer();
console.log(buffer.byteLength); // 13

// Now you can use typed arrays to read the bytes
let bytes = new Uint8Array(buffer);
console.log(bytes); // Uint8Array [72, 101, 108, 108, 111, 44, 32, 87, 111, 114, 108, 100, 33]

blob.text(): Blob to String

Returns a Promise that resolves with the Blob's contents as a UTF-8 string. This is the simplest way to read text from a Blob:

let blob = new Blob(["Hello, World!"], { type: "text/plain" });

let text = await blob.text();
console.log(text); // "Hello, World!"

If the Blob contains data in a non-UTF-8 encoding, use arrayBuffer() and then TextDecoder with the appropriate encoding:

// Blob containing Windows-1251 encoded text
let win1251Blob = new Blob([new Uint8Array([207, 240, 232, 226, 229, 242])]);

// blob.text() assumes UTF-8 (wrong result)
let wrongText = await win1251Blob.text();
console.log(wrongText); // "Ïðèâåò" (garbled)

// Correct approach: decode with the right encoding
let buffer = await win1251Blob.arrayBuffer();
let decoder = new TextDecoder("windows-1251");
let correctText = decoder.decode(buffer);
console.log(correctText); // "Привет"

blob.bytes(): Blob to Uint8Array

A newer method that returns a Promise resolving to a Uint8Array directly, without going through ArrayBuffer:

let blob = new Blob(["Hello"]);

let bytes = await blob.bytes();
console.log(bytes); // Uint8Array [72, 101, 108, 108, 111]
console.log(bytes instanceof Uint8Array); // true
info

blob.bytes() is a relatively new addition. If you need to support older browsers, use blob.arrayBuffer() and then wrap it in a Uint8Array:

// Fallback for browsers without blob.bytes()
async function blobToBytes(blob) {
if (blob.bytes) {
return blob.bytes();
}
let buffer = await blob.arrayBuffer();
return new Uint8Array(buffer);
}

Blob to Base64 (Data URL)

There is no direct blob.toBase64() method. To get a base64-encoded Data URL, use FileReader:

function blobToBase64(blob) {
return new Promise((resolve, reject) => {
let reader = new FileReader();

reader.onload = () => resolve(reader.result);
reader.onerror = () => reject(reader.error);

reader.readAsDataURL(blob); // Result: "data:type;base64,..."
});
}

// Usage
let blob = new Blob(["Hello, World!"], { type: "text/plain" });
let dataUrl = await blobToBase64(blob);
console.log(dataUrl);
// "data:text/plain;base64,SGVsbG8sIFdvcmxkIQ=="

If you need just the raw base64 string (without the data: prefix):

async function blobToRawBase64(blob) {
let dataUrl = await blobToBase64(blob);
// Remove the "data:type;base64," prefix
return dataUrl.split(",")[1];
}

let base64 = await blobToRawBase64(blob);
console.log(base64); // "SGVsbG8sIFdvcmxkIQ=="

Base64 to Blob

Going the other direction (base64 string back to a Blob):

function base64ToBlob(base64, mimeType = "") {
let binaryString = atob(base64);
let bytes = new Uint8Array(binaryString.length);

for (let i = 0; i < binaryString.length; i++) {
bytes[i] = binaryString.charCodeAt(i);
}

return new Blob([bytes], { type: mimeType });
}

// Usage
let blob = base64ToBlob("SGVsbG8sIFdvcmxkIQ==", "text/plain");
let text = await blob.text();
console.log(text); // "Hello, World!"

// Useful for data URLs received from APIs
function dataUrlToBlob(dataUrl) {
let [header, base64] = dataUrl.split(",");
let mimeType = header.match(/data:([^;]+)/)?.[1] || "";
return base64ToBlob(base64, mimeType);
}

let imageBlob = dataUrlToBlob("data:image/png;base64,iVBORw0KGgo...");

ArrayBuffer to Blob

let buffer = new ArrayBuffer(4);
new Uint8Array(buffer).set([0x89, 0x50, 0x4E, 0x47]); // PNG header

let blob = new Blob([buffer], { type: "image/png" });
console.log(blob.size); // 4
console.log(blob.type); // "image/png"

Conversion Summary

                    ┌─────────────┐
.text() │ │ new Blob([string])
◄──────────── │ Blob │ ◄──────────────────
(Promise) │ │
│ │ new Blob([buffer])
.arrayBuffer() │ │ ◄──────────────────
◄──────────── │ │
(Promise) │ │ new Blob([uint8arr])
│ │ ◄──────────────────
.bytes() │ │
◄──────────── │ │ new Blob([blob1, blob2])
(Promise) │ │ ◄──────────────────
│ │
.stream() │ │ .slice(start, end)
◄──────────── │ │ ──────────────────►
(ReadableStream) │ │ (new Blob)
└─────────────┘
FileReader URL.createObjectURL()
◄──────────── ──────────────────►
(base64/text/ (blob: URL)
arraybuffer)

blob.stream(): Readable Streams

The stream() method returns a ReadableStream that lets you read the Blob's content in chunks. This is ideal for processing large Blobs without loading the entire content into memory at once.

Basic Usage

let blob = new Blob(["Hello, World! This is a longer piece of text for streaming."]);

let stream = blob.stream();
let reader = stream.getReader();

while (true) {
let { done, value } = await reader.read();

if (done) {
console.log("Stream complete");
break;
}

// value is a Uint8Array chunk
console.log("Chunk:", value);
console.log("Chunk size:", value.length, "bytes");
}

Processing a Large File in Chunks

async function processLargeBlob(blob) {
let stream = blob.stream();
let reader = stream.getReader();
let decoder = new TextDecoder();
let totalBytes = 0;
let lineCount = 0;

while (true) {
let { done, value } = await reader.read();
if (done) break;

totalBytes += value.length;
let text = decoder.decode(value, { stream: true });
lineCount += (text.match(/\n/g) || []).length;

let progress = ((totalBytes / blob.size) * 100).toFixed(1);
console.log(`Progress: ${progress}%`);
}

// Flush remaining decoded text
decoder.decode();

console.log(`Total: ${totalBytes} bytes, ${lineCount} lines`);
}

Streaming a Blob Through a Transform

You can pipe a Blob's stream through transform streams for on-the-fly processing:

// Decompress a gzipped blob
async function decompressBlob(compressedBlob) {
let stream = compressedBlob.stream();
let decompressedStream = stream.pipeThrough(new DecompressionStream("gzip"));

// Collect the decompressed data into a new Blob
let response = new Response(decompressedStream);
return response.blob();
}

// Compress a blob
async function compressBlob(blob) {
let stream = blob.stream();
let compressedStream = stream.pipeThrough(new CompressionStream("gzip"));

let response = new Response(compressedStream);
return response.blob();
}

// Usage
let original = new Blob(["Hello, World! ".repeat(1000)], { type: "text/plain" });
console.log("Original size:", original.size); // ~14000 bytes

let compressed = await compressBlob(original);
console.log("Compressed size:", compressed.size); // Much smaller

let decompressed = await decompressBlob(compressed);
let text = await decompressed.text();
console.log("Decompressed:", text.substring(0, 50)); // "Hello, World! Hello, World! ..."
console.log("Decompressed size:", decompressed.size); // Back to ~14000

Computing a Hash of a Large Blob

async function hashBlob(blob, algorithm = "SHA-256") {
let buffer = await blob.arrayBuffer();
let hashBuffer = await crypto.subtle.digest(algorithm, buffer);
let hashArray = new Uint8Array(hashBuffer);

return Array.from(hashArray)
.map(b => b.toString(16).padStart(2, "0"))
.join("");
}

// Usage
let blob = new Blob(["Hello, World!"]);
let hash = await hashBlob(blob);
console.log("SHA-256:", hash);
// "dffd6021bb2bd5b0af676290809ec3a53191dd81c7f70a4b28688a362182986f"

Practical Examples

Building a File Download Generator

class FileGenerator {
static text(content, filename) {
let blob = new Blob([content], { type: "text/plain" });
FileGenerator._download(blob, filename);
}

static json(data, filename) {
let content = JSON.stringify(data, null, 2);
let blob = new Blob([content], { type: "application/json" });
FileGenerator._download(blob, filename);
}

static csv(headers, rows, filename) {
let lines = [headers.join(",")];
for (let row of rows) {
let escapedRow = row.map(cell => {
let str = String(cell);
// Escape cells containing commas, quotes, or newlines
if (str.includes(",") || str.includes('"') || str.includes("\n")) {
return `"${str.replace(/"/g, '""')}"`;
}
return str;
});
lines.push(escapedRow.join(","));
}

// Add UTF-8 BOM for Excel compatibility
let bom = new Uint8Array([0xEF, 0xBB, 0xBF]);
let csvContent = lines.join("\n") + "\n";
let blob = new Blob([bom, csvContent], { type: "text/csv;charset=utf-8" });
FileGenerator._download(blob, filename);
}

static html(content, filename) {
let fullHtml = `<!DOCTYPE html>
<html><head><meta charset="utf-8"><title>${filename}</title></head>
<body>${content}</body></html>`;
let blob = new Blob([fullHtml], { type: "text/html" });
FileGenerator._download(blob, filename);
}

static _download(blob, filename) {
let url = URL.createObjectURL(blob);
let a = document.createElement("a");
a.href = url;
a.download = filename;
a.click();
setTimeout(() => URL.revokeObjectURL(url), 5000);
}
}

// Usage
FileGenerator.text("Hello, World!", "greeting.txt");

FileGenerator.json(
{ users: [{ name: "Alice" }, { name: "Bob" }] },
"users.json"
);

FileGenerator.csv(
["Name", "Email", "Age"],
[
["Alice", "alice@example.com", 30],
["Bob", "bob@example.com", 25],
["Charlie", "charlie@example.com", 35]
],
"contacts.csv"
);

Image Processing Pipeline

// Load image → process on canvas → export as Blob → display/download

async function processImage(file) {
// 1. Create a Blob URL and load the image
let url = URL.createObjectURL(file);
let img = await loadImage(url);
URL.revokeObjectURL(url);

// 2. Draw on canvas and apply processing
let canvas = document.createElement("canvas");
canvas.width = img.naturalWidth;
canvas.height = img.naturalHeight;
let ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0);

// Apply grayscale filter
let imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
let pixels = imageData.data;
for (let i = 0; i < pixels.length; i += 4) {
let gray = pixels[i] * 0.299 + pixels[i + 1] * 0.587 + pixels[i + 2] * 0.114;
pixels[i] = pixels[i + 1] = pixels[i + 2] = gray;
}
ctx.putImageData(imageData, 0, 0);

// 3. Export as Blob
let processedBlob = await new Promise(resolve => {
canvas.toBlob(resolve, "image/jpeg", 0.85);
});

return processedBlob;
}

function loadImage(src) {
return new Promise((resolve, reject) => {
let img = new Image();
img.onload = () => resolve(img);
img.onerror = reject;
img.src = src;
});
}

// Usage
let fileInput = document.getElementById("imageInput");
fileInput.addEventListener("change", async (event) => {
let file = event.target.files[0];
if (!file) return;

console.log(`Original: ${(file.size / 1024).toFixed(1)} KB`);

let processed = await processImage(file);
console.log(`Processed: ${(processed.size / 1024).toFixed(1)} KB`);

// Display the processed image
let img = document.createElement("img");
let url = URL.createObjectURL(processed);
img.src = url;
img.onload = () => URL.revokeObjectURL(url);
img.style.maxWidth = "100%";
document.body.append(img);
});

Clipboard Integration

// Copy an image Blob to the clipboard
async function copyImageToClipboard(blob) {
try {
await navigator.clipboard.write([
new ClipboardItem({
[blob.type]: blob
})
]);
console.log("Image copied to clipboard!");
} catch (error) {
console.error("Failed to copy:", error);
}
}

// Read an image from the clipboard
async function pasteImageFromClipboard() {
try {
let items = await navigator.clipboard.read();

for (let item of items) {
for (let type of item.types) {
if (type.startsWith("image/")) {
let blob = await item.getType(type);
console.log(`Pasted image: ${blob.type}, ${blob.size} bytes`);
return blob;
}
}
}

console.log("No image found in clipboard");
return null;
} catch (error) {
console.error("Failed to read clipboard:", error);
return null;
}
}

Sending Blobs with Fetch

// Upload a Blob directly
async function uploadBlob(blob, url) {
let response = await fetch(url, {
method: "POST",
headers: {
"Content-Type": blob.type
},
body: blob // Blob can be used directly as fetch body
});
return response.json();
}

// Upload as FormData (multipart)
async function uploadFile(blob, filename, url) {
let formData = new FormData();
formData.append("file", blob, filename);
formData.append("description", "Uploaded via JavaScript");

let response = await fetch(url, {
method: "POST",
body: formData // No Content-Type header needed: browser sets it with boundary
});
return response.json();
}

// Receive a Blob from fetch
async function downloadImage(url) {
let response = await fetch(url);
let blob = await response.blob(); // Read response as Blob

console.log(`Downloaded: ${blob.type}, ${blob.size} bytes`);
return blob;
}

Generating a Blob from Canvas with Options

// Export canvas as different formats and qualities
async function canvasToBlob(canvas, format = "image/png", quality = 0.92) {
return new Promise((resolve) => {
canvas.toBlob(resolve, format, quality);
});
}

// Compare different export formats
async function compareFormats(canvas) {
let png = await canvasToBlob(canvas, "image/png");
let jpegHigh = await canvasToBlob(canvas, "image/jpeg", 0.95);
let jpegMed = await canvasToBlob(canvas, "image/jpeg", 0.75);
let jpegLow = await canvasToBlob(canvas, "image/jpeg", 0.50);
let webp = await canvasToBlob(canvas, "image/webp", 0.80);

console.log("PNG: ", (png.size / 1024).toFixed(1), "KB");
console.log("JPEG (95%): ", (jpegHigh.size / 1024).toFixed(1), "KB");
console.log("JPEG (75%): ", (jpegMed.size / 1024).toFixed(1), "KB");
console.log("JPEG (50%): ", (jpegLow.size / 1024).toFixed(1), "KB");
console.log("WebP (80%): ", (webp.size / 1024).toFixed(1), "KB");

return { png, jpegHigh, jpegMed, jpegLow, webp };
}

Summary

Blob is JavaScript's high-level interface for immutable binary data, designed for transporting data between your code and the outside world.

What a Blob Is:

  • Immutable raw data with an optional MIME type.
  • Cannot be read directly. Must be converted via text(), arrayBuffer(), bytes(), stream(), or FileReader.
  • The File object extends Blob, adding name and lastModified.

Creating Blobs:

  • new Blob([parts], { type }) creates a Blob from strings, ArrayBuffers, typed arrays, or other Blobs.
  • blob.slice(start, end, type) creates a new Blob from a portion of an existing one (no copy).
  • canvas.toBlob(callback, format, quality) exports canvas content as a Blob.

Blob URLs:

  • URL.createObjectURL(blob) creates a temporary URL (blob:origin/uuid) usable as src, href, etc.
  • URL.revokeObjectURL(url) frees the reference. Always revoke when done to prevent memory leaks.
  • Revoke after the resource is consumed (after onload, not before).

Converting Blob Contents:

  • blob.text() returns a Promise resolving to a UTF-8 string.
  • blob.arrayBuffer() returns a Promise resolving to an ArrayBuffer.
  • blob.bytes() returns a Promise resolving to a Uint8Array.
  • blob.stream() returns a ReadableStream for chunked processing.
  • Use FileReader.readAsDataURL(blob) for base64 Data URLs.

Common Use Cases:

  • File downloads: Create a Blob, generate a URL, trigger download via <a>.
  • File uploads: Send Blob directly as fetch body or via FormData.
  • Image previews: URL.createObjectURL(file) for instant <img> display.
  • Canvas export: canvas.toBlob() for processed image output.
  • Clipboard: ClipboardItem accepts and returns Blobs.
  • Chunked uploads: blob.slice() to split large files into uploadable pieces.
  • Compression: Pipe blob.stream() through CompressionStream/DecompressionStream.