file-saver vs downloadjs vs js-file-download
Client-Side File Download Strategies in Modern Web Apps
file-saverdownloadjsjs-file-downloadSimilar Packages:

Client-Side File Download Strategies in Modern Web Apps

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.

Npm Package Weekly Downloads Trend

3 Years

Github Stars Ranking

Stat Detail

Package
Downloads
Stars
Size
Issues
Publish
License
file-saver7,438,56521,989-2146 years agoMIT
downloadjs02,329-5010 years agoMIT
js-file-download0919-116 years agoMIT

Client-Side File Downloads: downloadjs vs file-saver vs js-file-download

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.

📦 Basic Usage: Functions vs Classes vs Helpers

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');

🍎 iOS Safari and Mobile Edge Cases

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

🧹 Memory Management and Object URLs

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');
}

📝 Handling Different Data Types

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

🛠️ Dependencies and Bundle Impact

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.

🌱 When Not to Use These

These libraries are for client-side generated content or proxying blobs. Do not use them if:

  • You are downloading massive files (GBs) – let the server handle the stream and use a direct link.
  • You need download progress bars – these libraries trigger the download, but tracking the progress of the browser's native download manager is not possible via JS. You would need to fetch the file as a blob first (consuming RAM) to track progress, which defeats the purpose for large files.
// 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';

📌 Summary Table

Featuredownloadjsfile-saverjs-file-download
Primary InputBlob, Data URL, StringBlobString (converts to Blob)
API StyleSingle Function download()Function saveAs()Function fileDownload()
iOS Safari SupportGoodExcellent (Legacy focus)Standard
Auto Cleanup✅ Yes✅ Yes✅ Yes
Best ForGeneral purpose, Data URLsEnterprise, Max CompatibilityText/JSON/CSV Exports
DependenciesNoneNone (Polyfill optional for IE)None

💡 Final Recommendation

Think about your data source and your audience.

  • Building a data dashboard with CSV/JSON exports? Go with js-file-download. It cuts the boilerplate of creating Blobs for text data and keeps your component code clean.
  • Supporting a wide range of devices including older iPads or corporate legacy browsers? Stick with file-saver. It is the battle-tested standard with the most extensive edge-case handling for mobile quirks.
  • Want a simple, no-nonsense utility for mixed content (images, blobs, data URLs) without extra fluff? Choose 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.

How to Choose: file-saver vs downloadjs vs js-file-download

  • file-saver:

    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.

  • 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.

  • js-file-download:

    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.

README for file-saver

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

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.

Supported Browsers

BrowserConstructs asFilenamesMax Blob SizeDependencies
Firefox 20+BlobYes800 MiBNone
Firefox < 20data: URINon/aBlob.js
ChromeBlobYes2GBNone
Chrome for AndroidBlobYesRAM/5None
EdgeBlobYes?None
IE 10+BlobYes600 MiBNone
Opera 15+BlobYes500 MiBNone
Opera < 15data: URINon/aBlob.js
Safari 6.1+*BlobNo?None
Safari < 6data: URINon/aBlob.js
Safari 10.1+  Blob        Yes        n/a          None

Feature detection is possible:

try {
    var isFileSaverSupported = !!new Blob;
} catch (e) {}

IE < 10

It is possible to save text files in IE < 10 without Flash-based polyfills. See ChenWenBrian and koffsyrup's saveTextAs() for more details.

Safari 6.1+

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.

iOS

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.

Syntax

Import saveAs() from file-saver

import { 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.

Examples

Saving text using require()

var FileSaver = require('file-saver');
var blob = new Blob(["Hello, world!"], {type: "text/plain;charset=utf-8"});
FileSaver.saveAs(blob, "hello world.txt");

Saving text

var blob = new Blob(["Hello, world!"], {type: "text/plain;charset=utf-8"});
FileSaver.saveAs(blob, "hello world.txt");

Saving URLs

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.

Saving a canvas

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.

Saving File

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);

Tracking image

Installation

# 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