Skip to content

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.

  1. Replace handsontable/common imports

    The handsontable/common subpath 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/common are now exported from the main entry points — handsontable and handsontable/base. The new exports are a superset: every type that existed in handsontable/common is 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/common import with an import from handsontable (or handsontable/base if you use tree shaking).

    The table below lists the complete set of types that were available from handsontable/common and their new import location.

    TypeNew import
    GridSettingshandsontable
    Eventshandsontable
    HotInstancehandsontable
    ColumnSettingshandsontable
    CellPropertieshandsontable
    CellMetahandsontable
    CellValuehandsontable
    CellChangehandsontable
    ChangeSourcehandsontable
    RowObjecthandsontable
    SourceRowDatahandsontable
    SelectOptionsObjecthandsontable
    RangeTypehandsontable
    CellCoordshandsontable
    CellRangehandsontable
    IndexMapperhandsontable
    HooksRegistryhandsontable

    Before:

    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.

  2. Migrate numeric format from Numbro.js pattern and culture to Intl.NumberFormat

    Handsontable 18.0 removes the Numbro.js library. The numericFormat.pattern and numericFormat.culture options no longer work.

    Who is affected

    You are affected if your Handsontable configuration uses numericFormat with a pattern or culture property:

    numericFormat: {
    pattern: '0,0.00 $',
    culture: 'de-DE'
    }

    How to migrate

    Replace pattern and culture with Intl.NumberFormat options, and move the locale to the top-level locale option.

    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.NumberFormat style values:

    StyleUse caseRequired extra option
    'decimal'Plain numbers with grouping separators
    'currency'Currency amountscurrency: 'USD' (ISO 4217 code)
    'percent'Percentage values
    'unit'Physical measurementsunit: 'kilometer' (unit identifier)

    For the full list of options, see the Numeric cell type guide and MDN: Intl.NumberFormat.

  3. Migrate date and time cells to ISO 8601 format

    Handsontable 18.0 removes Moment.js and Pikaday. The legacy date cell type (moment.js-based string dateFormat) and the legacy time cell type (moment.js-based string timeFormat) still accept the date and time keys as aliases, but their underlying implementations now use Intl.DateTimeFormat and 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 date cell type with a string dateFormat (for example, dateFormat: 'DD/MM/YYYY').
    • You use the time cell type with a string timeFormat (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 8601
    data: [
    { date: '2023-05-15' },
    { date: '2024-09-22' }
    ]

    The date key is the canonical name. The intl-date key 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 format
    data: [{ start: '14:30:00' }]

    The time key is the canonical name. The intl-time key 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.

    correctFormat option

    The correctFormat option (which auto-corrected entered date and time values to match a Moment.js format string) is removed. The date, time, intl-date, and intl-time cell types work with ISO 8601 values and do not reformat input. Remove correctFormat from your configuration. To normalize or correct entered values, use the valueParser or valueSetter options.

    datePickerConfig option

    The datePickerConfig option (which passed options to Pikaday) no longer has any effect. The date and intl-date cell types use the browser’s native date input. Remove datePickerConfig from your configuration.

  4. 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:

    • colHeaders and rowHeaders
    • 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 editor selectOptions.
    • You relied on Handsontable to strip <script> tags or event handlers from HTML passed to those surfaces.
    • You use the sanitizer option or test sanitization behavior.

    How to migrate

    If you pass untrusted HTML to headers or other configuration options, supply your own sanitizer via the sanitizer option. 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 needed
    new 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 sanitizer option was introduced in version 17.0, so you can add it before upgrading to 18.0 to prepare your project.

  5. Stop calling deprecated resize-state methods

    The following methods previously saved and loaded column or row sizes through the PersistentState plugin. Because PersistentState was 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.

    MethodPlugin
    saveManualColumnWidths()ManualColumnResize
    loadManualColumnWidths()ManualColumnResize
    saveManualRowHeights()ManualRowResize
    loadManualRowHeights()ManualRowResize

    Who 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 columnWidths and rowHeights values yourself — for example in localStorage — and pass them back as initial configuration when the grid initializes.

  6. Update theme wrapper border variables

    Handsontable 18.0 simplifies the table border theme variables. The --ht-wrapper-border-radius variable is renamed to --ht-border-radius (JS token borderRadius), and the --ht-wrapper-border-width and --ht-wrapper-border-color variables (JS tokens wrapperBorderWidth and wrapperBorderColor) are removed. Their defaults were 0px and 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, or wrapperBorderColor tokens to createTheme() or params().

    How to migrate

    Rename the radius variable. Replace the removed width and color variables with a CSS box-shadow on 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 wrapperBorderRadius to borderRadius and remove wrapperBorderWidth and wrapperBorderColor.

    Before:

    createTheme('my-theme', {
    tokens: {
    wrapperBorderRadius: '8px',
    wrapperBorderWidth: '1px',
    wrapperBorderColor: ['colors.palette.200', 'colors.palette.700']
    }
    });

    After:

    createTheme('my-theme', {
    tokens: {
    borderRadius: '8px'
    }
    });
  7. Update CSS selectors and DOM queries for the new wrapper structure

    Handsontable 18.0 adds wrapper layout slots and a layout option 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-wrapper or .ht-grid (for example, with querySelector or children).
    • You traverse the DOM from the grid root element with a fixed number of parentNode or firstElementChild steps.

    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-content element 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-grid is no longer the only child.

    How to migrate

    Update selectors that assumed the old structure. Account for the new .ht-grid-content wrapper 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.

Summary of breaking changes

ChangeWho is affectedAction required
handsontable/common subpath removedAny project importing TypeScript types from handsontable/commonReplace from 'handsontable/common' with from 'handsontable' (or from 'handsontable/base')
numericFormat.pattern and numericFormat.culture removedProjects using Numbro.js-based numeric formattingMigrate to Intl.NumberFormat options; move locale to locale option
Moment.js and Pikaday removed; ISO 8601 required for date/time cellsProjects using non-ISO date strings or string dateFormat/timeFormatConvert source data to ISO 8601; use intl-date/intl-time with object dateFormat/timeFormat
correctFormat and datePickerConfig options removedProjects using correctFormat or datePickerConfig on date/time cellsRemove both options; use valueParser/valueSetter for value correction
DOMPurify removed; no built-in HTML sanitizationProjects rendering untrusted user HTMLAdd a sanitizer function (for example, using DOMPurify) to the Handsontable configuration
saveManualColumnWidths(), loadManualColumnWidths(), saveManualRowHeights(), loadManualRowHeights() now no-opAny project calling these methodsRemove calls; implement custom persistence if needed
--ht-wrapper-border-radius renamed to --ht-border-radius; --ht-wrapper-border-width and --ht-wrapper-border-color removedProjects overriding these theme variables or passing the matching JS tokensRename 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 elementsProjects with CSS selectors or DOM queries targeting Handsontable’s wrapper elements by positionUpdate selectors to be class-based; account for the new .ht-grid-content wrapper and sibling slot containers

Result

Your application now runs on Handsontable 18.0.