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 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 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 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.
========
The download() function is used to trigger a file download from JavaScript.
It specifies the contents and name of a new file placed in the browser's download directory. The input can be a URL, String, Blob, or Typed Array of data, or via a dataURL representing the file's data as base64 or url-encoded string. No matter the input format, download() saves a file using the specified file name and mime information in the same manner as a server using a Content-Disposition HTTP header.
npm install downloadjs
bower install downloadjs
require("downloadjs")(data, strFileName, strMimeType);
download function via <script> includedownload(data, strFileName, strMimeType);
require(['path/to/file'], function(download) {
download(data, strFileName, strMimeType);
});
download("hello world", "dlText.txt", "text/plain");
download("data:text/plain,hello%20world", "dlDataUrlText.txt", "text/plain");
download(new Blob(["hello world"]), "dlTextBlob.txt", "text/plain");
download("/robots.txt");
var str= "hello world", arr= new Uint8Array(str.length);
str.split("").forEach(function(a,b){
arr[b]=a.charCodeAt();
});
download( arr, "textUInt8Array.txt", "text/plain" );
download(document.documentElement.outerHTML, "dlHTML.html", "text/html");
download(new Blob(["hello world".bold()]), "dlHtmlBlob.html", "text/html");
(note that callback mode won't work on vanilla ajax or with binary files)
$.ajax({
url: "/download.html",
success: download.bind(true, "text/html", "dlAjaxCallback.html")
});
download("/diff6.png");
var x=new XMLHttpRequest();
x.open( "GET", "/diff6.png" , true);
x.responseType="blob";
x.onload= function(e){download(e.target.response, "awesomesauce.png", "image/png");};
x.send();
download.js works with a wide range of devices and browsers.
You can expect it to work for the vast majority of your users, with some common-sense limits:
Can I tell when a download is done/canceled? No.How can I style the temporary download link? Define CSS class styles for .download-js-link.What's up with Safari? I don't know either but pull requests that improve the situation are welcome.Why is my binary file corrupted? Likely: an incorrect MIME or using jQuery ajax, which has no bin support.How big of files work? Depends, try yourself: File Echo Demo... I do a 1GB dl routinely on a thinkpad...