Skip to content

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).

Terminal window
npm install exceljs

Pass 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.

TypeScript
/* 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 */
HTML
<div>
<app-example1></app-example1>
</div>

Available methods

The plugin exposes the following methods to export data.

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.

OptionTypeDefaultDescription
enginesObject-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).

OptionType / DefaultDescription
filenameString, default 'Handsontable [YYYY]-[MM]-[DD]'File name without extension. Placeholders [YYYY], [MM], and [DD] are replaced with the current date.
columnHeadersBoolean, default falseInclude column headers in the exported file. Supports the NestedHeaders plugin.
rowHeadersBoolean, default falseInclude row headers as a frozen first column in the exported file.
exportHiddenColumnsBoolean | 'hide', default falseControls 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.
exportHiddenRowsBoolean | 'hide', default falseControls 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.
exportFormulasBoolean, default falseExport HyperFormula cells and ColumnSummary destination cells as live Excel formulas instead of their pre-calculated values.
sheetsArray, 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.
compressionBoolean | Number (1–9), default falseEnable DEFLATE compression. true uses level 6. A number 1–9 sets a specific level (1 = fastest, 9 = smallest).
conditionalFormattingArray, 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.
rangeArray, 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.

TypeScript
/* 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 */
HTML
<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.

TypeScript
/* 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 */
HTML
<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 typeExcel behavior
numericNumber cell. The numericFormat option is translated to an Excel numFmt string using Intl.NumberFormat.
dateDate cell with an Excel date serial number. Reads ISO 8601 strings (YYYY-MM-DD).
timeTime cell with an Excel time serial number. Reads HH:mm, HH:mm:ss, and 12-hour (h:mm AM/PM) formats.
checkboxBoolean cell (TRUE / FALSE).
dropdown / autocompleteText cell. The validation list is not exported.
All othersText 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 CustomBorders plugin. 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.

Plugins