80

If we were on a nodeJS server, we could write a header, set a mime type, and send it:

res.header("Content-Disposition", "attachment;filename="+name+".csv"); 
res.type("text/csv");
res.send(200, csvString);

and because of the headers, the browser will create a download for the named csv file.

When useful data is generated in a browser, one solution to getting it in a CSV file is to use ajax, upload it to the server, (perhaps optionally save it there) and get the server to send it back with these headers to become a csv download back at the browser.

However, I would like a 100% browser solution that does not involve ping-pong with the server.

So it occurred to me that one could open a new window and try to set the header with a META tag equivalent.

But this doesn't work for me in recent Chrome.

I do get a new window, and it contains the csvString, but does not act as a download.

I guess I expected to get either a download in a bottom tab or a blank new window with a download in a bottom tab.

I'm wondering if the meta tags are correct or if other tags are also needed.

Is there a way to make this work without punting it to the server?

JsFiddle for Creating a CSV in the Browser (not working - outputs window but no download)

var A = [['n','sqrt(n)']];  // initialize array of rows with header row as 1st item
for(var j=1;j<10;++j){ A.push([j, Math.sqrt(j)]) }
var csvRows = [];
for(var i=0,l=A.length; i<l; ++i){
    csvRows.push(A[i].join(','));   // unquoted CSV row
}
var csvString = csvRows.join("\n");
console.log(csvString);
var csvWin = window.open("","","");
csvWin.document.write('<meta name="content-type" content="text/csv">');
csvWin.document.write('<meta name="content-disposition" content="attachment;  filename=data.csv">  ');
csvWin.document.write(csvString);
Paul
  • 26,170
  • 12
  • 85
  • 119
  • 1
    Maybe this helps: http://stackoverflow.com/questions/6796974/force-download-an-image-using-javascript/9158343#9158343 – Vengarioth Jul 24 '13 at 14:04
  • @Vengarioth Data URLs are certainly obscure enough to be a possibility. It is certainly not obvious, I haven't seen a library that does it, and another answer from 2011 says it can't be done. – Paul Jul 24 '13 at 14:10
  • similar data URL tricks are in answer by @Italo to http://stackoverflow.com/questions/4639372/export-to-csv-in-jquery – Paul Jul 24 '13 at 15:07

6 Answers6

164

There's always the HTML5 download attribute :

This attribute, if present, indicates that the author intends the hyperlink to be used for downloading a resource so that when the user clicks on the link they will be prompted to save it as a local file.

If the attribute has a value, the value will be used as the pre-filled file name in the Save prompt that opens when the user clicks on the link.

var A = [['n','sqrt(n)']];

for(var j=1; j<10; ++j){ 
    A.push([j, Math.sqrt(j)]);
}

var csvRows = [];

for(var i=0, l=A.length; i<l; ++i){
    csvRows.push(A[i].join(','));
}

var csvString = csvRows.join("%0A");
var a         = document.createElement('a');
a.href        = 'data:attachment/csv,' +  encodeURIComponent(csvString);
a.target      = '_blank';
a.download    = 'myFile.csv';

document.body.appendChild(a);
a.click();

FIDDLE

Tested in Chrome and Firefox, works fine in the newest versions (as of July 2013).
Works in Opera as well, but does not set the filename (as of July 2013).
Does not seem to work in IE9 (big suprise) (as of July 2013).

An overview over what browsers support the download attribute can be found Here
For non-supporting browsers, one has to set the appropriate headers on the serverside.


Apparently there is a hack for IE10 and IE11, which doesn't support the download attribute (Edge does however).

var A = [['n','sqrt(n)']];

for(var j=1; j<10; ++j){ 
    A.push([j, Math.sqrt(j)]);
}

var csvRows = [];

for(var i=0, l=A.length; i<l; ++i){
    csvRows.push(A[i].join(','));
}

var csvString = csvRows.join("%0A");

if (window.navigator.msSaveOrOpenBlob) {
    var blob = new Blob([csvString]);
    window.navigator.msSaveOrOpenBlob(blob, 'myFile.csv');
} else {
    var a         = document.createElement('a');
    a.href        = 'data:attachment/csv,' +  encodeURIComponent(csvString);
    a.target      = '_blank';
    a.download    = 'myFile.csv';
    document.body.appendChild(a);
    a.click();
}
adeneo
  • 312,895
  • 29
  • 395
  • 388
  • +1, Wow. I didn't expect this kind of answer so soon. Two issues: (1) the newlines are not in the file, so it isn't really a standard csv anymore; (2) downloads as name "true". – Paul Jul 24 '13 at 14:18
  • 2
    Couldn't figure out how to set a filename, but I'm sure it's possible somehow. It's usually `attachment;filename=somefile.csv` but that doesn't seem to work for csv strings ? – adeneo Jul 24 '13 at 14:34
  • Another way to do this would be to just url encode the string an post it to a PHP file that echo's it out with the content disposition headers set, but then it wouldn't be entirely clientside anymore, but that is the usual way of doing this. – adeneo Jul 24 '13 at 14:35
  • It seems a.download can set the file name but only on chrome. http://stackoverflow.com/questions/283956/is-there-any-way-to-specify-a-suggested-filename-when-using-data-uri – Paul Jul 24 '13 at 14:39
  • The only question now is how to preserve the newlines. I'm thinking some kind of encoding of the string before appending it to a.href. Base64? url encoded? – Paul Jul 24 '13 at 14:40
  • @Paul - stupid me, of course you put the name in the download attribute, I knew that, but set it to true for some reason, and couldn't really understand why the file was always named true. – adeneo Jul 24 '13 at 14:44
  • 19
    a.href = 'data:attachment/csv,' + encodeURIComponent(csvString); – Paul Jul 24 '13 at 14:54
  • That works too, but would convert any special characters not just the newlines, but if that's not an issue, no problem. – adeneo Jul 24 '13 at 14:56
  • 1
    I suspect they might get converted back in the actual file... but not sure – Paul Jul 24 '13 at 14:56
  • OK, you get a check mark. This is blog-coderwall-github worthy stuff, people really don't know how to do this. – Paul Jul 24 '13 at 14:57
  • If it's decoded in the file, that's something the browser does. I tested and works fine with URI encoding in Chrome, and checking the file, it does get decoded before it's saved, strangely enough! And yes, this is not something that is commonly done, it's all very new with the download attributes and everything. – adeneo Jul 24 '13 at 14:59
  • Thanks for helping me learn about these data URLs. I created a library (license: GPL) that will handle these CSV downloads, among other things. https://github.com/DrPaulBrewer/html5csv – Paul Aug 08 '13 at 11:41
  • Still no IE for Ubuntu either ... :-) – Paul Jan 20 '14 at 14:59
  • 1
    The filename did not work for me in Chrome. It worked when I changed a.href to this: a.href = window.URL.createObjectURL(new Blob([csvString], { type: "attachment/csv" })); – Facio Ratio Jun 11 '14 at 16:03
  • 1
    @FacioRatio Google changed something in Chrome. breaking the ability to set the filename. See: https://code.google.com/p/chromium/issues/detail?id=373182 – Paul Jun 12 '14 at 08:47
  • hey I tried the FIDDLE. But the file being downloaded is in .part format and not .csv. I tried in Firefox 19.0.2. – Vishnu Raj Jun 23 '14 at 09:54
  • Is the maximum length of a link-url (a.href) of a browser affected by this href-assignment? – tokosh Jul 29 '14 at 05:19
  • 1
    With the `target` attribute set to `_blank`, this does't work in Safari. And Safari does not support the `download` attribute, so the file name will not work too, it downloads 'Unknown'. – Primoz990 Feb 05 '15 at 11:03
  • it crashes Chrome with a 20.000 rows csv download http://stackoverflow.com/questions/23301467/javascript-exporting-large-text-csv-file-crashes-google-chrome – max4ever Jul 21 '15 at 16:27
  • I was able to activate the anchor without adding it to the dom. (one less line) – Jared Jul 24 '15 at 15:36
  • white space in between words are removed, i.e. South Africa -> SouthAfrica – neel4soft Dec 19 '15 at 10:33
  • "There's always the HTML5 download attribute" ... no not really, not in older browsers ;) – superhero Apr 01 '16 at 12:16
  • Doesn't work from me after EncodeURIComponent with '%0A' as a delimiter. You may need to replace it with '/r/n/'. – Nisarg Shah Nov 22 '16 at 05:51
  • the answer in the below link has the hack for IE download -> https://stackoverflow.com/a/35448277/3366706 hope it helps someone. – user3366706 Jun 02 '17 at 21:44
  • I ran into trouble with this when there are commas in the data. – wwwhack Oct 26 '18 at 18:20
30

@adeneo answer works for Firefox and chrome... For IE the below can be used.

if (window.navigator.msSaveOrOpenBlob) {
  var blob = new Blob([decodeURIComponent(encodeURI(result.data))], {
    type: "text/csv;charset=utf-8;"
  });
  navigator.msSaveBlob(blob, 'FileName.csv');
}
Manu Sharma
  • 671
  • 7
  • 11
  • 3
    I can confirm this works in IE11, and have updated my library [html5csv](https://github.com/DrPaulBrewer/html5csv) to include IE 11 support based on this technique. I credited your post in the code comments. Thanks a lot. – Paul Jan 11 '15 at 08:29
15

See adeneo's answer, but don't forget encodeURIComponent!

a.href     = 'data:application/csv;charset=utf-8,' + encodeURIComponent(csvString);

Also, I needed to do "\r\n" not just "\n" for the row delimiter.

var csvString = csvRows.join("\r\n");

Revised fiddle: http://jsfiddle.net/7Q3c6/

Brock Adams
  • 90,639
  • 22
  • 233
  • 295
user2608223
  • 151
  • 1
  • 2
7

Once I packed JS code doing that to a tiny library:

https://github.com/AlexLibs/client-side-csv-generator

The Code, Documentation and Demo/Playground are provided on Github.

Enjoy :)

Pull requests are welcome.

Alexander
  • 7,484
  • 4
  • 51
  • 65
3

We can easily create and export/download the excel file with any separator (in this answer I am using the comma separator) using javascript. I am not using any external package for creating the excel file.

    var Head = [[
        'Heading 1',
        'Heading 2', 
        'Heading 3', 
        'Heading 4'
    ]];

    var row = [
       {key1:1,key2:2, key3:3, key4:4},
       {key1:2,key2:5, key3:6, key4:7},
       {key1:3,key2:2, key3:3, key4:4},
       {key1:4,key2:2, key3:3, key4:4},
       {key1:5,key2:2, key3:3, key4:4}
    ];

for (var item = 0; item < row.length; ++item) {
       Head.push([
          row[item].key1,
          row[item].key2,
          row[item].key3,
          row[item].key4
       ]);
}

var csvRows = [];
for (var cell = 0; cell < Head.length; ++cell) {
       csvRows.push(Head[cell].join(','));
}
            
var csvString = csvRows.join("\n");
let csvFile = new Blob([csvString], { type: "text/csv" });
let downloadLink = document.createElement("a");
downloadLink.download = 'MYCSVFILE.csv';
downloadLink.href = window.URL.createObjectURL(csvFile);
downloadLink.style.display = "none";
document.body.appendChild(downloadLink);
downloadLink.click();
Pulkit Aggarwal
  • 2,554
  • 4
  • 23
  • 33
2

See adeneo's answer, but to make this work in Excel in all countries you should add "SEP=," to the first line of the file. This will set the standard separator in Excel and will not show up in the actual document

var csvString = "SEP=, \n" + csvRows.join("\r\n");
Thyselius
  • 53
  • 5
  • I doubt R or Pandas (Python) is going to like that in a csv file. – Paul Oct 18 '18 at 05:03
  • 1
    @Paul That might be true, but in my case I needed to make a csv file to be opened in excel and I want it to open in a presentable manner regardless of locality. If you have a better solution for this I'd love to hear it! – Thyselius Oct 19 '18 at 06:12