downloadjs, file-saver, and js-file-download are lightweight utilities designed to trigger file downloads directly in the browser without requiring a server round-trip for the final delivery. They solve the common problem of saving Blobs, Data URLs, or text strings as files on a user's device, handling cross-browser inconsistencies like the lack of the download attribute on anchor tags in older Safari versions or the need for specific MIME type handling. While they share the same core goal, they differ significantly in their API design, dependency requirements, and how they handle edge cases like iOS Safari limitations or Blob cleanup.
Triggering a file download from the browser used to be a headache. You had to deal with missing download attributes in Safari, weird MIME type behaviors in Internet Explorer, and the hassle of cleaning up Object URLs. The packages downloadjs, file-saver, and js-file-download all solve this, but they take different approaches to the API and edge cases. Let's look at how they handle real-world scenarios.
The most immediate difference is how you call these libraries. Do you want a simple function, a class instance, or a specialized helper?
downloadjs offers a single, standalone function. You don't need to import a class or instantiate anything. You just call it with your data, filename, and MIME type.
import download from 'downloadjs';
// Download a Blob
const blob = new Blob(['Hello World'], { type: 'text/plain' });
download(blob, 'greeting.txt', 'text/plain');
// Download from a Data URL
download('data:text/plain;charset=utf-8,Hello%20World', 'greeting.txt');
file-saver uses a named export function saveAs. It is the most widely recognized API in the ecosystem. It focuses heavily on the Blob object as the primary input.
import { saveAs } from 'file-saver';
// Download a Blob
const blob = new Blob(['Hello World'], { type: 'text/plain' });
saveAs(blob, 'greeting.txt');
// Note: MIME type is derived from the Blob itself, not passed separately
js-file-download is specialized for text-based content. It accepts a string directly, removing the need for you to manually create a Blob object first. This reduces boilerplate for common text/JSON exports.
import fileDownload from 'js-file-download';
// Download a raw string (library creates the Blob internally)
fileDownload('Hello World', 'greeting.txt');
// Download JSON data
const data = { id: 1, name: 'Test' };
fileDownload(JSON.stringify(data), 'data.json', 'application/json');
Mobile browsers, especially Safari on iOS, have historically been strict about triggering downloads. They often ignore the download attribute on anchor tags and may try to display the file instead of saving it.
file-saver has the most robust history of handling these specific mobile quirks. It includes logic to detect iOS Safari and attempts to force the download dialog by leveraging specific navigation tricks if the standard Blob approach fails. It is often the default choice for apps that must support older mobile devices reliably.
// file-saver internally handles the iOS detection
// No extra code needed from you, but it adds logic to the bundle
import { saveAs } from 'file-saver';
saveAs(largeBlob, 'report.pdf');
// On iOS, this might trigger a redirect or a specific blob handling flow
downloadjs also attempts to handle cross-browser issues, including iOS. It uses a similar strategy of creating an object URL and simulating a click, but its implementation is more concise. In very recent iOS versions, the differences between downloadjs and file-saver have narrowed as Safari has improved its standards compliance.
// downloadjs handles the click simulation internally
import download from 'downloadjs';
download(pdfBlob, 'report.pdf', 'application/pdf');
js-file-download relies on standard Blob creation. While it works well on modern desktop and mobile browsers, it does not have the same depth of legacy mobile workarounds as file-saver. If your user base includes older iPads or iPhones, you might need to test this carefully.
// js-file-download creates a standard Blob and triggers download
import fileDownload from 'js-file-download';
fileDownload(contentString, 'report.txt');
// Relies on browser's native Blob URL handling
When you create a download from a Blob, the browser usually needs an Object URL (URL.createObjectURL). If you don't revoke these URLs, you can leak memory, especially in long-running single-page applications (SPAs) where users download many files.
downloadjs automatically handles the cleanup. After the download is triggered, it revokes the object URL. This is a huge win for developer experience because you don't have to remember to clean up.
// downloadjs: Automatic cleanup
import download from 'downloadjs';
function handleDownload() {
const blob = getLargeData();
// URL is created and revoked automatically inside this call
download(blob, 'large-file.zip');
}
file-saver also handles revocation internally in most modern browsers. It listens for the download completion or uses a timeout to revoke the URL, ensuring memory doesn't pile up. You generally don't need to manage this manually.
// file-saver: Automatic cleanup handled internally
import { saveAs } from 'file-saver';
function handleDownload() {
const blob = getLargeData();
saveAs(blob, 'large-file.zip');
// Library manages the revoke timing
}
js-file-download follows the same pattern. It creates the Object URL, triggers the click, and schedules the revocation. You get the same "set it and forget it" behavior.
// js-file-download: Automatic cleanup
import fileDownload from 'js-file-download';
function handleDownload() {
// Library handles Blob creation, URL creation, and revocation
fileDownload(getTextData(), 'output.txt');
}
Your choice might depend on what kind of data you are downloading. Are you dealing with binary files (PDFs, Images), or just text (CSV, JSON, Logs)?
downloadjs is extremely flexible. It accepts Blobs, Data URLs (strings starting with data:), and even regular URLs (though downloading from a remote URL usually requires CORS or server headers). It treats Data URLs as a first-class citizen.
// downloadjs: Great for Data URLs
const dataUrl = 'data:image/png;base64,iVBORw0KG...';
download(dataUrl, 'image.png');
file-saver is strictly Blob-focused. If you have a Data URL or a raw string, you must convert it to a Blob yourself before passing it to saveAs. This adds a small step but enforces a clear separation of concerns.
// file-saver: Requires manual Blob conversion for strings
const str = 'Hello World';
const blob = new Blob([str], { type: 'text/plain' });
saveAs(blob, 'file.txt');
js-file-download is the specialist for strings. If you are building a "Export to CSV" or "Download JSON" button, this library saves you the two lines of code needed to make a Blob. It assumes your input is content, not a binary asset.
// js-file-download: Best for dynamic text generation
const csvContent = rows.map(r => r.join(',')).join('\n');
fileDownload(csvContent, 'export.csv', 'text/csv');
// No need to new Blob() manually
In modern development, we try to avoid heavy dependencies for simple tasks.
downloadjs has zero dependencies. It is a single function that does the job. This makes it very easy to audit and ensures it won't pull in unexpected polyfills.
file-saver is also lightweight but historically included checks for older browsers (like IE < 10) which might require a polyfill like blob-polyfill if you support those ancient environments. In modern stacks (Webpack/Vite with modern targets), this is rarely an issue, but it's worth noting.
js-file-download is minimal and focused. It doesn't try to be everything to everyone, which keeps its footprint small. It is a great choice if you only need text downloads and don't want the broader feature set of the others.
These libraries are for client-side generated content or proxying blobs. Do not use them if:
// BAD: Trying to download a 2GB file with these libraries
// This will crash the browser tab by filling up RAM
const hugeBlob = await fetch('/huge-file.zip').then(r => r.blob());
saveAs(hugeBlob, 'huge-file.zip');
// GOOD: Let the browser handle the stream
window.location.href = '/huge-file.zip';
| Feature | downloadjs | file-saver | js-file-download |
|---|---|---|---|
| Primary Input | Blob, Data URL, String | Blob | String (converts to Blob) |
| API Style | Single Function download() | Function saveAs() | Function fileDownload() |
| iOS Safari Support | Good | Excellent (Legacy focus) | Standard |
| Auto Cleanup | ✅ Yes | ✅ Yes | ✅ Yes |
| Best For | General purpose, Data URLs | Enterprise, Max Compatibility | Text/JSON/CSV Exports |
| Dependencies | None | None (Polyfill optional for IE) | None |
Think about your data source and your audience.
js-file-download. It cuts the boilerplate of creating Blobs for text data and keeps your component code clean.file-saver. It is the battle-tested standard with the most extensive edge-case handling for mobile quirks.downloadjs. Its ability to handle Data URLs directly and its zero-dependency nature make it a fantastic modern default.All three are mature and reliable. The decision comes down to whether you value the specialized string handling of js-file-download, the legacy robustness of file-saver, or the flexible simplicity of downloadjs.
Choose file-saver if you need the industry standard with the widest range of legacy browser support and explicit handling of iOS Safari quirks. It is the safest bet for enterprise applications where guaranteeing a download prompt on very old browsers or specific mobile devices is a critical requirement, despite requiring a polyfill for older IE versions.
Choose downloadjs if you want a zero-dependency, function-based solution that feels like a native browser extension. It is ideal for projects that prefer simple, imperative calls without managing class instances or polyfills, and it handles data URLs and Blobs with a single, consistent API signature.
Choose js-file-download if your primary use case involves downloading raw text, JSON, or CSV data generated dynamically in the browser. It simplifies the workflow by accepting strings directly and handling the Blob conversion internally, making it perfect for export features in data grids or reporting dashboards.
If you need to save really large files bigger than the blob's size limitation or don't have enough RAM, then have a look at the more advanced StreamSaver.js that can save data directly to the hard drive asynchronously with the power of the new streams API. That will have support for progress, cancelation and knowing when it's done writing
FileSaver.js is the solution to saving files on the client-side, and is perfect for web apps that generates files on the client, However if the file is coming from the server we recommend you to first try to use Content-Disposition attachment response header as it has more cross-browser compatiblity.
Looking for canvas.toBlob() for saving canvases? Check out
canvas-toBlob.js for a cross-browser implementation.
| Browser | Constructs as | Filenames | Max Blob Size | Dependencies |
|---|---|---|---|---|
| Firefox 20+ | Blob | Yes | 800 MiB | None |
| Firefox < 20 | data: URI | No | n/a | Blob.js |
| Chrome | Blob | Yes | 2GB | None |
| Chrome for Android | Blob | Yes | RAM/5 | None |
| Edge | Blob | Yes | ? | None |
| IE 10+ | Blob | Yes | 600 MiB | None |
| Opera 15+ | Blob | Yes | 500 MiB | None |
| Opera < 15 | data: URI | No | n/a | Blob.js |
| Safari 6.1+* | Blob | No | ? | None |
| Safari < 6 | data: URI | No | n/a | Blob.js |
| Safari 10.1+ | Blob | Yes | n/a | None |
Feature detection is possible:
try {
var isFileSaverSupported = !!new Blob;
} catch (e) {}
It is possible to save text files in IE < 10 without Flash-based polyfills.
See ChenWenBrian and koffsyrup's saveTextAs() for more details.
Blobs may be opened instead of saved sometimes—you may have to direct your Safari users to manually
press ⌘+S to save the file after it is opened. Using the application/octet-stream MIME type to force downloads can cause issues in Safari.
saveAs must be run within a user interaction event such as onTouchDown or onClick; setTimeout will prevent saveAs from triggering. Due to restrictions in iOS saveAs opens in a new window instead of downloading, if you want this fixed please tell Apple how this WebKit bug is affecting you.
saveAs() from file-saverimport { saveAs } from 'file-saver';
FileSaver saveAs(Blob/File/Url, optional DOMString filename, optional Object { autoBom })
Pass { autoBom: true } if you want FileSaver.js to automatically provide Unicode text encoding hints (see: byte order mark). Note that this is only done if your blob type has charset=utf-8 set.
require()var FileSaver = require('file-saver');
var blob = new Blob(["Hello, world!"], {type: "text/plain;charset=utf-8"});
FileSaver.saveAs(blob, "hello world.txt");
var blob = new Blob(["Hello, world!"], {type: "text/plain;charset=utf-8"});
FileSaver.saveAs(blob, "hello world.txt");
FileSaver.saveAs("https://httpbin.org/image", "image.jpg");
Using URLs within the same origin will just use a[download].
Otherwise, it will first check if it supports cors header with a synchronous head request.
If it does, it will download the data and save using blob URLs.
If not, it will try to download it using a[download].
The standard W3C File API Blob interface is not available in all browsers.
Blob.js is a cross-browser Blob implementation that solves this.
var canvas = document.getElementById("my-canvas");
canvas.toBlob(function(blob) {
saveAs(blob, "pretty image.png");
});
Note: The standard HTML5 canvas.toBlob() method is not available in all browsers.
canvas-toBlob.js is a cross-browser canvas.toBlob() that polyfills this.
You can save a File constructor without specifying a filename. If the file itself already contains a name, there is a hand full of ways to get a file instance (from storage, file input, new constructor, clipboard event). If you still want to change the name, then you can change it in the 2nd argument.
// Note: Ie and Edge don't support the new File constructor,
// so it's better to construct blobs and use saveAs(blob, filename)
var file = new File(["Hello, world!"], "hello world.txt", {type: "text/plain;charset=utf-8"});
FileSaver.saveAs(file);

# Basic Node.JS installation
npm install file-saver --save
bower install file-saver
Additionally, TypeScript definitions can be installed via:
# Additional typescript definitions
npm install @types/file-saver --save-dev