[javascript] How to create a file in memory for user to download, but not through server?

Is there any way I can create a text file on the client side and prompt the user to download it, without any interaction with the server? I know I can't write directly to their machine (security and all), but can I create and prompt them to save it?

This question is related to javascript file web-applications client-side

The answer is


var element = document.createElement('a');
element.setAttribute('href', 'data:text/text;charset=utf-8,' +      encodeURI(data));
element.setAttribute('download', "fileName.txt");
element.click();

Based on @Rick answer which was really helpful.

You have to scape the string data if you want to share it this way:

$('a.download').attr('href', 'data:application/csv;charset=utf-8,'+ encodeURI(data));

` Sorry I can not comment on @Rick's answer due to my current low reputation in StackOverflow.

An edit suggestion was shared and rejected.


As of April 2014, FileSytem APIs may not be standardized in W3C. Anyone looking at the solution with blob should thread with caution, I guess.

HTML5 rocks heads up

W3C Mailing List on FileSytem API


If you just want to convert a string to be available for download you can try this using jQuery.

$('a.download').attr('href', 'data:application/csv;charset=utf-8,' + encodeURI(data));

Simple solution for HTML5 ready browsers...

_x000D_
_x000D_
function download(filename, text) {_x000D_
  var element = document.createElement('a');_x000D_
  element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text));_x000D_
  element.setAttribute('download', filename);_x000D_
_x000D_
  element.style.display = 'none';_x000D_
  document.body.appendChild(element);_x000D_
_x000D_
  element.click();_x000D_
_x000D_
  document.body.removeChild(element);_x000D_
}
_x000D_
form * {_x000D_
  display: block;_x000D_
  margin: 10px;_x000D_
}
_x000D_
<form onsubmit="download(this['name'].value, this['text'].value)">_x000D_
  <input type="text" name="name" value="test.txt">_x000D_
  <textarea name="text"></textarea>_x000D_
  <input type="submit" value="Download">_x000D_
</form>
_x000D_
_x000D_
_x000D_

Usage

download('test.txt', 'Hello world!');

This below function worked.

 private createDownloadableCsvFile(fileName, content) {
   let link = document.createElement("a");
   link.download = fileName;
   link.href = `data:application/octet-stream,${content}`;
   return link;
 }

All of the above example works just fine in chrome and IE, but fail in Firefox. Please do consider appending an anchor to the body and removing it after click.

var a = window.document.createElement('a');
a.href = window.URL.createObjectURL(new Blob(['Test,Text'], {type: 'text/csv'}));
a.download = 'test.csv';

// Append anchor to body.
document.body.appendChild(a);
a.click();

// Remove anchor from body
document.body.removeChild(a);

We can use the URL api, in particular URL.createObjectURL(), and the Blob api to encode and download pretty much anything.

If your download is small, this works fine:

_x000D_
_x000D_
document.body.innerHTML += 
`<a id="download" download="PATTERN.json" href="${URL.createObjectURL(new Blob([JSON.stringify("HELLO WORLD", null, 2)]))}"> Click me</a>`
download.click()
download.outerHTML = ""
_x000D_
_x000D_
_x000D_


If your download is huge, instead of using the DOM, a better way is to create a link element with the download parameters, and trigger a click.

Notice the link element isn't appended to the document but the click work anyway! This is possible to create a download of many hundreds of Mo this way.

_x000D_
_x000D_
const stack = {
 some: "stuffs",
 alot: "of them!"
}

BUTTONDOWNLOAD.onclick = (function(){
  let j = document.createElement("a")
  j.download = "stack_"+Date.now()+".json"
  j.href = URL.createObjectURL(new Blob([JSON.stringify(stack, null, 2)]))
  j.click()
})
_x000D_
<button id="BUTTONDOWNLOAD">DOWNLOAD!</button>
_x000D_
_x000D_
_x000D_


Bonus! Download any cyclic objects, avoid the errors:

TypeError: cyclic object value (Firefox) TypeError: Converting

circular structure to JSON (Chrome and Opera) TypeError: Circular

reference in value argument not supported (Edge)

Using https://github.com/douglascrockford/JSON-js/blob/master/cycle.js

On this example, downloading the document object as json.

_x000D_
_x000D_
/* JSON.decycle */
if(typeof JSON.decycle!=="function"){JSON.decycle=function decycle(object,replacer){"use strict";var objects=new WeakMap();return(function derez(value,path){var old_path;var nu;if(replacer!==undefined){value=replacer(value)}
if(typeof value==="object"&&value!==null&&!(value instanceof Boolean)&&!(value instanceof Date)&&!(value instanceof Number)&&!(value instanceof RegExp)&&!(value instanceof String)){old_path=objects.get(value);if(old_path!==undefined){return{$ref:old_path}}
objects.set(value,path);if(Array.isArray(value)){nu=[];value.forEach(function(element,i){nu[i]=derez(element,path+"["+i+"]")})}else{nu={};Object.keys(value).forEach(function(name){nu[name]=derez(value[name],path+"["+JSON.stringify(name)+"]")})}
return nu}
return value}(object,"$"))}}


document.body.innerHTML += 
`<a id="download" download="PATTERN.json" href="${URL.createObjectURL(new Blob([JSON.stringify(JSON.decycle(document), null, 2)]))}"></a>`
download.click()
_x000D_
_x000D_
_x000D_


The following method works in IE11+, Firefox 25+ and Chrome 30+:

<a id="export" class="myButton" download="" href="#">export</a>
<script>
    function createDownloadLink(anchorSelector, str, fileName){
        if(window.navigator.msSaveOrOpenBlob) {
            var fileData = [str];
            blobObject = new Blob(fileData);
            $(anchorSelector).click(function(){
                window.navigator.msSaveOrOpenBlob(blobObject, fileName);
            });
        } else {
            var url = "data:text/plain;charset=utf-8," + encodeURIComponent(str);
            $(anchorSelector).attr("download", fileName);               
            $(anchorSelector).attr("href", url);
        }
    }

    $(function () {
        var str = "hi,file";
        createDownloadLink("#export",str,"file.txt");
    });

</script>

See this in Action: http://jsfiddle.net/Kg7eA/

Firefox and Chrome support data URI for navigation, which allows us to create files by navigating to a data URI, while IE doesn't support it for security purposes.

On the other hand, IE has API for saving a blob, which can be used to create and download files.


All the above solutions didn't work in all browsers. Here is what finally works on IE 10+, Firefox and Chrome (and without jQuery or any other library):

save: function(filename, data) {
    var blob = new Blob([data], {type: 'text/csv'});
    if(window.navigator.msSaveOrOpenBlob) {
        window.navigator.msSaveBlob(blob, filename);
    }
    else{
        var elem = window.document.createElement('a');
        elem.href = window.URL.createObjectURL(blob);
        elem.download = filename;        
        document.body.appendChild(elem);
        elem.click();        
        document.body.removeChild(elem);
    }
}

Note that, depending on your situation, you may also want to call URL.revokeObjectURL after removing elem. According to the docs for URL.createObjectURL:

Each time you call createObjectURL(), a new object URL is created, even if you've already created one for the same object. Each of these must be released by calling URL.revokeObjectURL() when you no longer need them. Browsers will release these automatically when the document is unloaded; however, for optimal performance and memory usage, if there are safe times when you can explicitly unload them, you should do so.


As mentioned before, filesaver is a great package to work with files on the client side. But, it is not do well with large files. StreamSaver.js is an alternative solution (which is pointed in FileServer.js) that can handle large files:

const fileStream = streamSaver.createWriteStream('filename.txt', size);
const writer = fileStream.getWriter();
for(var i = 0; i < 100; i++){
    var uint8array = new TextEncoder("utf-8").encode("Plain Text");
    writer.write(uint8array);
}
writer.close()

For me this worked perfectly, with the same filename and extension getting downloaded

<a href={"data:application/octet-stream;charset=utf-16le;base64," + file64 } download={title} >{title}</a>

'title' is the file name with extension i.e, sample.pdf, waterfall.jpg, etc..

'file64' is the base64 content something like this i.e, Ww6IDEwNDAsIFNsaWRpbmdTY2FsZUdyb3VwOiAiR3JvdXAgQiIsIE1lZGljYWxWaXNpdEZsYXRGZWU6IDM1LCBEZW50YWxQYXltZW50UGVyY2VudGFnZTogMjUsIFByb2NlZHVyZVBlcmNlbnQ6IDcwLKCFfSB7IkdyYW5kVG90YWwiOjEwNDAsIlNsaWRpbmdTY2FsZUdyb3VwIjoiR3JvdXAgQiIsIk1lZGljYWxWaXNpdEZsYXRGZWUiOjM1LCJEZW50YWxQYXltZW50UGVyY2VudGFnZSI6MjUsIlByb2NlZHVyZVBlcmNlbnQiOjcwLCJDcmVhdGVkX0J5IjoiVGVycnkgTGVlIiwiUGF0aWVudExpc3QiOlt7IlBhdGllbnRO


I would use an <a></a> tag then set the href='path'. Afterwards, place an image in between the <a> elements so that I can have a visual to see it. If you wanted to, you could create a function that will change the href so that it won't just be the same link but be dynamic.

Give the <a> tag an id as well if you want to access it with javascript.

Starting with the HTML Version:

<a href="mp3/tupac_shakur-how-do-you-want-it.mp3" download id="mp3Anchor">
     <img src="some image that you want" alt="some description" width="100px" height="100px" />
</a>

Now with JavaScript:

*Create a small json file*;

const array = [
     "mp3/tupac_shakur-how-do-you-want-it.mp3",
     "mp3/spice_one-born-to-die.mp3",
     "mp3/captain_planet_theme_song.mp3",
     "mp3/tenchu-intro.mp3",
     "mp3/resident_evil_nemesis-intro-theme.mp3"
];

//load this function on window
window.addEventListener("load", downloadList);

//now create a function that will change the content of the href with every click
function downloadList() {
     var changeHref=document.getElementById("mp3Anchor");

     var j = -1;

     changeHref.addEventListener("click", ()=> {

           if(j < array.length-1) {
               j +=1;
               changeHref.href=""+array[j];
          }
           else {
               alert("No more content to download");
          }
}

If the file contains text data, a technique I use is to put the text into a textarea element and have the user select it (click in textarea then ctrl-A) then copy followed by a paste to a text editor.


This works for me (Chrome 86):

_x000D_
_x000D_
function download(content, mimeType, filename){
  var a = document.createElement('a')
  var blob = new Blob([content], {type: mimeType})
  var url = URL.createObjectURL(blob)
  a.setAttribute('href', url)
  a.setAttribute('download', filename)
  a.click()
}
_x000D_
_x000D_
_x000D_

Here you can see a Fiddle: https://jsfiddle.net/Stelios2020/ukmf5304/6/


The package js-file-download from github.com/kennethjiang/js-file-download handles edge cases for browser support:

View source to see how it uses techniques mentioned on this page.

Installation

yarn add js-file-download
npm install --save js-file-download

Usage

import fileDownload from 'js-file-download'

// fileDownload(data, filename, mime)
// mime is optional

fileDownload(data, 'filename.csv', 'text/csv')

Solution that work on IE10: (I needed a csv file, but it's enough to change type and filename to txt)

var csvContent=data; //here we load our csv data 
var blob = new Blob([csvContent],{
    type: "text/csv;charset=utf-8;"
});

navigator.msSaveBlob(blob, "filename.csv")

This solution is extracted directly from tiddlywiki's (tiddlywiki.com) github repository. I have used tiddlywiki in almost all browsers and it works like a charm:

function(filename,text){
    // Set up the link
    var link = document.createElement("a");
    link.setAttribute("target","_blank");
    if(Blob !== undefined) {
        var blob = new Blob([text], {type: "text/plain"});
        link.setAttribute("href", URL.createObjectURL(blob));
    } else {
        link.setAttribute("href","data:text/plain," + encodeURIComponent(text));
    }
    link.setAttribute("download",filename);
    document.body.appendChild(link);
    link.click();
    document.body.removeChild(link);
}

Github repo: Download saver module


The following method works in IE10+, Edge, Opera, FF and Chrome:

const saveDownloadedData = (fileName, data) => {
    if(~navigator.userAgent.indexOf('MSIE') || ~navigator.appVersion.indexOf('Trident/')) { /* IE9-11 */
        const blob = new Blob([data], { type: 'text/csv;charset=utf-8;' });
        navigator.msSaveBlob(blob, fileName);
    } else {
        const link = document.createElement('a')
        link.setAttribute('target', '_blank');
        if(Blob !== undefined) {
            const blob = new Blob([data], { type: 'text/plain' });
            link.setAttribute('href', URL.createObjectURL(blob));
        } else {
            link.setAttribute('href', 'data:text/plain,' + encodeURIComponent(data));
        }

        ~window.navigator.userAgent.indexOf('Edge')
            && (fileName = fileName.replace(/[&\/\\#,+$~%.'':*?<>{}]/g, '_')); /* Edge */

        link.setAttribute('download', fileName);
        document.body.appendChild(link);
        link.click();
        document.body.removeChild(link);
    }
}

So, just call the function:

saveDownloadedData('test.txt', 'Lorem ipsum');

I'm happily using FileSaver.js. Its compatibility is pretty good (IE10+ and everything else), and it's very simple to use:

var blob = new Blob(["some text"], {
    type: "text/plain;charset=utf-8;",
});
saveAs(blob, "thing.txt");

Examples related to javascript

need to add a class to an element How to make a variable accessible outside a function? Hide Signs that Meteor.js was Used How to create a showdown.js markdown extension Please help me convert this script to a simple image slider Highlight Anchor Links when user manually scrolls? Summing radio input values How to execute an action before close metro app WinJS javascript, for loop defines a dynamic variable name Getting all files in directory with ajax

Examples related to file

Gradle - Move a folder from ABC to XYZ Difference between opening a file in binary vs text Angular: How to download a file from HttpClient? Python error message io.UnsupportedOperation: not readable java.io.FileNotFoundException: class path resource cannot be opened because it does not exist Writing JSON object to a JSON file with fs.writeFileSync How to read/write files in .Net Core? How to write to a CSV line by line? Writing a dictionary to a text file? What are the pros and cons of parquet format compared to other formats?

Examples related to web-applications

Spring - No EntityManager with actual transaction available for current thread - cannot reliably process 'persist' call How do I choose the URL for my Spring Boot webapp? Difference between MEAN.js and MEAN.io External VS2013 build error "error MSB4019: The imported project <path> was not found" How to unpackage and repackage a WAR file IntelliJ, can't start simple web application: Unable to ping server at localhost:1099 Using form input to access camera and immediately upload photos using web app Pass user defined environment variable to tomcat ASP.NET: HTTP Error 500.19 – Internal Server Error 0x8007000d Best practices when running Node.js with port 80 (Ubuntu / Linode)

Examples related to client-side

Getting query parameters from react-router hash fragment Single Page Application: advantages and disadvantages How to export JavaScript array info to csv (on client side)? Best practice for localization and globalization of strings and labels How can you use php in a javascript function How to create a file in memory for user to download, but not through server? Pass request headers in a jQuery AJAX GET call Image resizing client-side with JavaScript before upload to the server Print directly from browser without print popup window Sending emails with Javascript