These libraries address the challenge of triggering file downloads directly from the browser, a common requirement for exporting reports, saving user-generated content, or handling binary data. file-saver is the industry standard for saving Blob objects, relying on the browser's native saveAs behavior. downloadjs and js-file-download offer lightweight, function-based alternatives that often handle data URI conversion internally. blob-util differs slightly by providing a suite of utility functions to convert between Blobs, Arrays, Strings, and Data URIs, which is often a prerequisite step before downloading. The download package is distinct as it is designed primarily for Node.js environments to fetch and save files to disk, making it less relevant for pure client-side browser architectures unless used in server-side rendering contexts.
Generating and downloading files directly in the browser is a frequent requirement in modern web applications, whether you are exporting user data to CSV, saving a generated PDF report, or allowing users to keep their work locally. While the HTML5 <a download> attribute handles simple cases, complex scenarios involving binary data, dynamic generation, and legacy browser support require specialized tools. The packages blob-util, download, downloadjs, file-saver, and js-file-download each tackle this problem from different angles.
It is critical to distinguish between client-side and server-side intent immediately. The download package is built for Node.js to fetch files to a server disk, whereas the other four are designed for browser environments. Among the browser tools, file-saver is the robust standard, downloadjs and js-file-download offer lightweight simplicity, and blob-util serves as a data preparation utility.
The first architectural decision is understanding what problem you are actually solving. Are you trying to trigger the browser's save dialog, or are you struggling to convert your data into a format the browser can download?
file-saver, downloadjs, and js-file-download focus on the trigger. They take data that is already ready (usually a Blob) and force the browser to open the "Save As" dialog. They handle the creation of temporary object URLs and the cleanup of memory.
blob-util focuses on transformation. It does not trigger downloads itself. Instead, it provides reliable methods to convert data between formats (e.g., Base64 to Blob, ArrayBuffer to String). You often use blob-util before calling one of the other libraries.
download focuses on server-side retrieval. It fetches a remote URL and pipes it to a file stream on the server. It is not used to trigger a browser download from client-side JavaScript.
// blob-util: Transforming data (Preparation)
import { base64ToBlob } from 'blob-util';
const base64Data = 'data:image/png;base64,iVBORw0KGgoAAAANS...';
const blob = await base64ToBlob(base64Data, 'image/png');
// Now 'blob' is ready for a downloader library
// file-saver: Triggering the download (Action)
import { saveAs } from 'file-saver';
saveAs(blob, 'image.png');
// downloadjs: Alternative Trigger (Action)
import download from 'downloadjs';
download(blob, 'image.png', 'image/png');
When architecting for enterprise environments, you often cannot assume users have the latest version of Chrome or Firefox. The handling of Object URLs and the download attribute varies significantly across older browsers, particularly Internet Explorer 10 and 11.
file-saver is the gold standard for compatibility. It includes specific polyfills and logic to detect Internet Explorer. If the native saveAs method is unavailable, it falls back to using msSaveBlob or msSaveOrOpenBlob, ensuring your application works even on legacy corporate machines.
downloadjs and js-file-download rely more heavily on standard browser behaviors. While they work perfectly in modern evergreen browsers, they may fail silently or require additional polyfills in older environments. They assume the browser can handle URL.createObjectURL and the <a download> attribute natively.
// file-saver: Handles IE10/11 internally
import { saveAs } from 'file-saver';
// Works in Chrome, Firefox, Safari, AND Internet Explorer
const blob = new Blob(['Hello World'], { type: 'text/plain;charset=utf-8' });
saveAs(blob, 'hello.txt');
// Internally checks: if (navigator.msSaveOrOpenBlob) { ... }
// downloadjs: Relies on modern standards
import download from 'downloadjs';
// May require polyfills for IE if createObjectURL is missing
download('Hello World', 'hello.txt', 'text/plain');
Real-world data rarely arrives as a perfect Blob object. You might have a JSON object, a raw string, a Data URI, or an ArrayBuffer. Different libraries accept different input types, which affects how much preprocessing code you need to write.
downloadjs is highly flexible regarding input types. It accepts strings, Blobs, Arrays, and Data URIs directly. It performs internal checks to determine the content type, reducing the boilerplate code you need to write.
js-file-download is similar but slightly more opinionated, often expecting strings or Blobs. It is very effective for text-based downloads like CSVs or logs.
file-saver is strictly typed towards Blob objects. If you have a string or a JSON object, you must manually convert it to a Blob before passing it to saveAs. This adds a step but ensures explicit control over MIME types.
blob-util shines here by providing the missing conversion steps for file-saver.
// downloadjs: Accepts raw strings and auto-detects
import download from 'downloadjs';
// Pass a string directly; library handles Blob creation internally
download('ID,Name,Value\n1,Item A,100', 'report.csv', 'text/csv');
// file-saver: Requires explicit Blob creation
import { saveAs } from 'file-saver';
const csvContent = 'ID,Name,Value\n1,Item A,100';
// Must manually create Blob with correct MIME type
const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
saveAs(blob, 'report.csv');
// blob-util: Helping with complex conversions
import { arrayBufferToBlob } from 'blob-util';
// If you have an ArrayBuffer from a WebSocket or Canvas
const buffer = await getBinaryData();
const blob = await arrayBufferToBlob(buffer, 'application/octet-stream');
saveAs(blob, 'binary.dat'); // Used with file-saver
Architectural decisions must account for the long-term viability of dependencies. Using unmaintained libraries introduces security risks and technical debt.
The download package (intended for Node.js) has historically faced maintenance issues and is often considered deprecated or unstable for modern Node.js workflows. Modern Node.js applications should prefer native fetch combined with fs.createWriteStream or libraries like node-fetch and stream-saver for better control and performance. It should not be used in new projects unless you have a specific legacy constraint.
file-saver remains actively maintained and is the reference implementation for the W3C FileSaver specification. It is safe for long-term use.
downloadjs and js-file-download are stable, lightweight utilities that rarely require updates because the underlying browser APIs they wrap are stable. They are safe to use.
blob-util is a utility library that is also stable, as the Blob API specifications are mature.
// AVOID: Using 'download' package in new Node.js projects
// Instead, use native Node.js streams:
import fs from 'fs';
import https from 'https';
const file = fs.createWriteStream('image.png');
https.get('https://example.com/image.png', (response) => {
response.pipe(file);
});
// PREFERRED: Using file-saver for client-side stability
import { saveAs } from 'file-saver';
// Reliable, standard-compliant, and maintained
When dealing with large files (e.g., exporting a 50MB dataset), memory management becomes critical. Creating Object URLs (URL.createObjectURL) ties up memory until explicitly revoked.
file-saver attempts to manage this automatically but gives developers the option to manually revoke URLs if needed for massive files. It is optimized to handle large Blobs without blocking the main thread excessively.
downloadjs and js-file-download create temporary anchor tags and Object URLs. In very long-running single-page applications (SPAs), if you trigger thousands of downloads in a session, you should be aware of potential memory leaks if the library does not revoke URLs aggressively. However, for typical user interactions (one or two exports per session), this is negligible.
blob-util operations are synchronous or promise-based conversions that happen in memory. Converting massive Base64 strings to Blobs can be CPU intensive and may cause temporary UI jank. It is recommended to perform heavy blob-util conversions inside a Web Worker if the data size exceeds a few megabytes.
// file-saver: Standard usage
import { saveAs } from 'file-saver';
const blob = generateLargeReport();
saveAs(blob, 'report.pdf');
// Library handles URL cleanup after download initiates
// blob-util: Potential performance bottleneck on main thread
import { base64ToBlob } from 'blob-util';
// For huge strings, move this to a Web Worker to prevent UI freeze
const hugeBase64 = getHugeString();
// const blob = await base64ToBlob(hugeBase64, 'application/pdf');
// ^ This blocks the thread if 'hugeBase64' is > 10MB
| Scenario | Recommended Package | Why? |
|---|---|---|
| Enterprise App with IE Support | file-saver | Only library with robust IE10/11 fallbacks. |
| Simple CSV/Text Export | downloadjs | Minimal code, accepts strings directly, lightweight. |
| Data Conversion (Base64 ↔ Blob) | blob-util | Specialized utilities for format transformation. |
| Server-Side File Fetching | Native Node Streams | download package is deprecated/unreliable; use native fs. |
| Minimalist Bundle | js-file-download | Tiny footprint for simple text/blob downloads. |
For most modern frontend architectures, file-saver remains the safest default choice due to its strict adherence to standards and legacy support. It removes guesswork from MIME types and browser quirks.
However, if you are building a modern, greenfield application where legacy browser support is not a requirement, downloadjs offers a cleaner, more ergonomic API that reduces boilerplate code. Use blob-util as a companion tool whenever your data source is not already a Blob (e.g., converting Canvas images or Base64 API responses).
Finally, strictly avoid the download package for client-side logic. Its domain is the server, and even there, native Node.js streams are now superior. By selecting the right tool for the specific layer (data transformation vs. download triggering), you ensure a responsive, compatible, and maintainable file handling system.
Choose blob-util when your primary challenge is data transformation rather than the download trigger itself. It is ideal if you need to convert complex data structures (like Base64 strings or ArrayBuffers) into Blob objects before passing them to a downloader. It acts as a reliable utility belt for preparing data formats but does not initiate the browser download dialog on its own.
Avoid download for client-side browser applications; it is built for Node.js environments to fetch remote URLs and write them to the server's file system. Only select this package if you are building a server-side script, a CLI tool, or an API endpoint that needs to retrieve and store files on the backend before serving them to the client.
Select downloadjs for a lightweight, zero-dependency solution when you need a simple function to trigger downloads from various data types (Blobs, Strings, Data URIs). It is an excellent choice for modern projects where you want to avoid the heavier API surface of file-saver and prefer a straightforward function call that handles MIME type detection automatically.
Opt for file-saver when you require maximum cross-browser compatibility and adherence to the W3C saveAs FileSaver specification. It is the safest choice for enterprise applications supporting legacy browsers (like older versions of Internet Explorer) or when you need robust handling of large Blob objects that might crash simpler implementations.
Pick js-file-download when you need a minimalistic wrapper that simplifies the creation of anchor tags for downloading text or binary data. It is suitable for projects that prioritize small bundle size and simple use cases, such as downloading CSV exports or text logs, without needing the extensive feature set of file-saver.
blob-util is a Blob library for busy people.
It offers a small set of cross-browser utilities for translating Blobs to and from different formats:
<img/> tagsIt's also a good pairing with the attachment API in PouchDB.
Note: this is a browser library. For Node.js, see Buffers.
Topics:
Via npm:
npm install blob-util
ES modules are supported:
import { canvasToBlob } from 'blob-util'
canvasToBlob(canvas, 'image/png').then(/* ... */)
Or as a script tag:
<script src="https://unpkg.com/blob-util/dist/blob-util.min.js"></script>
Then it's available as a global blobUtil object:
blobUtil.canvasToBlob(canvas, 'image/png').then(/* ... */)
As of v2.0.0, a built-in Promise polyfill is no longer provided. Assuming you provide a Promise
polyfill, the supported browsers are:
Blob or the older BlobBuilder; see caniuse for details.Blobs (binary large objects) are the modern way of working with binary data in the browser. The browser support is very good.
Once you have a Blob, you can make it available offline by storing it in IndexedDB, PouchDB, LocalForage, or other in-browser databases. So it's the perfect format for working with offline images, sound, and video.
A File is also a Blob. So if you have an <input type="file"> in your page, you can let your users upload any file and then work with it as a Blob.
Here's Kirby. He's a famous little Blob.
So let's fulfill his destiny, and convert him to a real Blob object.
var img = document.getElementById('kirby');
blobUtil.imgSrcToBlob(img.src).then(function (blob) {
// ladies and gents, we have a blob
}).catch(function (err) {
// image failed to load
});
(Don't worry, this won't download the image twice, because browsers are smart.)
Now that we have a Blob, we can convert it to a URL and use that as the source for another <img/> tag:
var blobURL = blobUtil.createObjectURL(blob);
var newImg = document.createElement('img');
newImg.src = blobURL;
document.body.appendChild(newImg);
So now we have two Kirbys - one with a normal URL, and the other with a blob URL. You can try this out yourself in the blob-util playground. Super fun!
▸ arrayBufferToBinaryString(buffer: ArrayBuffer): string
Convert an ArrayBuffer to a binary string.
Example:
var myString = blobUtil.arrayBufferToBinaryString(arrayBuff)
Parameters:
| Param | Type | Description |
|---|---|---|
| buffer | ArrayBuffer | array buffer |
Returns: string
binary string
▸ arrayBufferToBlob(buffer: ArrayBuffer, type?: string): Blob
Convert an ArrayBuffer to a Blob.
Example:
var blob = blobUtil.arrayBufferToBlob(arrayBuff, 'audio/mpeg');
Parameters:
| Param | Type | Description |
|---|---|---|
| buffer | ArrayBuffer | - |
Optional type | string | the content type (optional) |
Returns: Blob
Blob
▸ base64StringToBlob(base64: string, type?: string): Blob
Convert a base64-encoded string to a Blob.
Example:
var blob = blobUtil.base64StringToBlob(base64String);
Parameters:
| Param | Type | Description |
|---|---|---|
| base64 | string | base64-encoded string |
Optional type | string | the content type (optional) |
Returns: Blob
Blob
▸ binaryStringToArrayBuffer(binary: string): ArrayBuffer
Convert a binary string to an ArrayBuffer.
var myBuffer = blobUtil.binaryStringToArrayBuffer(binaryString)
Parameters:
| Param | Type | Description |
|---|---|---|
| binary | string | binary string |
Returns: ArrayBuffer
array buffer
▸ binaryStringToBlob(binary: string, type?: string): Blob
Convert a binary string to a Blob.
Example:
var blob = blobUtil.binaryStringToBlob(binaryString);
Parameters:
| Param | Type | Description |
|---|---|---|
| binary | string | binary string |
Optional type | string | the content type (optional) |
Returns: Blob
Blob
▸ blobToArrayBuffer(blob: Blob): Promise<ArrayBuffer>
Convert a Blob to an ArrayBuffer.
Example:
blobUtil.blobToArrayBuffer(blob).then(function (arrayBuff) {
// success
}).catch(function (err) {
// error
});
Parameters:
| Param | Type | Description |
|---|---|---|
| blob | Blob | - |
Returns: Promise<ArrayBuffer>
Promise that resolves with the ArrayBuffer
▸ blobToBase64String(blob: Blob): Promise<string>
Convert a Blob to a binary string.
Example:
blobUtil.blobToBase64String(blob).then(function (base64String) {
// success
}).catch(function (err) {
// error
});
Parameters:
| Param | Type | Description |
|---|---|---|
| blob | Blob | - |
Returns: Promise<string>
Promise that resolves with the binary string
▸ blobToBinaryString(blob: Blob): Promise<string>
Convert a Blob to a binary string.
Example:
blobUtil.blobToBinaryString(blob).then(function (binaryString) {
// success
}).catch(function (err) {
// error
});
Parameters:
| Param | Type | Description |
|---|---|---|
| blob | Blob | - |
Returns: Promise<string>
Promise that resolves with the binary string
▸ blobToDataURL(blob: Blob): Promise<string>
Convert a Blob to a data URL string (e.g. 'data:image/png;base64,iVBORw0KG...').
Example:
var dataURL = blobUtil.blobToDataURL(blob);
Parameters:
| Param | Type | Description |
|---|---|---|
| blob | Blob | - |
Returns: Promise<string>
Promise that resolves with the data URL string
▸ canvasToBlob(canvas: HTMLCanvasElement, type?: string, quality?: number): Promise<Blob>
Convert a canvas to a Blob.
Examples:
blobUtil.canvasToBlob(canvas).then(function (blob) {
// success
}).catch(function (err) {
// error
});
Most browsers support converting a canvas to both 'image/png' and 'image/jpeg'. You may also want to try 'image/webp', which will work in some browsers like Chrome (and in other browsers, will just fall back to 'image/png'):
blobUtil.canvasToBlob(canvas, 'image/webp').then(function (blob) {
// success
}).catch(function (err) {
// error
});
Parameters:
| Param | Type | Description |
|---|---|---|
| canvas | HTMLCanvasElement | HTMLCanvasElement |
Optional type | string | the content type (optional, defaults to 'image/png') |
Optional quality | number | a number between 0 and 1 indicating image quality if the requested type is 'image/jpeg' or 'image/webp' |
Returns: Promise<Blob>
Promise that resolves with the Blob
▸ createBlob(parts: Array<any>, properties?: * BlobPropertyBag | string*): Blob
Shim for new Blob() to support older browsers that use the deprecated BlobBuilder API.
Example:
var myBlob = blobUtil.createBlob(['hello world'], {type: 'text/plain'});
Parameters:
| Param | Type | Description |
|---|---|---|
| parts | Array<any> | content of the Blob |
Optional properties | BlobPropertyBag | string | usually {type: myContentType}, you can also pass a string for the content type |
Returns: Blob
Blob
▸ createObjectURL(blob: Blob): string
Shim for URL.createObjectURL() to support browsers that only have the prefixed webkitURL (e.g. Android <4.4).
Example:
var myUrl = blobUtil.createObjectURL(blob);
Parameters:
| Param | Type | Description |
|---|---|---|
| blob | Blob | - |
Returns: string
url
▸ dataURLToBlob(dataURL: string): Blob
Convert a data URL string (e.g. 'data:image/png;base64,iVBORw0KG...') to a Blob.
Example:
var blob = blobUtil.dataURLToBlob(dataURL);
Parameters:
| Param | Type | Description |
|---|---|---|
| dataURL | string | dataURL-encoded string |
Returns: Blob
Blob
▸ imgSrcToBlob(src: string, type?: string, crossOrigin?: string, quality?: number): Promise<Blob>
Convert an image's src URL to a Blob by loading the image and painting it to a canvas.
Note: this will coerce the image to the desired content type, and it will only paint the first frame of an animated GIF.
Examples:
blobUtil.imgSrcToBlob('http://mysite.com/img.png').then(function (blob) {
// success
}).catch(function (err) {
// error
});
blobUtil.imgSrcToBlob('http://some-other-site.com/img.jpg', 'image/jpeg',
'Anonymous', 1.0).then(function (blob) {
// success
}).catch(function (err) {
// error
});
Parameters:
| Param | Type | Description |
|---|---|---|
| src | string | image src |
Optional type | string | the content type (optional, defaults to 'image/png') |
Optional crossOrigin | string | for CORS-enabled images, set this to 'Anonymous' to avoid "tainted canvas" errors |
Optional quality | number | a number between 0 and 1 indicating image quality if the requested type is 'image/jpeg' or 'image/webp' |
Returns: Promise<Blob>
Promise that resolves with the Blob
▸ imgSrcToDataURL(src: string, type?: string, crossOrigin?: string, quality?: number): Promise<string>
Convert an image's src URL to a data URL by loading the image and painting it to a canvas.
Note: this will coerce the image to the desired content type, and it will only paint the first frame of an animated GIF.
Examples:
blobUtil.imgSrcToDataURL('http://mysite.com/img.png').then(function (dataURL) {
// success
}).catch(function (err) {
// error
});
blobUtil.imgSrcToDataURL('http://some-other-site.com/img.jpg', 'image/jpeg',
'Anonymous', 1.0).then(function (dataURL) {
// success
}).catch(function (err) {
// error
});
Parameters:
| Param | Type | Description |
|---|---|---|
| src | string | image src |
Optional type | string | the content type (optional, defaults to 'image/png') |
Optional crossOrigin | string | for CORS-enabled images, set this to 'Anonymous' to avoid "tainted canvas" errors |
Optional quality | number | a number between 0 and 1 indicating image quality if the requested type is 'image/jpeg' or 'image/webp' |
Returns: Promise<string>
Promise that resolves with the data URL string
▸ revokeObjectURL(url: string): void
Shim for URL.revokeObjectURL() to support browsers that only have the prefixed webkitURL (e.g. Android <4.4).
Example:
blobUtil.revokeObjectURL(myUrl);
Parameters:
| Param | Type | Description |
|---|---|---|
| url | string |
Returns: void
Thanks to the rest of the PouchDB team for figuring most of this crazy stuff out.
npm install
npm run build
npm install
Then to test in the browser using Saucelabs:
npm test
Or to test locally in your browser of choice:
npm run test-local
To build the API docs and insert them in the README:
npm run doc