Migrating from 17.1 to 18.0
Migrate from Handsontable 17.1 to Handsontable 18.0.
For a detailed list of changes in this release, see the Changelog.
Replace
handsontable/commonimportsThe
handsontable/commonsubpath is removed in version 18.0. It was never part of the official public API and was not documented, but some projects imported TypeScript types from it directly.All types previously available at
handsontable/commonare now exported from the main entry points —handsontableandhandsontable/base. The new exports are a superset: every type that existed inhandsontable/commonis available in the new location.Who is affected
You are affected if your TypeScript source files contain any of these import patterns:
import type { ... } from 'handsontable/common';import { ... } from 'handsontable/common';How to migrate
Replace every
handsontable/commonimport with an import fromhandsontable(orhandsontable/baseif you use tree shaking).The table below lists the complete set of types that were available from
handsontable/commonand their new import location.Type New import GridSettingshandsontableEventshandsontableHotInstancehandsontableColumnSettingshandsontableCellPropertieshandsontableCellMetahandsontableCellValuehandsontableCellChangehandsontableChangeSourcehandsontableRowObjecthandsontableSourceRowDatahandsontableSelectOptionsObjecthandsontableRangeTypehandsontableCellCoordshandsontableCellRangehandsontableIndexMapperhandsontableHooksRegistryhandsontableBefore:
import type { RowObject, CellChange, GridSettings } from 'handsontable/common';After:
import type { RowObject, CellChange, GridSettings } from 'handsontable';Or, if you use the base module with tree shaking:
import type { RowObject, CellChange, GridSettings } from 'handsontable/base';Quick fix with search and replace
To migrate an entire project at once, run a global find-and-replace in your editor or IDE:
Find:
from 'handsontable/common'Replace:
from 'handsontable'If you use double quotes:
Find:
from "handsontable/common"Replace:
from "handsontable"For a full reference of all exported types and usage examples, see TypeScript types.
Migrate numeric format from Numbro.js
patternandculturetoIntl.NumberFormatHandsontable 18.0 removes the Numbro.js library. The
numericFormat.patternandnumericFormat.cultureoptions no longer work.Who is affected
You are affected if your Handsontable configuration uses
numericFormatwith apatternorcultureproperty:numericFormat: {pattern: '0,0.00 $',culture: 'de-DE'}How to migrate
Replace
patternandculturewithIntl.NumberFormatoptions, and move the locale to the top-levellocaleoption.Before:
columns: [{type: 'numeric',numericFormat: {pattern: '0,0.00 $',culture: 'de-DE'}}]After:
columns: [{type: 'numeric',locale: 'de-DE',numericFormat: {style: 'currency',currency: 'EUR',minimumFractionDigits: 2}}]Common
Intl.NumberFormatstyle values:Style Use case Required extra option 'decimal'Plain numbers with grouping separators — 'currency'Currency amounts currency: 'USD'(ISO 4217 code)'percent'Percentage values — 'unit'Physical measurements unit: 'kilometer'(unit identifier)For the full list of options, see the Numeric cell type guide and MDN: Intl.NumberFormat.
Migrate date and time cells to ISO 8601 format
Handsontable 18.0 removes Moment.js and Pikaday. The legacy
datecell type (moment.js-based stringdateFormat) and the legacytimecell type (moment.js-based stringtimeFormat) still accept thedateandtimekeys as aliases, but their underlying implementations now useIntl.DateTimeFormatand require ISO 8601 source data.Who is affected
You are affected if any of the following apply:
- Your data source stores dates in non-ISO format (for example,
'15/05/2023'or'May 15, 2023'). - You use the
datecell type with a stringdateFormat(for example,dateFormat: 'DD/MM/YYYY'). - You use the
timecell type with a stringtimeFormat(for example,timeFormat: 'h:mm:ss a'). - You use
datePickerConfig(Pikaday-specific options). - Your filters or sort operations rely on Moment.js to parse date strings.
How to migrate — date cells
Before:
columns: [{type: 'date',dateFormat: 'DD/MM/YYYY'}]// Data (old format)data: [{ date: '15/05/2023' },{ date: '22/09/2024' }]After:
columns: [{type: 'intl-date',locale: 'en-GB',dateFormat: {year: 'numeric',month: '2-digit',day: '2-digit'}}]// Data must be ISO 8601data: [{ date: '2023-05-15' },{ date: '2024-09-22' }]The
datekey is the canonical name. Theintl-datekey is an alias for backward compatibility and continues to work without a warning, so you can migrate the data format and display format independently.How to migrate — time cells
Before:
columns: [{type: 'time',timeFormat: 'h:mm:ss a'}]// Data (old format)data: [{ start: '2:30:00 PM' }]After:
columns: [{type: 'intl-time',locale: 'en-US',timeFormat: {hour: 'numeric',minute: '2-digit',second: '2-digit',hour12: true}}]// Data must be 24-hour formatdata: [{ start: '14:30:00' }]The
timekey is the canonical name. Theintl-timekey is an alias for backward compatibility and continues to work without a warning.Sorting and filtering dates
Filters and sort operations now require date values in ISO 8601 format (
YYYY-MM-DD). If your data source stored dates in another format, convert the values before passing them to Handsontable.correctFormatoptionThe
correctFormatoption (which auto-corrected entered date and time values to match a Moment.js format string) is removed. Thedate,time,intl-date, andintl-timecell types work with ISO 8601 values and do not reformat input. RemovecorrectFormatfrom your configuration. To normalize or correct entered values, use thevalueParserorvalueSetteroptions.datePickerConfigoptionThe
datePickerConfigoption (which passed options to Pikaday) no longer has any effect. Thedateandintl-datecell types use the browser’s native date input. RemovedatePickerConfigfrom your configuration.- Your data source stores dates in non-ISO format (for example,
Replace built-in DOMPurify with a custom sanitizer
Handsontable 18.0 removes the built-in DOMPurify dependency. HTML passed to the following surfaces is no longer sanitized automatically:
colHeadersandrowHeaders- Context menu item labels
- HTML pasted from the clipboard
- Dialog and notification content
- Select editor dropdown option values
Who is affected
You are affected if any of the following apply:
- You pass user-supplied or third-party HTML in
colHeaders,rowHeaders, context-menu labels, or select editorselectOptions. - You relied on Handsontable to strip
<script>tags or event handlers from HTML passed to those surfaces. - You use the
sanitizeroption or test sanitization behavior.
How to migrate
If you pass untrusted HTML to headers or other configuration options, supply your own sanitizer via the
sanitizeroption. The function receives the raw HTML string and returns a sanitized string.Before (automatic DOMPurify behavior):
// DOMPurify was applied automatically in 17.x -- no configuration needednew Handsontable(container, {colHeaders: ['<b>Name</b>', '<b>Score</b>', '<i>Status</i>']});After (explicit sanitizer required):
import DOMPurify from 'dompurify';new Handsontable(container, {sanitizer: (html) => DOMPurify.sanitize(html),colHeaders: ['<b>Name</b>', '<b>Score</b>', '<i>Status</i>']});You can use any sanitization library or write your own sanitizer function. The
sanitizeroption was introduced in version 17.0, so you can add it before upgrading to 18.0 to prepare your project.Stop calling deprecated resize-state methods
The following methods previously saved and loaded column or row sizes through the
PersistentStateplugin. BecausePersistentStatewas removed in Handsontable 17.0, these methods no longer do anything. They are now formally deprecated and will be removed in the next major release.Method Plugin saveManualColumnWidths()ManualColumnResizeloadManualColumnWidths()ManualColumnResizesaveManualRowHeights()ManualRowResizeloadManualRowHeights()ManualRowResizeWho is affected
You are affected if your code calls any of these methods directly on the plugin instance, for example:
hot.getPlugin('manualColumnResize').saveManualColumnWidths();hot.getPlugin('manualColumnResize').loadManualColumnWidths();hot.getPlugin('manualRowResize').saveManualRowHeights();hot.getPlugin('manualRowResize').loadManualRowHeights();How to migrate
Remove all calls to these methods. They produce no effect and will throw in a future release. If you need to persist column widths or row heights across page loads, store the
columnWidthsandrowHeightsvalues yourself — for example inlocalStorage— and pass them back as initial configuration when the grid initializes.Update theme wrapper border variables
Handsontable 18.0 simplifies the table border theme variables. The
--ht-wrapper-border-radiusvariable is renamed to--ht-border-radius(JS tokenborderRadius), and the--ht-wrapper-border-widthand--ht-wrapper-border-colorvariables (JS tokenswrapperBorderWidthandwrapperBorderColor) are removed. Their defaults were0pxand the base border color, so by default the table rendered no wrapper border.Who is affected
You are affected if any of the following apply:
- Your CSS overrides
--ht-wrapper-border-radius,--ht-wrapper-border-width, or--ht-wrapper-border-color. - You pass
wrapperBorderRadius,wrapperBorderWidth, orwrapperBorderColortokens tocreateTheme()orparams().
How to migrate
Rename the radius variable. Replace the removed width and color variables with a CSS
box-shadowon the table wrapper if you relied on a visible wrapper border.Before:
.ht-theme-main {--ht-wrapper-border-radius: 8px;--ht-wrapper-border-width: 1px;--ht-wrapper-border-color: #a0aec0;}After:
.ht-theme-main {--ht-border-radius: 8px;}/* Restore a visible wrapper border, if needed */.ht-theme-main .ht-root-wrapper {box-shadow: 0 0 0 1px #a0aec0;}For the JS theme API, rename
wrapperBorderRadiustoborderRadiusand removewrapperBorderWidthandwrapperBorderColor.Before:
createTheme('my-theme', {tokens: {wrapperBorderRadius: '8px',wrapperBorderWidth: '1px',wrapperBorderColor: ['colors.palette.200', 'colors.palette.700']}});After:
createTheme('my-theme', {tokens: {borderRadius: '8px'}});- Your CSS overrides
Update CSS selectors and DOM queries for the new wrapper structure
Handsontable 18.0 adds wrapper layout slots and a
layoutoption that controls the order of UI elements rendered around the grid, such as pagination and dialogs. To support this, the built-in UI now mounts into dedicated wrapper containers, which changes the DOM structure that Handsontable renders around the grid.The grid table itself is unchanged. Only the container elements between your root element and the grid table are different.
Who is affected
You are affected if any of the following apply:
- Your CSS targets Handsontable’s wrapper elements with direct-child (
>),:first-child,:only-child, or:nth-child()selectors. - Your JavaScript queries the DOM relative to
.ht-root-wrapperor.ht-grid(for example, withquerySelectororchildren). - You traverse the DOM from the grid root element with a fixed number of
parentNodeorfirstElementChildsteps.
What changed
Before (17.1):
<div class="ht-root-wrapper"><div class="ht-grid"><div class="ht-wrapper handsontable"></div></div></div>After (18.0):
<div class="ht-root-wrapper"><div class="ht-slot-top"></div><div class="ht-grid"><div class="ht-grid-content"><div class="ht-wrapper handsontable"></div></div></div><div class="ht-slot-bottom"></div><div class="ht-overlay"></div></div>Two changes can break existing selectors:
- A new
.ht-grid-contentelement wraps the grid root element inside.ht-grid. - New sibling containers (
.ht-slot-top,.ht-slot-bottom, and.ht-overlay) are added inside.ht-root-wrapper, so.ht-gridis no longer the only child.
How to migrate
Update selectors that assumed the old structure. Account for the new
.ht-grid-contentwrapper and the new sibling containers.Before:
.ht-root-wrapper > .ht-grid > .handsontable {border: 1px solid #a0aec0;}.ht-root-wrapper > :first-child {margin-top: 0;}After:
.ht-grid-content > .handsontable {border: 1px solid #a0aec0;}.ht-grid {margin-top: 0;}For DOM queries, target the grid root element by its class instead of by position:
Before:
const grid = wrapper.querySelector('.ht-grid').firstElementChild;After:
const grid = wrapper.querySelector('.ht-wrapper.handsontable');Use class-based selectors instead of positional ones, so future layout slot additions do not break your code.
- Your CSS targets Handsontable’s wrapper elements with direct-child (
Summary of breaking changes
| Change | Who is affected | Action required |
|---|---|---|
handsontable/common subpath removed | Any project importing TypeScript types from handsontable/common | Replace from 'handsontable/common' with from 'handsontable' (or from 'handsontable/base') |
numericFormat.pattern and numericFormat.culture removed | Projects using Numbro.js-based numeric formatting | Migrate to Intl.NumberFormat options; move locale to locale option |
| Moment.js and Pikaday removed; ISO 8601 required for date/time cells | Projects using non-ISO date strings or string dateFormat/timeFormat | Convert source data to ISO 8601; use intl-date/intl-time with object dateFormat/timeFormat |
correctFormat and datePickerConfig options removed | Projects using correctFormat or datePickerConfig on date/time cells | Remove both options; use valueParser/valueSetter for value correction |
| DOMPurify removed; no built-in HTML sanitization | Projects rendering untrusted user HTML | Add a sanitizer function (for example, using DOMPurify) to the Handsontable configuration |
saveManualColumnWidths(), loadManualColumnWidths(), saveManualRowHeights(), loadManualRowHeights() now no-op | Any project calling these methods | Remove calls; implement custom persistence if needed |
--ht-wrapper-border-radius renamed to --ht-border-radius; --ht-wrapper-border-width and --ht-wrapper-border-color removed | Projects overriding these theme variables or passing the matching JS tokens | Rename to --ht-border-radius / borderRadius; recreate a wrapper border with box-shadow if needed |
Wrapper DOM structure changed — new .ht-grid-content, .ht-slot-top, .ht-slot-bottom, and .ht-overlay elements | Projects with CSS selectors or DOM queries targeting Handsontable’s wrapper elements by position | Update selectors to be class-based; account for the new .ht-grid-content wrapper and sibling slot containers |
Related resources
Result
Your application now runs on Handsontable 18.0.