Create a client-side download with JavaScript

OpenJavaScript 0
Reading Time: 3 minutes 🕑

Last updated: September 27, 2022.

There are two options for creating a file download in frontend JavaScript.

The first is to create a data URI containing the data. This is a long string containing the data that is recognized as data to download by browsers.

A more versatile second solution is to store data in a Blob object and then use URL.createObjectURL() to create a pseudo-URL to the Blob in the browser session. A Blob can support large binary data of various types, including image, audio and video.

Table of contents

Creating a data URI for download

With the URI method, you first create a link element (<a>).

The href of the element is going to be the data URI.

The URI begins with the data MIME type, followed by its encoding (usually utf-8).

In this case, we’ll set up a text file for download with the following:

'data:text/plain;charset=utf-8,'

Now, the result of wrapping the data in encodeURIComponent() is concatenated to form a long string that contains the information about the data and the data itself.

Finally, give the file a name by setting the download attribute on the link element, including a file extension.

Finally, you can start the download by appending the link element to the DOM and simulating a user click on it using the click() method. This will start the file download in the user’s browser.

const a = document.createElement('a');
a.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent("Please download me"));
a.setAttribute('download', 'text.txt');

a.style.display = 'none';
document.body.appendChild(a);

a.click();

document.body.removeChild(a);

Assuming you only want the download to run once, you should finish by removing the link element from the DOM.

To make this process easily repeatable, you can store it in a utility function:

function createURIDownload(MIME, data, filename) {
    const a = document.createElement('a');
    a.setAttribute('href', 'data:' + MIME + '; encoding:utf-8,' + encodeURIComponent(data));
    a.setAttribute('download', filename);

    a.style.display = 'none';
    document.body.appendChild(a);

    a.click();

    document.body.removeChild(a);
}

createURIDownload('text/plain', "I'm your data", "test.txt");

With this, all you need to do is pass in the relevant data as arguments when calling the function to create a new download.

Creating a Blob object for download

The second solution is to create a new Blob object that will store the file to be downloaded.

You create a new Blob object by calling new Blob() and passing in the file inside an array.

Now for the 'magic': passing in the result of this to URL.createObjectURL() creates a pseudo-URL to the file stored inside the Blob.

Next, you'll want to create a new link element (<a>) and set the pseudo-URL as its href attribute. This points to the file inside the Blob in memory. Also, you'll want to give the file a name by setting the download attribute.

Optionally, you can give a name to the file by setting the download attribute on the link element.

Finally, append the link element to the DOM and simulate a click on it using the click() method. This will start the download.

const data = "Please download me!";
const myBlob = new Blob([data], {type: 'text/plain'})
blobURL = URL.createObjectURL(myBlob);

const a = document.createElement('a');
a.setAttribute('href', blobURL);
a.setAttribute('download', 'blob-test.txt');

a.style.display = 'none';
document.body.appendChild(a);

a.click();

document.body.removeChild(a);
URL.revokeObjectURL(blobURL);

If the download should only occur once, you will want to remove the link element from the DOM after the simulated click.

You should also remove the pseudo-URL from browser memory by calling URL.revokeObjectURL() and passing in a reference to it. This is because the file in the Blob will continue existing in memory until the pseudo-URL is revoked.

To make this functionality reusable, you can store the code in a utility function, replacing the hard-coded data with parameters:

function startBlobDownload(MIME, file, filename) {
    const data = file;
    const myBlob = new Blob([data], {type: MIME})
    blobURL = URL.createObjectURL(myBlob);

    const a = document.createElement('a');
    a.setAttribute('href', blobURL);
    a.setAttribute('download', filename);

    a.style.display = 'none';
    document.body.appendChild(a);

    a.click();

    document.body.removeChild(a);
    URL.revokeObjectURL(blobURL);
}

startBlobDownload('text/plain', "Wassup", "wassup.txt");

And here is a more elaborate example of a CSV download:

// The data array
const theData = [
    ["Height", "Hair color", "Gender"],
    ["170", "Black", "Male"],
    ["180", "Blonde", "Female"],
    ["175", "Red", "Female"],
]

// Format it to CSV: join the contents inside the nested array into single string with comma separation between items and join the resulting strings with line break separation
let csvFormat = theData.map(row => row.join(",")).join("\n");

// Call the function, passing in a MIME of text/csv and setting the file extension to csv
startBlobDownload('text/csv', csvFormat, "test-spreadsheet.csv");

function startBlobDownload(MIME, file, filename) {
    const data = file;
    const myBlob = new Blob([data], {type: MIME})
    blobURL = URL.createObjectURL(myBlob);

    const a = document.createElement('a');
    a.setAttribute('href', blobURL);
    a.setAttribute('download', filename);

    a.style.display = 'none';
    document.body.appendChild(a);

    a.click();

    document.body.removeChild(a);
    URL.revokeObjectURL(blobURL);
}

You can read more about converting an array to CSV format here.

Related links