I am trying to add a feature of csv download option in my website. It should convert the html table present in the website in to csv content and make it downloadable. Ive been searching through internet for a good plugin and found some usefule ones like http://www.dev-skills.com/export-html-table-to-csv-file/ But it uses php script to do the download part. I was wondering if there is a pure javascript library available to do this feature using server side softwares like node.js without the use of php??
-
2possible duplicate of [Export to csv in jQuery](http://stackoverflow.com/questions/4639372/export-to-csv-in-jquery) – Italo Borssatto Nov 14 '13 at 19:28
-
Possible duplicate of [Export to CSV using jQuery and html](https://stackoverflow.com/questions/16078544/export-to-csv-using-jquery-and-html) – Dave Jarvis Apr 21 '18 at 00:54
-
https://www.codexworld.com/export-html-table-data-to-csv-using-javascript/ – Stack Underflow Aug 15 '19 at 19:12
9 Answers
Should work on every modern browser and without jQuery or any dependency, here my implementation :
// Quick and simple export target #table_id into a csv
function download_table_as_csv(table_id, separator = ',') {
// Select rows from table_id
var rows = document.querySelectorAll('table#' + table_id + ' tr');
// Construct csv
var csv = [];
for (var i = 0; i < rows.length; i++) {
var row = [], cols = rows[i].querySelectorAll('td, th');
for (var j = 0; j < cols.length; j++) {
// Clean innertext to remove multiple spaces and jumpline (break csv)
var data = cols[j].innerText.replace(/(\r\n|\n|\r)/gm, '').replace(/(\s\s)/gm, ' ')
// Escape double-quote with double-double-quote (see https://stackoverflow.com/questions/17808511/properly-escape-a-double-quote-in-csv)
data = data.replace(/"/g, '""');
// Push escaped string
row.push('"' + data + '"');
}
csv.push(row.join(separator));
}
var csv_string = csv.join('\n');
// Download it
var filename = 'export_' + table_id + '_' + new Date().toLocaleDateString() + '.csv';
var link = document.createElement('a');
link.style.display = 'none';
link.setAttribute('target', '_blank');
link.setAttribute('href', 'data:text/csv;charset=utf-8,' + encodeURIComponent(csv_string));
link.setAttribute('download', filename);
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
Then add your download button/link :
<a href="#" onclick="download_table_as_csv('my_id_table_to_export');">Download as CSV</a>
CSV file is timedated and compatible with default Excel format.
Update after comments: Added second parameter "separator", it can be used to configure another character like ;
, it's useful if you have user downloading your csv in different region of the world because they can use another default separator for Excel, for more information see : https://superuser.com/a/606274/908273

- 2,865
- 1
- 20
- 30
-
5Excellent! After changing one line from csv.push(row.join(';')); to csv.push(row.join(','));, it worked for me. – Shivanshu May 09 '20 at 07:06
-
1I just looked for information on the default separator for Excel and it depends on the region, so indeed it is possible that for some people it is necessary to change the separator in the code. See : https://superuser.com/a/606274/908273 – Calumah May 09 '20 at 11:18
-
-
so nice. i guess the semi-colon delimiter must just be to verify those that use this aren't outright slugs. lol. anyway, thank you. – WEBjuju Jun 01 '20 at 01:05
-
2I had to change `var rows = document.querySelectorAll('table#' + table_id + ' tr');` to `var rows = document.querySelectorAll('#' + table_id + ' tr');` to get it working, but all is well now – Grintor Jul 21 '20 at 17:54
-
This works well, I needed to add a replace for non breaking space characters, like so: var data = cols[j].innerText.replace(/\u00a0/g, ''); and then the existing data = data.replace(/(\r\n|\n|\r)/gm, '').replace(/(\s\s)/gm, ' '). I tried to edit the answer, but the edit queue is full. – Chris Apr 27 '21 at 17:48
-
This seems to work on Desktop, is there a way to make this work on iOS? Clicking the button on iOS does not start the download. – Marcipanas Jun 12 '21 at 08:29
-
For me it appears to be adding a blank row in as the filter row of the grid?. – c-sharp-and-swiftui-devni Aug 25 '21 at 15:56
-
anyone had UTF-8 encoding issue. please try this. `link.setAttribute('href', 'data:text/csv;charset=utf-8,%EF%BB%BF' + encodeURIComponent(csv_string));` – Jin Lim Jun 20 '22 at 20:45
-
-
Using just jQuery
, vanilla Javascript
, and the table2CSV
library:
export-to-html-table-as-csv-file-using-jquery
Put this code into a script to be loaded in the head
section:
$(document).ready(function () {
$('table').each(function () {
var $table = $(this);
var $button = $("<button type='button'>");
$button.text("Export to spreadsheet");
$button.insertAfter($table);
$button.click(function () {
var csv = $table.table2CSV({
delivery: 'value'
});
window.location.href = 'data:text/csv;charset=UTF-8,'
+ encodeURIComponent(csv);
});
});
})
Notes:
Requires jQuery and table2CSV: Add script references to both libraries before the script above.
The table
selector is used as an example, and can be adjusted to suit your needs.
It only works in browsers with full Data URI
support: Firefox, Chrome and Opera, not in IE, which only supports Data URIs
for embedding binary image data into a page.
For full browser compatibility you would have to use a slightly different approach that requires a server side script to echo
the CSV.

- 1,130
- 1
- 10
- 17

- 9,331
- 2
- 29
- 46
-
1Hi... thanks for the answer... but when I try to download the tabletoCsv file from the link that you provided.. it shows the error "page not found" – sam Mar 22 '13 at 05:04
-
Thanks for the reply!!! I really apppreciate your help!! I found another interesting option too after a lot of searching http://www.datatables.net/examples/ – sam Mar 22 '13 at 11:47
-
-
table2csv.com is command line program, the correct link would be on the italo's answer: http://www.kunalbabre.com/projects/table2CSV.php – Kokizzu Dec 30 '13 at 11:49
-
1I have tried most of the above, but the easiest one is this. http://jordiburgos.com/post/2014/excellentexport-javascript-export-to-excel-csv.html – Buddhika Ariyaratne Jan 28 '15 at 18:46
-
-
-
1
There is a very easy free and open source solution at http://jordiburgos.com/post/2014/excellentexport-javascript-export-to-excel-csv.html
First download the javascript file and sample files from https://github.com/jmaister/excellentexport/releases/tag/v1.4
The html page looks like below.
Make sure the the javascript file is in the same folder or change the path of the script in the html file accordingly.
<html>
<head>
<title>Export to excel test</title>
<script src="excellentexport.js"></script>
<style>
table, tr, td {
border: 1px black solid;
}
</style>
</head>
<body>
<h1>ExcellentExport.js</h1>
Check on <a href="http://jordiburgos.com">jordiburgos.com</a> and <a href="https://github.com/jmaister/excellentexport">GitHub</a>.
<h3>Test page</h3>
<br/>
<a download="somedata.xls" href="#" onclick="return ExcellentExport.excel(this, 'datatable', 'Sheet Name Here');">Export to Excel</a>
<br/>
<a download="somedata.csv" href="#" onclick="return ExcellentExport.csv(this, 'datatable');">Export to CSV</a>
<br/>
<table id="datatable">
<tr>
<th>Column 1</th>
<th>Column "cool" 2</th>
<th>Column 3</th>
</tr>
<tr>
<td>100,111</td>
<td>200</td>
<td>300</td>
</tr>
<tr>
<td>400</td>
<td>500</td>
<td>600</td>
</tr>
<tr>
<td>Text</td>
<td>More text</td>
<td>Text with
new line</td>
</tr>
</table>
</body>
It is very easy to use this as I have tried most of the other methods.

- 33
- 1
- 6

- 2,339
- 6
- 51
- 88
-
1
-
I have a table that is dynamically generated using javascript. Apparently, this solution didn't work. In return ExcellentExport.csv(this, 'datatable' ), what does 'this' referring to? – Nguai al Mar 02 '21 at 22:50
You don't need PHP script on server side. Do that in the client side only, in browsers that accept Data URIs:
data:application/csv;charset=utf-8,content_encoded_as_url
The Data URI will be something like:
data:application/csv;charset=utf-8,Col1%2CCol2%2CCol3%0AVal1%2CVal2%2CVal3%0AVal11%2CVal22%2CVal33%0AVal111%2CVal222%2CVal333
You can call this URI by:
- using
window.open
- or setting the
window.location
- or by the href of an anchor
- by adding the download attribute it will work in chrome, still have to test in IE.
To test, simply copy the URIs above and paste in your browser address bar. Or test the anchor below in a HTML page:
<a download="somedata.csv" href="data:application/csv;charset=utf-8,Col1%2CCol2%2CCol3%0AVal1%2CVal2%2CVal3%0AVal11%2CVal22%2CVal33%0AVal111%2CVal222%2CVal333">Example</a>
To create the content, getting the values from the table, you can use table2CSV mentioned by MelanciaUK and do:
var csv = $table.table2CSV({delivery:'value'});
window.location.href = 'data:application/csv;charset=UTF-8,' + encodeURIComponent(csv);

- 33
- 1
- 6

- 15,044
- 7
- 62
- 88
(1)This is the native javascript solution for this issue. It works on most of modern browsers.
function export2csv() {
let data = "";
const tableData = [];
const rows = document.querySelectorAll("table tr");
for (const row of rows) {
const rowData = [];
for (const [index, column] of row.querySelectorAll("th, td").entries()) {
// To retain the commas in the "Description" column, we can enclose those fields in quotation marks.
if ((index + 1) % 3 === 0) {
rowData.push('"' + column.innerText + '"');
} else {
rowData.push(column.innerText);
}
}
tableData.push(rowData.join(","));
}
data += tableData.join("\n");
const a = document.createElement("a");
a.href = URL.createObjectURL(new Blob([data], { type: "text/csv" }));
a.setAttribute("download", "data.csv");
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
}
table {
border-collapse: collapse;
}
td, th {
border: 1px solid #aaa;
padding: 0.5rem;
text-align: left;
}
td {
font-size: 0.875rem;
}
.btn-group {
padding: 1rem 0;
}
button {
background-color: #fff;
border: 1px solid #000;
margin-top: 0.5rem;
border-radius: 3px;
padding: 0.5rem 1rem;
font-size: 1rem;
}
button:hover {
cursor: pointer;
background-color: #000;
color: #fff;
}
<table>
<thead>
<tr>
<th>Name</th>
<th>Author</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>jQuery</td>
<td>John Resig</td>
<td>The Write Less, Do More, JavaScript Library.</td>
</tr>
<tr>
<td>React</td>
<td>Jordan Walke</td>
<td>React makes it painless to create interactive UIs.</td>
</tr>
<tr>
<td>Vue.js</td>
<td>Yuxi You</td>
<td>The Progressive JavaScript Framework.</td>
</tr>
</tbody>
</table>
<div class="btn-group">
<button onclick="export2csv()">csv</button>
</div>
(2) If you want a pure javascript library, FileSaver.js could help you save the code snippets for triggering file download. Besides, FileSaver.js will not be responsible for constructing content for exporting. You have to construct the content by yourself in the format you want.

- 1,340
- 17
- 7
A Modern Solution
Most of the proposed solutions here will break with nested tables or other elements inside your td elements. I frequently use other elements inside my tables, but only want to export the topmost table. I took some of the code found here from Calumah and added in some modern vanilla ES6 JS.
Using textContent is a better solution than innerText as innerText will return any HTML inside your td elements. However, even textContent will return the text from nested elements. An even better solution is to use custom data attributes on your td and pull the values for you CSV from there.
Happy coding!
function downloadAsCSV(tableEle, separator = ','){
let csvRows = []
//only get direct children of the table in question (thead, tbody)
Array.from(tableEle.children).forEach(function(node){
//using scope to only get direct tr of node
node.querySelectorAll(':scope > tr').forEach(function(tr){
let csvLine = []
//again scope to only get direct children
tr.querySelectorAll(':scope > td').forEach(function(td){
//clone as to not remove anything from original
let copytd = td.cloneNode(true)
let data
if(copytd.dataset.val) data = copytd.dataset.val.replace(/(\r\n|\n|\r)/gm, '')
else {
Array.from(copytd.children).forEach(function(remove){
//remove nested elements before getting text
remove.parentNode.removeChild(remove)
})
data = copytd.textContent.replace(/(\r\n|\n|\r)/gm, '')
}
data = data.replace(/(\s\s)/gm, ' ').replace(/"/g, '""')
csvLine.push('"'+data+'"')
})
csvRows.push(csvLine.join(separator))
})
})
var a = document.createElement("a")
a.style = "display: none; visibility: hidden" //safari needs visibility hidden
a.href = 'data:text/csv;charset=utf-8,' + encodeURIComponent(csvRows.join('\n'))
a.download = 'testfile.csv'
document.body.appendChild(a)
a.click()
a.remove()
}
Edit: cloneNode() updated to cloneNode(true) to get insides

- 61
- 1
- 2
Used the answer above, but altered it for my needs.
I used the following function and imported to my REACT file where I needed to download the csv file.
I had a span
tag within my th
elements. Added comments to what most functions/methods do.
import { tableToCSV, downloadCSV } from './../Helpers/exportToCSV';
export function tableToCSV(){
let tableHeaders = Array.from(document.querySelectorAll('th'))
.map(item => {
// title = splits elem tags on '\n',
// then filter out blank "" that appears in array.
// ex ["Timestamp", "[Full time]", ""]
let title = item.innerText.split("\n").filter(str => (str !== 0)).join(" ")
return title
}).join(",")
const rows = Array.from(document.querySelectorAll('tr'))
.reduce((arr, currRow) => {
// if tr tag contains th tag.
// if null return array.
if (currRow.querySelector('th')) return arr
// concats individual cells into csv format row.
const cells = Array.from(currRow.querySelectorAll('td'))
.map(item => item.innerText)
.join(',')
return arr.concat([cells])
}, [])
return tableHeaders + '\n' + rows.join('\n')
}
export function downloadCSV(csv){
const csvFile = new Blob([csv], { type: 'text/csv' })
const downloadLink = document.createElement('a')
// sets the name for the download file
downloadLink.download = `CSV-${currentDateUSWritten()}.csv`
// sets the url to the window URL created from csv file above
downloadLink.href = window.URL.createObjectURL(csvFile)
// creates link, but does not display it.
downloadLink.style.display = 'none'
// add link to body so click function below works
document.body.appendChild(downloadLink)
downloadLink.click()
}
When user click export to csv it trigger the following function in react.
handleExport = (e) => {
e.preventDefault();
const csv = tableToCSV()
return downloadCSV(csv)
}
Example html table elems.
<table id="datatable">
<tbody>
<tr id="tableHeader" className="t-header">
<th>Timestamp
<span className="block">full time</span></th>
<th>current rate
<span className="block">alt view</span>
</th>
<th>Battery Voltage
<span className="block">current voltage
</span>
</th>
<th>Temperature 1
<span className="block">[C]</span>
</th>
<th>Temperature 2
<span className="block">[C]</span>
</th>
<th>Time & Date </th>
</tr>
</tbody>
<tbody>
{this.renderData()}
</tbody>
</table>
</div>

- 2,807
- 29
- 31
-
Thanks for writing this out, could you add the function `currentDateUSWritten()` that you're calling inside of `downloadCSV()`? – Preston Badeer Feb 05 '21 at 01:14
I found there is a library for this. See example here:
https://editor.datatables.net/examples/extensions/exportButtons.html
In addition to the above code, the following Javascript library files are loaded for use in this example:
In HTML, include following scripts:
jquery.dataTables.min.js
dataTables.editor.min.js
dataTables.select.min.js
dataTables.buttons.min.js
jszip.min.js
pdfmake.min.js
vfs_fonts.js
buttons.html5.min.js
buttons.print.min.js
Enable buttons by adding scripts like:
<script>
$(document).ready( function () {
$('#table-arrays').DataTable({
dom: '<"top"Blf>rt<"bottom"ip>',
buttons: ['copy', 'excel', 'csv', 'pdf', 'print'],
select: true,
});
} );
</script>
For some reason, the excel export results in corrupted file, but can be repaired. Alternatively, disable excel and use csv export.

- 91
- 1
- 2
I used Calumah's function posted above, but I did run into an issue with his code as poisted.
The rows are joined with a semicolon
csv.push(row.join(';'));
but the link generated has "text/csv" as the content type
Maybe in Windows that isn't a problem, but in Excel for Mac that throws things off. I changed the array join to a comma and it worked perfect.

- 39
- 5