Exporting tables to CSV format is essential for websites looking to provide users with an easy way to download and analyze tabular data in a versatile, widely-used format. In this tutorial, I'll provide practical examples using a JavaScript class that can easily convert your HTML tables to CSV.

The HTML to CSV JavaScript class is capable of extracting data from the HTML tables and converting it to comma-separated values (CSV) while providing configurable options. It's ideal if you would like to add a "Download CSV" button to your tables, etc.

What is a CSV file?

CSV, also known as Comma-Separated Values, are plain text files that store data in a tabular format. They are widely used in spreadsheet software like Microsoft Excel. It's a versatile format that has major support across many platforms and applications.

1. HTML to CSV JavaScript Class

The JS class includes a variety of methods and configurable options that give you complete control over how your table data is transformed into CSV, from custom delimiters and row numbering to tailored cell formatting.

Create a new file called TableToCSV.js and add the following code:

JS
/*
* Created by David Adams
* https://codeshack.io/convert-html-tables-csv-javascript/
* 
* Released under the MIT license
*/
class TableToCSV {

    constructor(options) {
        const defaults = {
            selector: 'table',
            filename: 'table.csv',
            delimiter: ',',
            lineEnding: '\n',
            download: true,
            includeHeaders: true,
            ignoreCellsSelector: '.no-export',
            includeRowNumbers: false,
            cellFormatter: null
        };
        this.options = Object.assign({}, defaults, options);
    }

    getTables() {
        return document.querySelectorAll(this.options.selector);
    }

    exportTable(table) {
        let csv = [];
        let rows = this.options.includeHeaders ? table.querySelectorAll("tr") : table.querySelectorAll("tbody tr");
        let rowIndex = 0;
        rows.forEach(row => {
            let rowData = [];
            if (this.options.includeRowNumbers) {
                rowData.push(rowIndex + 1);
            }
            const cells = row.querySelectorAll("th, td");
            cells.forEach(cell => {
                if (this.options.ignoreCellsSelector && cell.matches(this.options.ignoreCellsSelector)) {
                    return;
                }
                let data = cell.innerText;
                if (typeof this.options.cellFormatter === 'function') {
                    data = this.options.cellFormatter(cell, data);
                }
                if (data.includes('"')) {
                    data = data.replace(/"/g, '""');
                }
                if (data.search(new RegExp(`("|${this.options.delimiter}|\n)`)) >= 0) {
                    data = `"${data}"`;
                }
                rowData.push(data);
            });
            csv.push(rowData.join(this.options.delimiter));
            rowIndex++;
        });
        return csv.join(this.options.lineEnding);
    }

    downloadCSV(csvData, filename) {
        const blob = new Blob([csvData], { type: 'text/csv' });
        const link = document.createElement('a');
        link.download = filename;
        link.href = window.URL.createObjectURL(blob);
        link.style.display = 'none';
        document.body.appendChild(link);
        link.click();
        document.body.removeChild(link);
    }

    exportAll() {
        const tables = this.getTables();
        tables.forEach((table, index) => {
            const csvData = this.exportTable(table);
            let filename = this.options.filename;
            if (tables.length > 1) {
                const dotIndex = filename.lastIndexOf('.');
                filename = dotIndex > 0 ? filename.slice(0, dotIndex) + '-' + (index + 1) + filename.slice(dotIndex) : filename + '-' + (index + 1);
            }
            if (this.options.download) {
                this.downloadCSV(csvData, filename);
            }
        });
    }

    exportBySelector(selector) {
        const table = document.querySelector(selector);
        if (table) {
            const csvData = this.exportTable(table);
            if (this.options.download) {
                this.downloadCSV(csvData, this.options.filename);
            }
            return csvData;
        }
        return null;
    }

    exportCombined() {
        const tables = this.getTables();
        let combinedCSV = '';
        tables.forEach((table, index) => {
            if (tables.length > 1) {
                combinedCSV += `Table ${index + 1}${this.options.lineEnding}`;
            }
            combinedCSV += this.exportTable(table);
            if (index < tables.length - 1) {
                combinedCSV += this.options.lineEnding + this.options.lineEnding;
            }
        });
        if (this.options.download) {
            this.downloadCSV(combinedCSV, this.options.filename);
        }
        return combinedCSV;
    }

    previewBySelector(selector) {
        const csvData = this.exportBySelector(selector);
        if (csvData) {
            console.log(csvData);
        } else {
            console.warn(`No table found for selector: ${selector}`);
        }
    }

    setCellFormatter(formatter) {
        if (typeof formatter === 'function') {
            this.options.cellFormatter = formatter;
        }
    }

    get selector() {
        return this.options.selector;
    }

    set selector(value) {
        this.options.selector = value;
    }

    get filename() {
        return this.options.filename;
    }

    set filename(value) {
        this.options.filename = value;
    }

    get delimiter() {
        return this.options.delimiter;
    }

    set delimiter(value) {
        this.options.delimiter = value;
    }

    get lineEnding() {
        return this.options.lineEnding;
    }

    set lineEnding(value) {
        this.options.lineEnding = value;
    }

    get download() {
        return this.options.download;
    }

    set download(value) {
        this.options.download = value;
    }

    get includeHeaders() {
        return this.options.includeHeaders;
    }

    set includeHeaders(value) {
        this.options.includeHeaders = value;
    }

    get ignoreCellsSelector() {
        return this.options.ignoreCellsSelector;
    }

    set ignoreCellsSelector(value) {
        this.options.ignoreCellsSelector = value;
    }

    get includeRowNumbers() {
        return this.options.includeRowNumbers;
    }

    set includeRowNumbers(value) {
        this.options.includeRowNumbers = value;
    }

    get cellFormatter() {
        return this.options.cellFormatter;
    }

    set cellFormatter(value) {
        if (typeof value === 'function') {
            this.options.cellFormatter = value;
        }
    }

}

2. Examples

In this section, I'll provide practical examples of how you can integrate the class into your own projects.

2.1. Simple Usage

Let's assume we have the following table:

HTML
<table id="fruits">
    <thead>
        <tr>
            <th>Fruit</th>
            <th>Color</th>
            <th>Price</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td>Apple</td>
            <td>Red</td>
            <td>$1.00</td>
        </tr>
        <tr>
            <td>Banana</td>
            <td>Yellow</td>
            <td>$0.50</td>
        </tr>
        <tr>
            <td>Grape</td>
            <td>Purple</td>
            <td>$2.00</td>
        </tr>
    </tbody>
</table>

We can easily export this table to CSV using the following JS snippet:

JS
const tableToCSV = new TableToCSV({
    selector: '#fruits',
    filename: 'fruits.csv',
    delimiter: ',',
    includeHeaders: true,
    includeRowNumbers: false
});
tableToCSV.exportAll();

The ideal scenario would be to add this logic to a button that the user can use to trigger the code and download the CSV file.

2.2. Convert Multiple Tables to CSV

We can convert multiple tables to CSV by utilizing the exportAll method and selecting multiple elements.

Let's assume we have the following tables:

HTML
<table id="fruits">
    <thead>
        <tr>
            <th>Fruit</th>
            <th>Color</th>
            <th>Price</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td>Apple</td>
            <td>Red</td>
            <td>$1.00</td>
        </tr>
        <tr>
            <td>Banana</td>
            <td>Yellow</td>
            <td>$0.50</td>
        </tr>
        <tr>
            <td>Grape</td>
            <td>Purple</td>
            <td>$2.00</td>
        </tr>
    </tbody>
</table>

<table id="vegetables">
    <thead>
        <tr>
            <th>Vegetable</th>
            <th>Color</th>
            <th>Price</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td>Carrot</td>
            <td>Orange</td>
            <td>$0.30</td>
        </tr>
        <tr>
            <td>Broccoli</td>
            <td>Green</td>
            <td>$1.20</td>
        </tr>
        <tr>
            <td>Eggplant</td>
            <td>Purple</td>
            <td>$1.50</td>
        </tr>
    </tbody>
</table>

We change the selector option to table with the following code:

JS
const tableToCSV = new TableToCSV({
    selector: 'table',
    filename: 'fruits-vegetables.csv',
    delimiter: ',',
    includeHeaders: true,
    includeRowNumbers: false
});
tableToCSV.exportAll();

That will export all the tables because the selector will initiate code that will select all table elements. If you prefer to add class names to your tables, you could also change the selector to, for example, .table.

We can also combine these tables into one CSV file by executing the exportCombined method, like so:

JS
tableToCSV.exportCombined();

That will download a single CSV file with all the tables combined.

2.3. Include Table Headers

We can easily enable the includeHeaders option to include table headers when converting the HTML table to CSV.

Here's how to do it:

JS
const tableToCSV = new TableToCSV({
    selector: '#fruits',
    filename: 'fruits.csv',
    delimiter: ',',
    includeHeaders: true, // Set the following to "true" to include headers
    includeRowNumbers: false
});
tableToCSV.exportAll();

That's essentially all you need to do.

2.4. Include Row Numbers

Like before, all we have to do is enable the includeRowNumbers option, like so:

JS
const tableToCSV = new TableToCSV({
    selector: '#fruits',
    filename: 'fruits.csv',
    delimiter: ',',
    includeHeaders: true,
    includeRowNumbers: true // Set the following to "true" to include row numbers
});
tableToCSV.exportAll();

The row numbers will appear in the first column.

2.5. Change the CSV Delimiter

If we want to change the CSV delimiter, we could easily include the delimiter option when creating a new instance of the Table to CSV class:

JS
const tableToCSV = new TableToCSV({
    selector: '#fruits',
    filename: 'fruits.csv',
    delimiter: ';', // Changed to ";" from ","
    includeHeaders: true,
    includeRowNumbers: false
});
tableToCSV.exportAll();

In the above examples, I changed it from a comma to a semi-colon, but you can change it to whichever delimiter your prefer.

2.6. Change the Filename

Changing the filename is rather straightforward — all we have to do is include the filename option and change the value, like so:

JS
const tableToCSV = new TableToCSV({
    selector: '#fruits',
    filename: 'my_custom_filename.csv', // Change the filename of the CSV file
    delimiter: ',',
    includeHeaders: true,
    includeRowNumbers: false
});
tableToCSV.exportAll();

Feel free to change the filename to your liking.

2.7. Export By Selector

If we're selecting multiple tables when we create a new instance of the class, we can export specific tables using the following method

JS
const tableToCSV = new TableToCSV({
    selector: 'table',
    filename: 'data.csv',
    delimiter: ',',
    includeHeaders: true,
    includeRowNumbers: false
});
tableToCSV.exportBySelector('#fruits'); // export fruits table
tableToCSV.exportBySelector('#vegetables'); // export vegetables table

That way, you don't need to create multiple instances of the same class to export specific tables.

2.8. Format Cells

We can utilize the cellFormatter function to format our cells. Here's how we can do it:

JS
const tableToCSV = new TableToCSV({
    selector: '#fruits',
    filename: 'fruits.csv',
    delimiter: ',',
    includeHeaders: true,
    includeRowNumbers: false,
    cellFormatter: (cell, text) => text.trim() // trim the text in the cell
});
tableToCSV.exportAll();

The above example will trim the text in the cell.

3. Reference

Below, are all available options included with the class.

Option Type Default Description
selector String table The element(s) to select.
filename String table.csv The filename of the exported CSV file.
delimiter String , The delimiter to use for the CSV file.
lineEnding String \n The line ending to use when exporting CSV files. The default value is a newline.
download Boolean true Determines whether to download the CSV file when exporting HTML tables to CSV.
includeHeaders Boolean true Determines whether cell headers should be included in the CSV file.
includeRowNumbers Boolean true Determines whether row numbers should be included in the CSV file.
ignoreCellsSelector String .no-export Will ignore specific table cells based on the cell selector.
cellFormatter Function null A function that's executed when exporting tables.

Below, are all the public methods available in the class.

Method Arguments Description
getTables none Retrieve all the selected tables.
exportTable element Exports a specific table.
exportAll none Exports all selected tables to CSV. If multiple tables are present, it will download each table separately.
exportBySelector selector Exports a specific table by the selector.
exportCombined none Exports all selected tables and combines them into one CSV file.
previewBySelector selector Preview the CSV data that will be logged to the browser console.

Those are all the available options and methods for the HTML to CSV JavaScript class.

Conclusion

The HTML table to CSV Javascript class includes all the essentials to export HTML tables to CSV files. Whether you're exporting large or small tables, it can be handy in many use cases.

If you've enjoyed this tutorial, feel free to share it using the social links and drop a comment below if you encounter any issues or suggestions.

Thanks for reading and enjoy coding!