---
type: how-to
title: Migrating from 17.1 to 18.0
metaTitle: Migrating from 17.1 to 18.0 - JavaScript Data Grid | Handsontable
description: Migrate from Handsontable 17.1 to Handsontable 18.0 -- remove Moment.js, Pikaday, Numbro.js, and DOMPurify dependencies, and update TypeScript imports.
permalink: /migration-from-17.1-to-18.0
canonicalUrl: /migration-from-17.1-to-18.0
pageClass: migration-guide
react:
  metaTitle: Migrate from 17.1 to 18.0 - React Data Grid | Handsontable
angular:
  metaTitle: Migrate from 17.1 to 18.0 - Angular Data Grid | Handsontable
vue:
  metaTitle: Migrate from 17.1 to 18.0 - Vue Data Grid | Handsontable
searchCategory: Guides
category: Upgrade and migration
---

Migrate from Handsontable 17.1 to Handsontable 18.0.

For a detailed list of changes in this release, see the [Changelog](@/guides/upgrade-and-migration/changelog/changelog.md).

[[toc]]

## 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:

```typescript
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](@/guides/tools-and-building/modules/modules.md)).

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

| Type | New import |
| --- | --- |
| `GridSettings` | `handsontable` |
| `Events` | `handsontable` |
| `HotInstance` | `handsontable` |
| `ColumnSettings` | `handsontable` |
| `CellProperties` | `handsontable` |
| `CellMeta` | `handsontable` |
| `CellValue` | `handsontable` |
| `CellChange` | `handsontable` |
| `ChangeSource` | `handsontable` |
| `RowObject` | `handsontable` |
| `SourceRowData` | `handsontable` |
| `SelectOptionsObject` | `handsontable` |
| `RangeType` | `handsontable` |
| `CellCoords` | `handsontable` |
| `CellRange` | `handsontable` |
| `IndexMapper` | `handsontable` |
| `HooksRegistry` | `handsontable` |

**Before:**

```typescript
import type { RowObject, CellChange, GridSettings } from 'handsontable/common';
```

**After:**

```typescript
import type { RowObject, CellChange, GridSettings } from 'handsontable';
```

Or, if you use the base module with tree shaking:

```typescript
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"
```

::: tip

If you use `handsontable/base` throughout your project, replace `'handsontable'` in the replacement above with `'handsontable/base'`. Both entry points export the same set of types.

:::

For a full reference of all exported types and usage examples, see [TypeScript types](@/guides/tools-and-building/typescript-types/typescript-types.md).

## 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:

```javascript
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:**

```javascript
columns: [{
  type: 'numeric',
  numericFormat: {
    pattern: '0,0.00 $',
    culture: 'de-DE'
  }
}]
```

**After:**

```javascript
columns: [{
  type: 'numeric',
  locale: 'de-DE',
  numericFormat: {
    style: 'currency',
    currency: 'EUR',
    minimumFractionDigits: 2
  }
}]
```

Common `Intl.NumberFormat` style 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](@/guides/cell-types/numeric-cell-type/numeric-cell-type.md) guide and [MDN: Intl.NumberFormat](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat/NumberFormat#options).

## 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:**

```javascript
columns: [{
  type: 'date',
  dateFormat: 'DD/MM/YYYY'
}]

// Data (old format)
data: [
  { date: '15/05/2023' },
  { date: '22/09/2024' }
]
```

**After:**

```javascript
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:**

```javascript
columns: [{
  type: 'time',
  timeFormat: 'h:mm:ss a'
}]

// Data (old format)
data: [{ start: '2:30:00 PM' }]
```

**After:**

```javascript
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`](@/api/options.md#valueparser) or [`valueSetter`](@/api/options.md#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`](@/api/options.md#sanitizer) option. The function receives the raw HTML string and returns a sanitized string.

**Before (automatic DOMPurify behavior):**

```javascript
// 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):**

```javascript
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.

::: warning
If you render untrusted user HTML without a `sanitizer`, your users are exposed to XSS attacks. Always sanitize untrusted HTML content.
:::

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

| Method | Plugin |
| --- | --- |
| `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:

```javascript
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:**

```css
.ht-theme-main {
  --ht-wrapper-border-radius: 8px;
  --ht-wrapper-border-width: 1px;
  --ht-wrapper-border-color: #a0aec0;
}
```

**After:**

```css
.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:**

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

**After:**

```javascript
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):**

```html
<div class="ht-root-wrapper">
  <div class="ht-grid">
    <div class="ht-wrapper handsontable"></div>
  </div>
</div>
```

**After (18.0):**

```html
<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:**

```css
.ht-root-wrapper > .ht-grid > .handsontable {
  border: 1px solid #a0aec0;
}

.ht-root-wrapper > :first-child {
  margin-top: 0;
}
```

**After:**

```css
.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:**

```javascript
const grid = wrapper.querySelector('.ht-grid').firstElementChild;
```

**After:**

```javascript
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

| 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

- [TypeScript types](@/guides/tools-and-building/typescript-types/typescript-types.md)
- [Modules](@/guides/tools-and-building/modules/modules.md)
- [Numeric cell type](@/guides/cell-types/numeric-cell-type/numeric-cell-type.md)
- [Date cell type](@/guides/cell-types/date-cell-type/date-cell-type.md)
- [Time cell type](@/guides/cell-types/time-cell-type/time-cell-type.md)
- [Security](@/guides/security/security/security.md)

## Result

Your application now runs on Handsontable 18.0.
