Skip to content

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 ExportFile plugin (the examples use registerAllModules()).

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.

JavaScript
import { useRef } from 'react';
import { HotTable } from '@handsontable/react-wrapper';
import { registerAllModules } from 'handsontable/registry';
// register Handsontable's modules
registerAllModules();
const ExampleComponent = () => {
const hotRef = useRef(null);
const buttonClickCallback = () => {
const hot = hotRef.current?.hotInstance;
const exportPlugin = hot?.getPlugin('exportFile');
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,
});
};
return (<>
<div className="example-controls-container">
<div className="controls">
<button id="export-file" onClick={() => buttonClickCallback()}>
Download CSV
</button>
</div>
</div>
<HotTable ref={hotRef} 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"/>
</>);
};
export default ExampleComponent;
TypeScript
import { useRef } from 'react';
import { HotTable, HotTableRef } from '@handsontable/react-wrapper';
import { registerAllModules } from 'handsontable/registry';
// register Handsontable's modules
registerAllModules();
const ExampleComponent = () => {
const hotRef = useRef<HotTableRef>(null);
const buttonClickCallback = () => {
const hot = hotRef.current?.hotInstance;
const exportPlugin = hot?.getPlugin('exportFile');
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,
});
};
return (
<>
<div className="example-controls-container">
<div className="controls">
<button id="export-file" onClick={() => buttonClickCallback()}>
Download CSV
</button>
</div>
</div>
<HotTable
ref={hotRef}
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"
/>
</>
);
};
export default ExampleComponent;

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.

JavaScript
import { useRef } from 'react';
import { HotTable } from '@handsontable/react-wrapper';
import { registerAllModules } from 'handsontable/registry';
// register Handsontable's modules
registerAllModules();
const ExampleComponent = () => {
const hotRef = useRef(null);
const buttonClickCallback = () => {
const hot = hotRef.current?.hotInstance;
const exportPlugin = hot?.getPlugin('exportFile');
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);
};
return (<>
<div className="example-controls-container">
<div className="controls">
<button id="export-blob" onClick={() => buttonClickCallback()}>
Export as a Blob
</button>
</div>
</div>
<HotTable ref={hotRef} 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"/>
</>);
};
export default ExampleComponent;
TypeScript
import { useRef } from 'react';
import { HotTable, HotTableRef } from '@handsontable/react-wrapper';
import { registerAllModules } from 'handsontable/registry';
// register Handsontable's modules
registerAllModules();
const ExampleComponent = () => {
const hotRef = useRef<HotTableRef>(null);
const buttonClickCallback = () => {
const hot = hotRef.current?.hotInstance;
const exportPlugin = hot?.getPlugin('exportFile');
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);
};
return (
<>
<div className="example-controls-container">
<div className="controls">
<button id="export-blob" onClick={() => buttonClickCallback()}>
Export as a Blob
</button>
</div>
</div>
<HotTable
ref={hotRef}
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"
/>
</>
);
};
export default ExampleComponent;

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.

JavaScript
import { useRef } from 'react';
import { HotTable } from '@handsontable/react-wrapper';
import { registerAllModules } from 'handsontable/registry';
// register Handsontable's modules
registerAllModules();
const ExampleComponent = () => {
const hotRef = useRef(null);
const buttonClickCallback = () => {
const hot = hotRef.current?.hotInstance;
const exportPlugin = hot?.getPlugin('exportFile');
const exportedString = exportPlugin?.exportAsString('csv', {
bom: false,
columnDelimiter: ',',
colHeaders: false,
exportHiddenColumns: false,
exportHiddenRows: false,
rowDelimiter: '\r\n',
rowHeaders: true,
});
console.log(exportedString);
};
return (<>
<div className="example-controls-container">
<div className="controls">
<button id="export-string" onClick={() => buttonClickCallback()}>
Export as a string
</button>
</div>
</div>
<HotTable ref={hotRef} 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"/>
</>);
};
export default ExampleComponent;
TypeScript
import { useRef } from 'react';
import { HotTable, HotTableRef } from '@handsontable/react-wrapper';
import { registerAllModules } from 'handsontable/registry';
// register Handsontable's modules
registerAllModules();
const ExampleComponent = () => {
const hotRef = useRef<HotTableRef>(null);
const buttonClickCallback = () => {
const hot = hotRef.current?.hotInstance;
const exportPlugin = hot?.getPlugin('exportFile');
const exportedString = exportPlugin?.exportAsString('csv', {
bom: false,
columnDelimiter: ',',
colHeaders: false,
exportHiddenColumns: false,
exportHiddenRows: false,
rowDelimiter: '\r\n',
rowHeaders: true,
});
console.log(exportedString);
};
return (
<>
<div className="example-controls-container">
<div className="controls">
<button id="export-string" onClick={() => buttonClickCallback()}>
Export as a string
</button>
</div>
</div>
<HotTable
ref={hotRef}
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"
/>
</>
);
};
export default ExampleComponent;

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.

JavaScript
import { useRef } from 'react';
import { HotTable } from '@handsontable/react-wrapper';
import { registerAllModules } from 'handsontable/registry';
// register Handsontable's modules
registerAllModules();
const ExampleComponent = () => {
const hotRef = useRef(null);
const downloadWithNoSanitizationCallback = () => {
const hot = hotRef.current?.hotInstance;
const exportPlugin = hot?.getPlugin('exportFile');
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',
});
};
const downloadWithRecommendedSanitizationCallback = () => {
const hot = hotRef.current?.hotInstance;
const exportPlugin = hot?.getPlugin('exportFile');
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,
});
};
const downloadWithRegexpSanitizationCallback = () => {
const hot = hotRef.current?.hotInstance;
const exportPlugin = hot?.getPlugin('exportFile');
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|^\+/,
});
};
const downloadWithFunctionSanitizationCallback = () => {
const hot = hotRef.current?.hotInstance;
const exportPlugin = hot?.getPlugin('exportFile');
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;
},
});
};
return (<>
<div className="example-controls-container">
<div className="controls">
<button onClick={() => downloadWithNoSanitizationCallback()}>Download CSV with no sanitization</button>
<button onClick={() => downloadWithRecommendedSanitizationCallback()}>
Download CSV with recommended sanitization
</button>
<button onClick={() => downloadWithRegexpSanitizationCallback()}>
Download CSV with sanitization using a regexp
</button>
<button onClick={() => downloadWithFunctionSanitizationCallback()}>
Download CSV with sanitization using a function
</button>
</div>
</div>
<HotTable ref={hotRef} 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"/>
</>);
};
export default ExampleComponent;
TypeScript
import { useRef } from 'react';
import { HotTable, HotTableRef } from '@handsontable/react-wrapper';
import { registerAllModules } from 'handsontable/registry';
// register Handsontable's modules
registerAllModules();
const ExampleComponent = () => {
const hotRef = useRef<HotTableRef>(null);
const downloadWithNoSanitizationCallback = () => {
const hot = hotRef.current?.hotInstance;
const exportPlugin = hot?.getPlugin('exportFile');
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',
});
};
const downloadWithRecommendedSanitizationCallback = () => {
const hot = hotRef.current?.hotInstance;
const exportPlugin = hot?.getPlugin('exportFile');
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,
});
};
const downloadWithRegexpSanitizationCallback = () => {
const hot = hotRef.current?.hotInstance;
const exportPlugin = hot?.getPlugin('exportFile');
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|^\+/,
});
};
const downloadWithFunctionSanitizationCallback = () => {
const hot = hotRef.current?.hotInstance;
const exportPlugin = hot?.getPlugin('exportFile');
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;
},
});
};
return (
<>
<div className="example-controls-container">
<div className="controls">
<button onClick={() => downloadWithNoSanitizationCallback()}>Download CSV with no sanitization</button>
<button onClick={() => downloadWithRecommendedSanitizationCallback()}>
Download CSV with recommended sanitization
</button>
<button onClick={() => downloadWithRegexpSanitizationCallback()}>
Download CSV with sanitization using a regexp
</button>
<button onClick={() => downloadWithFunctionSanitizationCallback()}>
Download CSV with sanitization using a function
</button>
</div>
</div>
<HotTable
ref={hotRef}
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"
/>
</>
);
};
export default ExampleComponent;

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.

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

PropertyType / DefaultDescription
bomBoolean, default truePrepend output with BOM (UTF-8). Browser uses EF BB BF.
colHeadersBoolean, default falseInclude column headers. Does not support the NestedHeaders plugin.
columnDelimiterString, default ','Column delimiter.
exportHiddenColumnsBoolean, default falseInclude hidden columns.
exportHiddenRowsBoolean, default falseInclude hidden rows.
fileExtensionString, default 'csv'File extension. Used by downloadFile().
filenameString, default 'Handsontable [YYYY]-[MM]-[DD]'File name. Placeholders [YYYY], [MM], [DD] are replaced with the current date. Used by downloadFile().
mimeTypeString, default 'text/csv'MIME type. Used by downloadFile() and exportAsBlob().
rangeArray, default []Cell range to export: [startRow, startColumn, endRow, endColumn] (visual indexes).
rowDelimiterString, default '\r\n'Row delimiter.
rowHeadersBoolean, default falseInclude row headers.
sanitizeValuesBoolean | RegExp | Function, default falseValue sanitization. true = OWASP CSV injection rules; RegExp = escape matching values; Function = replace with return value.

Plugins