Export to Excel
Export your grid data to an Excel (.xlsx) file, preserving cell types, styling, formulas, merged cells, and more.
Overview
The ExportFile plugin supports XLSX export via ExcelJS, which you must install as a peer dependency. XLSX export goes beyond CSV in several ways:
- Cell types (
numeric,date,time,checkbox,dropdown) are written as native Excel types with matching number formats. - Cell styling is read from the rendered DOM - font properties, background colors, alignment, and borders are all transferred to the workbook.
- Column headers and nested headers, merged cells, frozen panes, hidden rows, and hidden columns are preserved.
- HyperFormula and ColumnSummary destination cells can be exported as live Excel formulas.
- Multiple Handsontable instances can be exported into separate sheets of one workbook.
Prerequisites
Install ExcelJS. The supported version range is ^4.4.0 (ExcelJS 4.x, version 4.4.0 or later).
npm install exceljsPass the ExcelJS constructor as engines: { xlsx: ExcelJS } in the exportFile plugin configuration:
import ExcelJS from 'exceljs';
readonly hotSettings: GridSettings = { exportFile: { engines: { xlsx: ExcelJS } },};Example
The table below is a Q1 sales report that demonstrates the main XLSX export features: nested column headers, numeric and checkbox cell types, a column summary total, merged cells, a custom border, a frozen first column, and a cell comment on Alice’s row.
Click Export XLSX to download the file and open it in Microsoft Excel or any compatible spreadsheet application.
/* file: app.component.ts */import { Component, ViewChild } from '@angular/core';import { GridSettings, HotTableComponent } from '@handsontable/angular-wrapper';import ExcelJS from 'exceljs';
@Component({ selector: 'app-example1', template: ` <div class="example-controls-container"> <div class="controls"> <button (click)="exportFile()">Export XLSX</button> </div> </div>
<hot-table [settings]="hotSettings!" [data]="hotData" (afterInit)="onAfterInit()"> </hot-table> `, standalone: false,})export class AppComponent { @ViewChild(HotTableComponent, { static: false }) hotTable!: HotTableComponent;
readonly hotData = [ ['Alice Martin', 'North', 142000, true, 'Exceeded Q1 target by 18%.'], ['Bob Chen', 'East', 98500, true, 'Strong pipeline for Q2.'], ['Carol Davies', 'South', 76200, false, 'Needs coaching on closing.'], ['David Kim', 'West', 115300, true, 'Cross-sell opportunity.'], ['Eva Rossi', 'North', 54800, false, 'Sick leave impacted March.'], ['TOTALS', '', null, '', ''], ];
readonly hotSettings: GridSettings = { nestedHeaders: [ [ { label: 'Sales Representative', colspan: 2, headerClassName: 'htCenter' }, { label: 'Results', colspan: 2, headerClassName: 'htCenter' }, { label: 'Notes', colspan: 1, headerClassName: 'htLeft' }, ], ['Name', 'Region', 'Revenue ($)', 'Hit Target?', 'Notes'], ], columns: [ { type: 'text' }, { type: 'dropdown', source: ['North', 'South', 'East', 'West'] }, { type: 'numeric', locale: 'en-US', numericFormat: { style: 'currency', currency: 'USD', minimumFractionDigits: 2 }, }, { type: 'checkbox' }, { type: 'text' }, ], columnSummary: [ { sourceColumn: 2, destinationRow: 5, destinationColumn: 2, type: 'sum', forceNumeric: true, }, ], mergeCells: [{ row: 5, col: 0, rowspan: 1, colspan: 2 }], customBorders: [{ row: 5, col: 2, top: { width: 2, color: '#333333' } }], cell: [ { row: 5, col: 0, readOnly: true }, { row: 5, col: 2, readOnly: true }, ], fixedColumnsStart: 1, rowHeaders: true, colHeaders: false, height: 'auto', autoWrapRow: true, autoWrapCol: true, exportFile: { engines: { xlsx: ExcelJS } }, };
onAfterInit(): void { const hot = this.hotTable.hotInstance!;
hot.setCellMeta(0, 4, 'comment', { value: 'Top sales rep — review for promotion.' }); hot.render(); }
async exportFile(): Promise<void> { const exportPlugin = this.hotTable.hotInstance!.getPlugin('exportFile');
await exportPlugin.downloadFileAsync('xlsx', { filename: 'Q1-Sales-Report', columnHeaders: true, rowHeaders: true, exportFormulas: true, }); }}/* end-file */
/* file: app.module.ts */import { NgModule, ApplicationConfig } from '@angular/core';import { BrowserModule } from '@angular/platform-browser';import { registerAllModules } from 'handsontable/registry';import { HOT_GLOBAL_CONFIG, HotGlobalConfig, HotTableModule } from '@handsontable/angular-wrapper';import { CommonModule } from '@angular/common';import { NON_COMMERCIAL_LICENSE } from '@handsontable/angular-wrapper';
/* start:skip-in-compilation */import { AppComponent } from './app.component';/* end:skip-in-compilation */
registerAllModules();
export const appConfig: ApplicationConfig = { providers: [ { provide: HOT_GLOBAL_CONFIG, useValue: { license: NON_COMMERCIAL_LICENSE, } as HotGlobalConfig, }, ],};
@NgModule({ imports: [BrowserModule, HotTableModule, CommonModule], declarations: [AppComponent], providers: [...appConfig.providers], bootstrap: [AppComponent],})export class AppModule {}/* end-file */<div> <app-example1></app-example1></div>Available methods
The plugin exposes the following methods to export data.
downloadFileAsync(format, options)- generates a downloadable.xlsxfile directly in the browser. Returns aPromisethat resolves when the download starts.exportAsBlob(format, options)- exports the data as a JavaScriptBlob. Returns aPromisethat resolves with theBlob.
Both methods take two parameters. The first, format, must be 'xlsx'. The second, options, is an optional object that configures the exported workbook.
Both methods are asynchronous. Use await or .then() to handle the result:
async exportFile(): Promise<void> { const exportPlugin = this.hotTable.hotInstance!.getPlugin('exportFile');
await exportPlugin.downloadFileAsync('xlsx', { filename: 'my-report' });}Plugin configuration
Configure the plugin in Handsontable’s settings under the exportFile key.
| Option | Type | Default | Description |
|---|---|---|---|
engines | Object | - | A map of format keys to their engine constructors. Pass { xlsx: ExcelJS } to enable XLSX export via ExcelJS. |
Export options
Pass these options as the second argument to downloadFileAsync('xlsx', options) or exportAsBlob('xlsx', options).
| Option | Type / Default | Description |
|---|---|---|
filename | String, default 'Handsontable [YYYY]-[MM]-[DD]' | File name without extension. Placeholders [YYYY], [MM], and [DD] are replaced with the current date. |
columnHeaders | Boolean, default false | Include column headers in the exported file. Supports the NestedHeaders plugin. |
rowHeaders | Boolean, default false | Include row headers as a frozen first column in the exported file. |
exportHiddenColumns | Boolean | 'hide', default false | Controls how hidden columns are handled. true exports them as normal visible columns. false omits them entirely. 'hide' exports them and marks them as hidden in Excel, so their data is preserved but not shown. |
exportHiddenRows | Boolean | 'hide', default false | Controls how hidden rows are handled. true exports them as normal visible rows. false omits them entirely. 'hide' exports them and marks them as hidden in Excel, so their data is preserved but not shown. |
exportFormulas | Boolean, default false | Export HyperFormula cells and ColumnSummary destination cells as live Excel formulas instead of their pre-calculated values. |
sheets | Array, default [] | Multi-sheet configuration. Each entry is an object with an instance (a Handsontable object), a name (the sheet tab label), and any per-sheet options such as columnHeaders or rowHeaders. When provided, the top-level instance is ignored and each sheet is exported separately. |
compression | Boolean | Number (1–9), default false | Enable DEFLATE compression. true uses level 6. A number 1–9 sets a specific level (1 = fastest, 9 = smallest). |
conditionalFormatting | Array, default [] | Array of conditional formatting descriptors. Each descriptor accepts optional rows and cols ranges (zero-based Handsontable indexes) and a rules array of ExcelJS conditional formatting rule objects. |
range | Array, default [] | Cell range to export: [startRow, startColumn, endRow, endColumn] (visual indexes). When omitted, the entire grid is exported. |
Multi-sheet export
Use the sheets option to export multiple Handsontable instances into a single workbook. Each entry specifies the instance to read from and a name for the sheet tab.
/* file: app.component.ts */import { Component, ViewChild } from '@angular/core';import { GridSettings, HotTableComponent } from '@handsontable/angular-wrapper';import ExcelJS from 'exceljs';
@Component({ selector: 'app-example2', template: ` <div class="example-controls-container"> <div class="controls"> <button (click)="exportSheets()">Export XLSX</button> </div> </div>
<p><strong>Q1 Sales</strong></p> <hot-table [settings]="sharedSettings" [data]="q1Data"></hot-table>
<p><strong>Q2 Sales</strong></p> <hot-table #hotQ2 [settings]="sharedSettings" [data]="q2Data"></hot-table> `, standalone: false,})export class AppComponent { @ViewChild('hotQ2', { static: false }) hotQ2!: HotTableComponent; @ViewChild(HotTableComponent, { static: false }) hotQ1!: HotTableComponent;
readonly q1Data = [ ['Alice Martin', 'North', 142000, true ], ['Bob Chen', 'East', 98500, true ], ['Carol Davies', 'South', 76200, false], ['David Kim', 'West', 115300, true ], ['Eva Rossi', 'North', 54800, false], ];
readonly q2Data = [ ['Alice Martin', 'North', 158000, true ], ['Bob Chen', 'East', 112400, true ], ['Carol Davies', 'South', 89100, true ], ['David Kim', 'West', 97600, false], ['Eva Rossi', 'North', 63200, true ], ];
readonly sharedSettings: GridSettings = { columns: [ { type: 'text' }, { type: 'dropdown', source: ['North', 'South', 'East', 'West'] }, { type: 'numeric', locale: 'en-US', numericFormat: { style: 'currency', currency: 'USD', minimumFractionDigits: 2 }, }, { type: 'checkbox' }, ], colHeaders: ['Name', 'Region', 'Revenue ($)', 'Hit Target?'], rowHeaders: true, height: 'auto', autoWrapRow: true, autoWrapCol: true, exportFile: { engines: { xlsx: ExcelJS } }, };
async exportSheets(): Promise<void> { const hotQ1 = this.hotQ1.hotInstance!; const exportPlugin = hotQ1.getPlugin('exportFile');
await exportPlugin.downloadFileAsync('xlsx', { filename: 'Annual-Sales-Report', sheets: [ { instance: hotQ1, name: 'Q1 Sales', columnHeaders: true, rowHeaders: true }, { instance: this.hotQ2.hotInstance!, name: 'Q2 Sales', columnHeaders: true, rowHeaders: true }, ], }); }}/* end-file */
/* file: app.module.ts */import { NgModule, ApplicationConfig } from '@angular/core';import { BrowserModule } from '@angular/platform-browser';import { registerAllModules } from 'handsontable/registry';import { HOT_GLOBAL_CONFIG, HotGlobalConfig, HotTableModule } from '@handsontable/angular-wrapper';import { CommonModule } from '@angular/common';import { NON_COMMERCIAL_LICENSE } from '@handsontable/angular-wrapper';
/* start:skip-in-compilation */import { AppComponent } from './app.component';/* end:skip-in-compilation */
registerAllModules();
export const appConfig: ApplicationConfig = { providers: [ { provide: HOT_GLOBAL_CONFIG, useValue: { license: NON_COMMERCIAL_LICENSE, } as HotGlobalConfig, }, ],};
@NgModule({ imports: [BrowserModule, HotTableModule, CommonModule], declarations: [AppComponent], providers: [...appConfig.providers], bootstrap: [AppComponent],})export class AppModule {}/* end-file */<div> <app-example2></app-example2></div>Context menu
When the context menu is enabled, Export to CSV and Export to Excel items are automatically added to the grid’s context menu. No extra configuration in exportFile is needed.
The Export to Excel item is only shown when an ExcelJS engine is configured via engines: { xlsx: ExcelJS }. The Export to CSV item is always available.
When you select a cell range before opening the context menu, the export covers only the selected range. When no selection is active, the entire grid is exported.
/* file: app.component.ts */import { Component } from '@angular/core';import { GridSettings } from '@handsontable/angular-wrapper';import ExcelJS from 'exceljs';
@Component({ selector: 'app-example3', template: ` <div class="example-controls-container"> <p>Right-click any cell to open the context menu.</p> </div> <hot-table [settings]="hotSettings" [data]="hotData"></hot-table> `, standalone: false,})export class AppComponent { readonly hotData = [ ['Alice Martin', 'North', 142000, true ], ['Bob Chen', 'East', 98500, true ], ['Carol Davies', 'South', 76200, false], ['David Kim', 'West', 115300, true ], ['Eva Rossi', 'North', 54800, false], ];
readonly hotSettings: GridSettings = { columns: [ { type: 'text' }, { type: 'dropdown', source: ['North', 'South', 'East', 'West'] }, { type: 'numeric', locale: 'en-US', numericFormat: { style: 'currency', currency: 'USD', minimumFractionDigits: 2 }, }, { type: 'checkbox' }, ], colHeaders: ['Name', 'Region', 'Revenue ($)', 'Hit Target?'], rowHeaders: true, height: 'auto', autoWrapRow: true, autoWrapCol: true, contextMenu: true, exportFile: { engines: { xlsx: ExcelJS } }, };}/* end-file */
/* file: app.module.ts */import { NgModule, ApplicationConfig } from '@angular/core';import { BrowserModule } from '@angular/platform-browser';import { registerAllModules } from 'handsontable/registry';import { HOT_GLOBAL_CONFIG, HotGlobalConfig, HotTableModule } from '@handsontable/angular-wrapper';import { CommonModule } from '@angular/common';import { NON_COMMERCIAL_LICENSE } from '@handsontable/angular-wrapper';
/* start:skip-in-compilation */import { AppComponent } from './app.component';/* end:skip-in-compilation */
registerAllModules();
export const appConfig: ApplicationConfig = { providers: [ { provide: HOT_GLOBAL_CONFIG, useValue: { license: NON_COMMERCIAL_LICENSE, } as HotGlobalConfig, }, ],};
@NgModule({ imports: [BrowserModule, HotTableModule, CommonModule], declarations: [AppComponent], providers: [...appConfig.providers], bootstrap: [AppComponent],})export class AppModule {}/* end-file */<div> <app-example3></app-example3></div>Cell types and styling
The following Handsontable cell types are recognized and written to the .xlsx file with their native Excel equivalents.
| Handsontable type | Excel behavior |
|---|---|
numeric | Number cell. The numericFormat option is translated to an Excel numFmt string using Intl.NumberFormat. |
date | Date cell with an Excel date serial number. Reads ISO 8601 strings (YYYY-MM-DD). |
time | Time cell with an Excel time serial number. Reads HH:mm, HH:mm:ss, and 12-hour (h:mm AM/PM) formats. |
checkbox | Boolean cell (TRUE / FALSE). |
dropdown / autocomplete | Text cell. The validation list is not exported. |
| All others | Text cell. |
Cell styling is read from the rendered DOM at export time. The following properties are transferred to the workbook:
- Font: bold, italic, underline, strikethrough, color, size, and family.
- Fill: background color.
- Alignment: horizontal (
htLeft,htCenter,htRight,htJustify) and vertical (htTop,htMiddle,htBottom). - Borders: configurations set via the
CustomBordersplugin. Border widths map to Excel styles: 1 px →thin, 2 px →medium, 3+ px →thick.
Read-only cells (readOnly: true) receive a light-gray fill and gray font color in the exported file by default. Applying CSS classes to a read-only cell overrides these defaults.
Related API reference
Plugins