downloadjs vs file-saver vs js-file-download
Client-Side File Download Strategies in Modern Web Apps
downloadjsfile-saverjs-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
downloadjs02,328-5110 years agoMIT
file-saver021,981-2146 years agoMIT
js-file-download0920-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: downloadjs vs file-saver vs js-file-download

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

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

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

download

========

Summary


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.

Getting and Using


Via NPM/Bower

npm install downloadjs
bower install downloadjs

require("downloadjs")(data, strFileName, strMimeType);

Simple global download function via <script> include

download(data, strFileName, strMimeType);

Included via AMD

require(['path/to/file'], function(download) {
    download(data, strFileName, strMimeType);
});

Parameters


  • data - The Blob, File, String, or dataURL containing the soon-to-be File's contents.
  • strFileName - The name of the file to be created. Note that older browsers (like FF3.5, Ch5) don't honor the file name you provide, instead they automatically name the downloaded file.
  • strMimeType - The MIME content-type of the file to download. While optional, it helps the browser present friendlier information about the download to the user, encouraging them to accept the download.

Example Usage


Plain Text

text string - live demo

download("hello world", "dlText.txt", "text/plain");

text dataURL - live demo

download("data:text/plain,hello%20world", "dlDataUrlText.txt", "text/plain");

text blob - live demo

download(new Blob(["hello world"]), "dlTextBlob.txt", "text/plain");

text url - live demo

download("/robots.txt");

text UInt8 Array - live demo

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

HTML

html string - live demo

download(document.documentElement.outerHTML, "dlHTML.html", "text/html");

html Blob - live demo

download(new Blob(["hello world".bold()]), "dlHtmlBlob.html", "text/html");

ajax callback - live demo

(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")
});

Binary Files

image from URL - live demo

download("/diff6.png");

Image via ajax for custom filename - live demo

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

Compatibility


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:

  • Devices without file systems like iPhone, iPad, Wii, et al. have nowhere to save the file to, sorry.
  • Android support starts at 4.2 for the built-in browser, though chrome 36+ and firefox 20+ on android 2.3+ work well.
  • Devices without Blob support won't be able to download Blobs or TypedArrays
  • Legacy devices (no a[download]) support can only download a few hundred kilobytes of data, and can't give the file a custom name.
  • Devices without window.URL support can only download a couple megabytes of data
  • IE versions of 9 and before are NOT supported because the don't support a[download] or dataURL frame locations.

FAQ


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

Change Log (v4.1)


  • 2008 :: landed a FF+Chrome compat way of downloading strings to local un-named files, upgraded to use a hidden frame and optional mime
  • 2012 :: added named files via a[download], msSaveBlob() for IE (10+) support, and window.URL support for larger+faster saves than dataURLs
  • 2014 :: added dataURL and Blob Input, bind-toggle arity, and legacy dataURL fallback was improved with force-download mime and base64 support
  • 2015 :: converted to amd/commonJS module with browser-friendly fallback
  • 2015 :: 4.1 added direct URL downloading via a single URL argument.
  • 2016 :: 4.2 added large dataURL support, a more semantic codebase, and hidden temp links
  • 2017 :: added support for empty dataURLs
  • 20XX :: ???? Considering Zip, Tar, and other multi-file outputs, Blob.prototype.download option, and more, stay tuned folks.