1134

Is there a way to 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 the file and prompt them to save it?

2

22 Answers 22

1032

Simple solution for HTML5 ready browsers...

function download(filename, text) {
  var element = document.createElement('a');
  element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text));
  element.setAttribute('download', filename);

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

  element.click();

  document.body.removeChild(element);
}
form * {
  display: block;
  margin: 10px;
}
<form onsubmit="download(this['name'].value, this['text'].value)">
  <input type="text" name="name" value="test.txt">
  <textarea name="text"></textarea>
  <input type="submit" value="Download">
</form>

Usage

download('test.txt', 'Hello world!');
21
  • 15
    Yep. This is exactly what @MatthewFlaschen has posted here about 3 years ago. Commented Aug 12, 2013 at 22:01
  • 67
    Yes, but with download attribute you can specify file name ;-) Commented Aug 12, 2013 at 22:08
  • 6
    Chrome only appends the txt extension if you do not provide an extension in the filename. If you do download("data.json", data) it'll work as expected.
    – Carl Smith
    Commented Jul 19, 2014 at 14:34
  • 9
    This worked for me in Chrome (73.0.3683.86), and Firefox (66.0.2). It did NOT work in IE11 (11.379.17763.0) and Edge (44.17763.1.0).
    – Sam
    Commented Mar 29, 2019 at 8:40
  • 6
    There's no need to attach the element to the DOM.
    – sean
    Commented Dec 6, 2019 at 14:04
487

You can use data URIs. Browser support varies; see Wikipedia. Example:

<a href="data:application/octet-stream;charset=utf-16le;base64,//5mAG8AbwAgAGIAYQByAAoA">text file</a>

The octet-stream is to force a download prompt. Otherwise, it will probably open in the browser.

For CSV, you can use:

<a href="data:application/octet-stream,field1%2Cfield2%0Afoo%2Cbar%0Agoo%2Cgai%0A">CSV Octet</a>

Try the jsFiddle demo.

26
  • 21
    This is not a cross browser solution but definitely something worth looking at. For example IE limits support to data uri. IE 8 limits size to 32KB and IE 7 and lower doesn't support at all. Commented Sep 8, 2010 at 6:32
  • 9
    in Chrome Version 19.0.1084.46, this method generates the following warning : "Resource interpreted as Document but transferred with MIME type text/csv: "data:text/csv,field1%2Cfield2%0Afoo%2Cbar%0Agoo%2Cgai%0A"." A download is not triggered
    – Chris
    Commented May 16, 2012 at 11:44
  • 3
    It does work in Chrome now (tested against v20 and v21) but not IE9 (that might just be the jsFiddle, but somehow I doubt it).
    – earcam
    Commented Aug 30, 2012 at 16:20
  • 7
    The correct charset is almost certainly UTF-16, unless you have code converting it to UTF-8. JavaScript uses UTF-16 internally. If you have a text or CSV file, start the string with '\ufeff', the Byte Order Mark for UTF-16BE, and text editors will be able to read non-ASCII characters correctly.
    – larspars
    Commented Nov 19, 2014 at 9:06
  • 27
    Just add download="txt.csv" attribute in order to have proper file name and extension and to tell your OS what to do with it.
    – elshnkhll
    Commented Jan 15, 2016 at 16:33
331

An example for IE 10+, Firefox and Chrome (and without jQuery or any other library):

function save(filename, data) {
    const blob = new Blob([data], {type: 'text/csv'});
    if(window.navigator.msSaveOrOpenBlob) {
        window.navigator.msSaveBlob(blob, filename);
    }
    else{
        const 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.

14
  • 2
    For AngularJS 1.x apps, you can build an array of Urls as they are created and then clean them up in the $onDestroy function of the component. This is working great for me.
    – Splaktar
    Commented Sep 6, 2016 at 18:12
  • 3
    Other answers led to Failed: network error in Chrome. This one works well.
    – juniper-
    Commented Sep 27, 2017 at 19:25
  • 6
    This worked for me in Chrome (73.0.3683.86), Firefox (66.0.2), IE11 (11.379.17763.0) and Edge (44.17763.1.0).
    – Sam
    Commented Mar 29, 2019 at 8:41
  • 6
    For those looking to avoid garbage collection on the URL or strange behavior, just declare your blob like this: const url = URL.createObjectURL(blob, { oneTimeOnly: true }). You can always save the blob and generate a new Url later if needed.
    – Daniel
    Commented May 1, 2020 at 21:10
  • 5
    Consider adding elem.style.display = 'none'; before document.body.appendChild(elem); if you want to avoid any potential for visual glitches Commented Jul 29, 2020 at 21:30
203

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);
4
  • 5
    However: there's an open bug in IE 10 (and I've still seen it in 11) that throws "Access is denied" on the a.click() line because it thinks the blob URL is cross-origin.
    – Matt
    Commented Dec 16, 2014 at 19:44
  • 1
    @Matt data uri is cross origin in some browsers. as far as I know, not just in msie, but in chrome as well. you can test it by trying to inject javascript with data uri. It won't be able to access other parts of the site...
    – inf3rno
    Commented Sep 13, 2015 at 0:13
  • 11
    "All of the above example works just fine in chrome and IE, but fail in Firefox.". Since the order of answers can change over time, it's unclear which answers were above yours when you wrote this. Can you indicate exactly which approaches don't work in Firefox?
    – Kevin
    Commented Apr 2, 2018 at 18:57
  • 2
    👍 This blob approach works much better for very large files.
    – joe
    Commented Jul 29, 2020 at 13:05
137

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");
9
  • This works great on Chrome. How do I allow the user to specific the location of the file on disk?
    – gregm
    Commented May 22, 2013 at 18:40
  • 6
    Wow, thanks for the easy to use library. This is easily the best answer, and who cares about people using HTML < 5 these days any ways? Commented Jun 19, 2013 at 21:16
  • @gregm I'm not sure you can with this plugin. Commented Jun 20, 2013 at 6:23
  • @gregm: You mean the download location? That's not related to FileSaver.js, you need to set your browser configuration so that it asks for a folder before every download, or use the rather new download attribute on <a>.
    – CodeManX
    Commented Dec 3, 2014 at 15:54
  • 1
    This is a GREAT solution for IE 10+ family of browsers. IE doesn't support the download HTML 5 tag yet and the other solutions on this page (and other SO pages discussing the same problem) were simply not working for me. FileSaver ftw!
    – TMc
    Commented Jan 12, 2015 at 20:32
50

Use Blob:

function download(content, mimeType, filename){
  const a = document.createElement('a') // Create "a" element
  const blob = new Blob([content], {type: mimeType}) // Create a blob (file-like object)
  const url = URL.createObjectURL(blob) // Create an object URL from blob
  a.setAttribute('href', url) // Set "a" element link
  a.setAttribute('download', filename) // Set download filename
  a.click() // Start downloading
}

Blob is being supported by all modern browsers.
Caniuse support table for Blob:

Here is a Fiddle

And here MDN Docs

The Blob object represents a blob, which is a file-like object of immutable, raw data; they can be read as text or binary data...

5
  • This is a copy of the existing answer here: stackoverflow.com/a/33542499/1250319
    – Daniel
    Commented Feb 6 at 1:52
  • @Daniel This is not a copy, there's subtle but important difference between the two answers.
    – xuhdev
    Commented Feb 29 at 21:58
  • How so @xuhdev? This answer contains the exact same elements as the other answer but without the additional window.navigator.msSaveOrOpenBlob alternative. That doesn't make this a new answer. The other answer uses Blob + using URL.createObjectURL + a dynamically created <a> tag whose click is immediately called.
    – Daniel
    Commented Mar 1 at 4:04
  • 2
    @Daniel This answer does not modify document as the other one does. The other answer doesn't explain why modifying the DOM is needed and what side effect it may have. So I think there are some positive points on this answer (hence "important difference between the two answers"). Of course, the other answer has its own positive sides, as you pointed out.
    – xuhdev
    Commented Mar 1 at 19:34
  • 2
    Thanks for the clarification @xuhdev, fair enough 👍 I've retracted my flag but maintain the downvote because I think the difference about the DOM could have been a comment or edit to the other answer I mentioned.
    – Daniel
    Commented Mar 2 at 20:37
22

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.

3
  • 1
    I just used jquery to attach events(onclick and onready) and set attributes, which you can also do with vanilla JS. The core part(window.navigator.msSaveOrOpenBlob) doesn't need jquery.
    – dinesh ygv
    Commented Nov 6, 2016 at 2:17
  • 1
    There is still the limitation of size for the data uri approach, isn't it?
    – phil
    Commented Jun 26, 2017 at 8:21
  • msSaveOrOpenBlob is shown as obsolete here: developer.mozilla.org/en-US/docs/Web/API/Navigator/msSaveBlob
    – eflat
    Commented May 14, 2020 at 0:30
22

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:

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 = ""


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, as the DOM is not modified (Otherwise the huge URL in the DOM can be a source of tab freeze).

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()
})
<button id="BUTTONDOWNLOAD">DOWNLOAD!</button>


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.

/* 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()

1
  • 3
    The best and easy solution to use! Thank you
    – enes islam
    Commented May 1, 2022 at 11:30
18

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')
1
  • 2
    Thanks - just tested - works with Firefox, Chrome and Edge on Windows Commented Apr 13, 2019 at 22:07
16

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

2
  • 1
    It works very nicely on Chrome, but not on Firefox. It does make a file and downloads it, but the file is empty. No content. Any ideas why? Haven't tested on IE...
    – Narxx
    Commented Feb 16, 2017 at 16:26
  • 4
    except that the function has no name, this is my favourite Commented Aug 20, 2018 at 20:32
15
function download(filename, text) {
  var element = document.createElement('a');
  element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text));
  element.setAttribute('download', filename);

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

  element.click();

  document.body.removeChild(element);
}



// Start file download.
download("hello.txt","This is the content of my file :)");

Original article : https://ourcodeworld.com/articles/read/189/how-to-create-a-file-and-generate-a-download-with-javascript-in-the-browser-without-a-server

11

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")
1
11

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

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()
1
  • 1
    Text Encoder is highly experimental right now, I'd suggest avoiding (or polyfilling) it. Commented Sep 22, 2017 at 20:07
8
var element = document.createElement('a');
element.setAttribute('href', 'data:text/text;charset=utf-8,' +      encodeURI(data));
element.setAttribute('download', "fileName.txt");
element.click();
1
  • 2
    What are the differences between this approach and creating a Blob? Commented Nov 5, 2016 at 21:08
7

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.

1
  • 2
    I was not able to accept the suggestion. Strange... I updated the code.
    – Rick
    Commented Jun 15, 2016 at 21:22
3

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;
 }
1
  • can you open the file in a new tab keeping the fileName assigned, but not downloading, just opening in a tab? Commented Dec 5, 2019 at 23:48
2

Download file with extensions or without extensions in the example, I am using JSON. You may add your data and extensions. You may use 'MAC-Addresses.json' here, as per your wish. If you want to add an extension, add there, else, just write the file name without extensions.

let myJson = JSON.stringify(yourdata);
    let element = document.createElement('a');
    element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(myJson));
    element.setAttribute('download', 'MAC-Addresses.json');
    element.style.display = 'none';
    document.body.appendChild(element);
    element.click();
    document.body.removeChild(element);
1
  • Nice code snippet, was useful for me.
    – Franco Gil
    Commented Nov 12, 2022 at 23:12
1

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

1

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

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

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.

1
  • 40
    I had considered that, but from a user-friendliness point, this is disastrous. Also, the file has to be saved with a CSV extension. Try telling that to your users. Commented Sep 8, 2010 at 7:07

Not the answer you're looking for? Browse other questions tagged or ask your own question.