Export to CSV
Export your grid’s raw data to the CSV format, as a downloadable file, a blob, or a string. Customize your export using Handsontable’s configuration options.
Prerequisites
- You configured Handsontable in your project.
- You registered the
ExportFileplugin (the examples useregisterAllModules()).
Examples
Mind that CSV exports contain only raw data, and don’t include formulas, styling, or formatting information.
Examples 1-3 use hidden rows and hidden columns with indicators turned on. The indicators show where hidden data exists in the grid, and each example explains whether the export includes or skips that hidden data.
Export to file
This example exports all rows and columns, including hidden ones, by setting both exportHiddenRows and exportHiddenColumns to true.
import Handsontable from 'handsontable/base';import { registerAllModules } from 'handsontable/registry';// Register all Handsontable's modules.registerAllModules();const container = document.querySelector('#example1');const hot = new Handsontable(container, { data: [ ['Spring Launch', 'Email', 'North America', '1240', '4.2%', '$12000', 'Q1 2025'], ['Partner Webinar', 'Paid Search', 'EMEA', '860', '6.1%', '$9400', 'Q1 2025'], ['Summer Upsell', 'Social', 'APAC', '1520', '3.7%', '$13800', 'Q2 2025'], ['Product Video', 'Email', 'North America', '980', '5.4%', '$8600', 'Q2 2025'], ['Back-to-School', 'Display', 'LATAM', '1110', '4.8%', '$10100', 'Q3 2025'], ['Holiday Teaser', 'Affiliate', 'EMEA', '1340', '5.9%', '$12700', 'Q4 2025'], ['Loyalty Drive', 'SMS', 'APAC', '790', '7.3%', '$6200', 'Q4 2025'], ], colHeaders: true, rowHeaders: true, hiddenRows: { rows: [1, 3, 5], indicators: true }, hiddenColumns: { columns: [1, 3, 5], indicators: true }, height: 'auto', autoWrapRow: true, autoWrapCol: true, licenseKey: 'non-commercial-and-evaluation',});const exportPlugin = hot.getPlugin('exportFile');const button = document.querySelector('#export-file');button.addEventListener('click', () => { exportPlugin.downloadFile('csv', { bom: false, columnDelimiter: ',', colHeaders: false, exportHiddenColumns: true, exportHiddenRows: true, fileExtension: 'csv', filename: 'Handsontable-CSV-file_[YYYY]-[MM]-[DD]', mimeType: 'text/csv', rowDelimiter: '\r\n', rowHeaders: true, });});import Handsontable from 'handsontable/base';import { registerAllModules } from 'handsontable/registry';import { ExportFile } from 'handsontable/plugins';
// Register all Handsontable's modules.registerAllModules();
const container = document.querySelector('#example1')!;
const hot = new Handsontable(container, { data: [ ['Spring Launch', 'Email', 'North America', '1240', '4.2%', '$12000', 'Q1 2025'], ['Partner Webinar', 'Paid Search', 'EMEA', '860', '6.1%', '$9400', 'Q1 2025'], ['Summer Upsell', 'Social', 'APAC', '1520', '3.7%', '$13800', 'Q2 2025'], ['Product Video', 'Email', 'North America', '980', '5.4%', '$8600', 'Q2 2025'], ['Back-to-School', 'Display', 'LATAM', '1110', '4.8%', '$10100', 'Q3 2025'], ['Holiday Teaser', 'Affiliate', 'EMEA', '1340', '5.9%', '$12700', 'Q4 2025'], ['Loyalty Drive', 'SMS', 'APAC', '790', '7.3%', '$6200', 'Q4 2025'], ], colHeaders: true, rowHeaders: true, hiddenRows: { rows: [1, 3, 5], indicators: true }, hiddenColumns: { columns: [1, 3, 5], indicators: true }, height: 'auto', autoWrapRow: true, autoWrapCol: true, licenseKey: 'non-commercial-and-evaluation',});
const exportPlugin: ExportFile = hot.getPlugin('exportFile');
const button = document.querySelector('#export-file')!;
button.addEventListener('click', () => { exportPlugin.downloadFile('csv', { bom: false, columnDelimiter: ',', colHeaders: false, exportHiddenColumns: true, exportHiddenRows: true, fileExtension: 'csv', filename: 'Handsontable-CSV-file_[YYYY]-[MM]-[DD]', mimeType: 'text/csv', rowDelimiter: '\r\n', rowHeaders: true, });});<div class="example-controls-container"> <div class="controls"> <button id="export-file">Download CSV</button> </div></div><div id="example1"></div>Export as a JavaScript Blob object
Open a console in browser developer tools to see the result for the below example.
This example keeps hidden indicators in the grid, but exports only visible rows and columns by setting exportHiddenRows and exportHiddenColumns to false.
import Handsontable from 'handsontable/base';import { registerAllModules } from 'handsontable/registry';// Register all Handsontable's modules.registerAllModules();const container = document.querySelector('#example2');const hot = new Handsontable(container, { data: [ ['Spring Launch', 'Email', 'North America', '1240', '4.2%', '$12000', 'Q1 2025'], ['Partner Webinar', 'Paid Search', 'EMEA', '860', '6.1%', '$9400', 'Q1 2025'], ['Summer Upsell', 'Social', 'APAC', '1520', '3.7%', '$13800', 'Q2 2025'], ['Product Video', 'Email', 'North America', '980', '5.4%', '$8600', 'Q2 2025'], ['Back-to-School', 'Display', 'LATAM', '1110', '4.8%', '$10100', 'Q3 2025'], ['Holiday Teaser', 'Affiliate', 'EMEA', '1340', '5.9%', '$12700', 'Q4 2025'], ['Loyalty Drive', 'SMS', 'APAC', '790', '7.3%', '$6200', 'Q4 2025'], ], colHeaders: true, rowHeaders: true, hiddenRows: { rows: [1, 3, 5], indicators: true }, hiddenColumns: { columns: [1, 3, 5], indicators: true }, height: 'auto', autoWrapRow: true, autoWrapCol: true, licenseKey: 'non-commercial-and-evaluation',});const exportPlugin = hot.getPlugin('exportFile');const button = document.querySelector('#export-blob');button.addEventListener('click', () => { const exportedBlob = exportPlugin.exportAsBlob('csv', { bom: false, columnDelimiter: ',', colHeaders: false, exportHiddenColumns: false, exportHiddenRows: false, mimeType: 'text/csv', rowDelimiter: '\r\n', rowHeaders: true, }); console.log(exportedBlob);});import Handsontable from 'handsontable/base';import { registerAllModules } from 'handsontable/registry';import { ExportFile } from 'handsontable/plugins';
// Register all Handsontable's modules.registerAllModules();
const container = document.querySelector('#example2')!;
const hot = new Handsontable(container, { data: [ ['Spring Launch', 'Email', 'North America', '1240', '4.2%', '$12000', 'Q1 2025'], ['Partner Webinar', 'Paid Search', 'EMEA', '860', '6.1%', '$9400', 'Q1 2025'], ['Summer Upsell', 'Social', 'APAC', '1520', '3.7%', '$13800', 'Q2 2025'], ['Product Video', 'Email', 'North America', '980', '5.4%', '$8600', 'Q2 2025'], ['Back-to-School', 'Display', 'LATAM', '1110', '4.8%', '$10100', 'Q3 2025'], ['Holiday Teaser', 'Affiliate', 'EMEA', '1340', '5.9%', '$12700', 'Q4 2025'], ['Loyalty Drive', 'SMS', 'APAC', '790', '7.3%', '$6200', 'Q4 2025'], ], colHeaders: true, rowHeaders: true, hiddenRows: { rows: [1, 3, 5], indicators: true }, hiddenColumns: { columns: [1, 3, 5], indicators: true }, height: 'auto', autoWrapRow: true, autoWrapCol: true, licenseKey: 'non-commercial-and-evaluation',});
const exportPlugin: ExportFile = hot.getPlugin('exportFile');
const button = document.querySelector('#export-blob')!;
button.addEventListener('click', () => { const exportedBlob = exportPlugin.exportAsBlob('csv', { bom: false, columnDelimiter: ',', colHeaders: false, exportHiddenColumns: false, exportHiddenRows: false, mimeType: 'text/csv', rowDelimiter: '\r\n', rowHeaders: true, });
console.log(exportedBlob);});<div class="example-controls-container"> <div class="controls"> <button id="export-blob">Export as a Blob</button> </div></div><div id="example2"></div>Export as a string
Open a console in browser developer tools to see the result for the below example. Like the Blob example, this export uses only visible data and skips hidden rows and columns.
import Handsontable from 'handsontable/base';import { registerAllModules } from 'handsontable/registry';// Register all Handsontable's modules.registerAllModules();const container = document.querySelector('#example3');const hot = new Handsontable(container, { data: [ ['Spring Launch', 'Email', 'North America', '1240', '4.2%', '$12000', 'Q1 2025'], ['Partner Webinar', 'Paid Search', 'EMEA', '860', '6.1%', '$9400', 'Q1 2025'], ['Summer Upsell', 'Social', 'APAC', '1520', '3.7%', '$13800', 'Q2 2025'], ['Product Video', 'Email', 'North America', '980', '5.4%', '$8600', 'Q2 2025'], ['Back-to-School', 'Display', 'LATAM', '1110', '4.8%', '$10100', 'Q3 2025'], ['Holiday Teaser', 'Affiliate', 'EMEA', '1340', '5.9%', '$12700', 'Q4 2025'], ['Loyalty Drive', 'SMS', 'APAC', '790', '7.3%', '$6200', 'Q4 2025'], ], colHeaders: true, rowHeaders: true, hiddenRows: { rows: [1, 3, 5], indicators: true }, hiddenColumns: { columns: [1, 3, 5], indicators: true }, height: 'auto', autoWrapRow: true, autoWrapCol: true, licenseKey: 'non-commercial-and-evaluation',});const exportPlugin = hot.getPlugin('exportFile');const button = document.querySelector('#export-string');button.addEventListener('click', () => { const exportedString = exportPlugin.exportAsString('csv', { bom: false, columnDelimiter: ',', colHeaders: false, exportHiddenColumns: false, exportHiddenRows: false, rowDelimiter: '\r\n', rowHeaders: true, }); console.log(exportedString);});import Handsontable from 'handsontable/base';import { registerAllModules } from 'handsontable/registry';import { ExportFile } from 'handsontable/plugins';
// Register all Handsontable's modules.registerAllModules();
const container = document.querySelector('#example3')!;
const hot = new Handsontable(container, { data: [ ['Spring Launch', 'Email', 'North America', '1240', '4.2%', '$12000', 'Q1 2025'], ['Partner Webinar', 'Paid Search', 'EMEA', '860', '6.1%', '$9400', 'Q1 2025'], ['Summer Upsell', 'Social', 'APAC', '1520', '3.7%', '$13800', 'Q2 2025'], ['Product Video', 'Email', 'North America', '980', '5.4%', '$8600', 'Q2 2025'], ['Back-to-School', 'Display', 'LATAM', '1110', '4.8%', '$10100', 'Q3 2025'], ['Holiday Teaser', 'Affiliate', 'EMEA', '1340', '5.9%', '$12700', 'Q4 2025'], ['Loyalty Drive', 'SMS', 'APAC', '790', '7.3%', '$6200', 'Q4 2025'], ], colHeaders: true, rowHeaders: true, hiddenRows: { rows: [1, 3, 5], indicators: true }, hiddenColumns: { columns: [1, 3, 5], indicators: true }, height: 'auto', autoWrapRow: true, autoWrapCol: true, licenseKey: 'non-commercial-and-evaluation',});
const exportPlugin: ExportFile = hot.getPlugin('exportFile');
const button = document.querySelector('#export-string')!;
button.addEventListener('click', () => { const exportedString = exportPlugin.exportAsString('csv', { bom: false, columnDelimiter: ',', colHeaders: false, exportHiddenColumns: false, exportHiddenRows: false, rowDelimiter: '\r\n', rowHeaders: true, });
console.log(exportedString);});<div class="example-controls-container"> <div class="controls"> <button id="export-string">Export as a string</button> </div></div><div id="example3"></div>Prevent CSV injection attack
“CSV Injection, also known as Formula Injection, occurs when websites embed untrusted input inside CSV files. When a spreadsheet program such as Microsoft Excel or LibreOffice Calc is used to open a CSV, any cells starting with = will be interpreted by the software as a formula.” (from OWASP website)
To prevent this attack, set the sanitizeValues option when exporting your data in CSV format.
import Handsontable from 'handsontable/base';import { registerAllModules } from 'handsontable/registry';// Register all Handsontable's modules.registerAllModules();const container = document.querySelector('#example4');const hot = new Handsontable(container, { data: [ ['https://api.acme-inventory.com/live-stock', '=WEBSERVICE("https://api.acme-inventory.com/live-stock")'], ['https://status.vertex-logistics.com/feed', '=WEBSERVICE("https://status.vertex-logistics.com/feed")'], ['http://malicious.example/payload.exe', '=CMD("| calc.exe")'], ['https://news.example.com/q2-briefing', '=HYPERLINK("http://malicious.example","Open report")'], ['https://cdn.example.com/daily.csv', '+SUM(1,1)'], ], colHeaders: true, rowHeaders: true, height: 'auto', autoWrapRow: true, autoWrapCol: true, licenseKey: 'non-commercial-and-evaluation',});const exportPlugin = hot.getPlugin('exportFile');document.querySelector('#no-sanitization').addEventListener('click', () => { exportPlugin.downloadFile('csv', { bom: false, columnDelimiter: ',', colHeaders: false, exportHiddenColumns: true, exportHiddenRows: true, fileExtension: 'csv', filename: 'Handsontable-CSV-file_[YYYY]-[MM]-[DD]', mimeType: 'text/csv', rowDelimiter: '\r\n', });});document.querySelector('#recommended-sanitization').addEventListener('click', () => { exportPlugin.downloadFile('csv', { bom: false, columnDelimiter: ',', colHeaders: false, exportHiddenColumns: true, exportHiddenRows: true, fileExtension: 'csv', filename: 'Handsontable-CSV-file_[YYYY]-[MM]-[DD]', mimeType: 'text/csv', rowDelimiter: '\r\n', sanitizeValues: true, });});document.querySelector('#regexp-sanitization').addEventListener('click', () => { exportPlugin.downloadFile('csv', { bom: false, columnDelimiter: ',', colHeaders: false, exportHiddenColumns: true, exportHiddenRows: true, fileExtension: 'csv', filename: 'Handsontable-CSV-file_[YYYY]-[MM]-[DD]', mimeType: 'text/csv', rowDelimiter: '\r\n', sanitizeValues: /WEBSERVICE|CMD|HYPERLINK|^\+/, });});document.querySelector('#function-sanitization').addEventListener('click', () => { exportPlugin.downloadFile('csv', { bom: false, columnDelimiter: ',', colHeaders: false, exportHiddenColumns: true, exportHiddenRows: true, fileExtension: 'csv', filename: 'Handsontable-CSV-file_[YYYY]-[MM]-[DD]', mimeType: 'text/csv', rowDelimiter: '\r\n', sanitizeValues: (value) => { return /WEBSERVICE|CMD|HYPERLINK|^\+/.test(value) ? 'REMOVED SUSPICIOUS CELL CONTENT' : value; }, });});import Handsontable from 'handsontable/base';import { registerAllModules } from 'handsontable/registry';import { ExportFile } from 'handsontable/plugins';
// Register all Handsontable's modules.registerAllModules();
const container = document.querySelector('#example4')!;
const hot = new Handsontable(container, { data: [ ['https://api.acme-inventory.com/live-stock', '=WEBSERVICE("https://api.acme-inventory.com/live-stock")'], ['https://status.vertex-logistics.com/feed', '=WEBSERVICE("https://status.vertex-logistics.com/feed")'], ['http://malicious.example/payload.exe', '=CMD("| calc.exe")'], ['https://news.example.com/q2-briefing', '=HYPERLINK("http://malicious.example","Open report")'], ['https://cdn.example.com/daily.csv', '+SUM(1,1)'], ], colHeaders: true, rowHeaders: true, height: 'auto', autoWrapRow: true, autoWrapCol: true, licenseKey: 'non-commercial-and-evaluation',});
const exportPlugin: ExportFile = hot.getPlugin('exportFile');
document.querySelector('#no-sanitization')!.addEventListener('click', () => { exportPlugin.downloadFile('csv', { bom: false, columnDelimiter: ',', colHeaders: false, exportHiddenColumns: true, exportHiddenRows: true, fileExtension: 'csv', filename: 'Handsontable-CSV-file_[YYYY]-[MM]-[DD]', mimeType: 'text/csv', rowDelimiter: '\r\n', });});
document.querySelector('#recommended-sanitization')!.addEventListener('click', () => { exportPlugin.downloadFile('csv', { bom: false, columnDelimiter: ',', colHeaders: false, exportHiddenColumns: true, exportHiddenRows: true, fileExtension: 'csv', filename: 'Handsontable-CSV-file_[YYYY]-[MM]-[DD]', mimeType: 'text/csv', rowDelimiter: '\r\n', sanitizeValues: true, });});
document.querySelector('#regexp-sanitization')!.addEventListener('click', () => { exportPlugin.downloadFile('csv', { bom: false, columnDelimiter: ',', colHeaders: false, exportHiddenColumns: true, exportHiddenRows: true, fileExtension: 'csv', filename: 'Handsontable-CSV-file_[YYYY]-[MM]-[DD]', mimeType: 'text/csv', rowDelimiter: '\r\n', sanitizeValues: /WEBSERVICE|CMD|HYPERLINK|^\+/, });});
document.querySelector('#function-sanitization')!.addEventListener('click', () => { exportPlugin.downloadFile('csv', { bom: false, columnDelimiter: ',', colHeaders: false, exportHiddenColumns: true, exportHiddenRows: true, fileExtension: 'csv', filename: 'Handsontable-CSV-file_[YYYY]-[MM]-[DD]', mimeType: 'text/csv', rowDelimiter: '\r\n', sanitizeValues: (value) => { return /WEBSERVICE|CMD|HYPERLINK|^\+/.test(value) ? 'REMOVED SUSPICIOUS CELL CONTENT' : value; }, });});<div class="example-controls-container"> <div class="controls"> <button id="no-sanitization">Download CSV with no sanitization</button> <button id="recommended-sanitization">Download CSV with recommended sanitization</button> <button id="regexp-sanitization">Download CSV with sanitization using a regexp</button> <button id="function-sanitization">Download CSV with sanitization using a function</button> </div></div><div id="example4"></div>Export headers as plain text
A column header setting is also its display string. A header of '<b>Total</b>' renders as Total in the grid, but the export writes the value it was given, so the file receives the literal <b>Total</b>.
To export headers as the text the grid displays, set the textExtractor option to true:
const hot = new Handsontable(container, { data: [[1, 2]], colHeaders: ['<b>Total</b>', 'Count'], textExtractor: true,});
hot.getPlugin('exportFile').downloadFile('csv', { colHeaders: true });// The file receives: Total,CounttextExtractor is a grid option, not an export configuration option, so you set it alongside colHeaders rather than passing it to downloadFile(). In an export it applies to column headers, row headers, and nested headers, in both CSV and Excel. Copying to the clipboard uses it for column headers, which are the only headers a copy carries.
Cell data is left alone. A cell exports its value, never its rendered markup, so a value such as a<b reaches the file unchanged.
One thing to know before you enable it: extraction turns HTML entities back into the characters they stand for. A header stored as =SUM(A1) displays as =SUM(A1), and once extracted it reaches the file as =SUM(A1) too, which a spreadsheet reads as a formula. Set sanitizeValues to true when you export headers that come from untrusted sources.
Pass a function instead of true when you need different rules per surface:
textExtractor: (content, source) => { if (source === 'ExportFile.rowHeader') { return content; }
return stripMarkup(content);},Result
After completing this guide, you can export grid data as a downloadable CSV file, a JavaScript Blob, or a string. You can customize delimiters, ranges, headers, and value sanitization through the export configuration.
Available methods
The plugin exposes the following methods to export data.
downloadFile(format, options)- generates a downloadable file directly in the browser. Synchronous; supports text-based formats only (e.g. CSV). For XLSX, usedownloadFileAsync.downloadFileAsync(format, options)- generates a downloadable file and returns aPromise. Supports all formats including XLSX.exportAsBlob(format, options)- allows you to export a JavaScript Blob object.exportAsString(format, options)- allows you to export data as a string. Supports text-based formats only (e.g. CSV).
Each method takes two parameters. The first, format, is required. The second, options, is an optional object that overrides or extends the default export configuration. The table below lists all supported options for CSV export.
Available options in the export configuration
| Property | Type / Default | Description |
|---|---|---|
bom | Boolean, default true | Prepend output with BOM (UTF-8). Browser uses EF BB BF. |
colHeaders | Boolean, default false | Include column headers. Does not support the NestedHeaders plugin. |
columnDelimiter | String, default ',' | Column delimiter. |
exportHiddenColumns | Boolean, default false | Include hidden columns. |
exportHiddenRows | Boolean, default false | Include hidden rows. |
fileExtension | String, default 'csv' | File extension. Used by downloadFile(). |
filename | String, default 'Handsontable [YYYY]-[MM]-[DD]' | File name. Placeholders [YYYY], [MM], [DD] are replaced with the current date. Used by downloadFile(). |
mimeType | String, default 'text/csv' | MIME type. Used by downloadFile() and exportAsBlob(). |
range | Array, default [] | Cell range to export: [startRow, startColumn, endRow, endColumn] (visual indexes). |
rowDelimiter | String, default '\r\n' | Row delimiter. |
rowHeaders | Boolean, default false | Include row headers. |
sanitizeValues | Boolean | RegExp | Function, default false | Value sanitization. true = OWASP CSV injection rules; RegExp = escape matching values; Function = replace with return value. |
Related blog articles
Related API reference
Plugins