Skip to content

Undo and redo

Revert and restore your changes, using the undo and redo features.

Overview

The UndoRedo plugin records supported grid operations and stores them in undo and redo stacks.

You can use keyboard shortcuts, the context menu, or API methods to move backward and forward through that history.

The plugin is enabled by default. When the context menu is enabled, its default items include Undo and Redo.

Basic demo

Make a few edits in the grid.

Press Ctrl/+Z to undo your last action.

Press Ctrl/+Y (or Ctrl/+Shift+Z) to redo it.

JavaScript
import Handsontable from 'handsontable/base';
import { registerAllModules } from 'handsontable/registry';
// Register all Handsontable's modules.
registerAllModules();
const container = document.querySelector('#example');
new Handsontable(container, {
data: [
['Ana García', 'Engineering', 'Senior Engineer', 95000, '2026-03-14'],
['James Okafor', 'Marketing', 'Product Manager', 88000, '2026-07-01'],
['Li Wei', 'Engineering', 'Frontend Dev', 82000, '2026-01-10'],
['Maria Santos', 'HR', 'HR Specialist', 71000, '2026-11-20'],
['David Kim', 'Engineering', 'Backend Dev', 85000, '2026-08-05'],
['Emma Wilson', 'Marketing', 'SEO Analyst', 68000, '2026-02-14'],
['Ahmed Hassan', 'Finance', 'Controller', 92000, '2026-06-30'],
['Sara Johansson', 'Engineering', 'QA Engineer', 78000, '2026-09-12'],
['Carlos Mendez', 'Sales', 'Account Manager', 74000, '2026-04-25'],
],
rowHeaders: true,
colHeaders: true,
stretchH: 'all',
height: 'auto',
autoWrapRow: true,
autoWrapCol: true,
licenseKey: 'non-commercial-and-evaluation',
});
TypeScript
import Handsontable from 'handsontable/base';
import { registerAllModules } from 'handsontable/registry';
// Register all Handsontable's modules.
registerAllModules();
const container = document.querySelector('#example')!;
new Handsontable(container, {
data: [
['Ana García', 'Engineering', 'Senior Engineer', 95000, '2026-03-14'],
['James Okafor', 'Marketing', 'Product Manager', 88000, '2026-07-01'],
['Li Wei', 'Engineering', 'Frontend Dev', 82000, '2026-01-10'],
['Maria Santos', 'HR', 'HR Specialist', 71000, '2026-11-20'],
['David Kim', 'Engineering', 'Backend Dev', 85000, '2026-08-05'],
['Emma Wilson', 'Marketing', 'SEO Analyst', 68000, '2026-02-14'],
['Ahmed Hassan', 'Finance', 'Controller', 92000, '2026-06-30'],
['Sara Johansson', 'Engineering', 'QA Engineer', 78000, '2026-09-12'],
['Carlos Mendez', 'Sales', 'Account Manager', 74000, '2026-04-25'],
],
rowHeaders: true,
colHeaders: true,
stretchH: 'all',
height: 'auto',
autoWrapRow: true,
autoWrapCol: true,
licenseKey: 'non-commercial-and-evaluation',
});

What UndoRedo tracks

UndoRedo tracks operations that emit dedicated hooks and register an action in the plugin.

The built-in tracked actions include:

  • Cell value changes (beforeChange)
  • Row and column insertion/removal (afterCreateRow, afterCreateCol, beforeRemoveRow, beforeRemoveCol)
  • Column sorting (beforeColumnSort)
  • Filtering (beforeFilter)
  • Row and column moving (afterRowMove, afterColumnMove)
  • Cell moving and copying (beforeMoveCells, afterMoveCells), when the moveCells option is enabled
  • Merge and unmerge (beforeMergeCells, afterUnmergeCells)
  • Alignment changes (beforeCellAlignment)

Batch edits and multi-cell changes

For data edits, UndoRedo records only effective changes:

  • Entries nullified by other beforeChange hooks are skipped.
  • If a single cell changed, UndoRedo restores selection to that cell.
  • If multiple cells changed in one operation, UndoRedo restores the full selection range.

When you undo a data-change action, Handsontable can also remove rows or columns that were created as a side effect of that edit, and then restore the previous selection.

An undo puts each value back on the row you edited, even when that row moved or is no longer visible. If a filter or row trimming hides the row after your edit, Handsontable writes the value straight to the source data. Three things follow from that:

  • The beforeChange, afterChange, and modifyData hooks do not run for those values. afterSetSourceDataAtCell and modifySourceData run instead, so a listener that vetoes, rewrites, or normalizes edited values has to cover the source hooks as well.
  • The write does not re-run the filter, so the row stays hidden until you filter again.
  • Handsontable identifies the row by its position in the source data, not by a stable ID. If you remove rows without going through the undo stack, an undo recorded before that removal can land on a neighboring row.

Hooks and stack lifecycle

UndoRedo exposes hooks for both stack updates and action execution:

You can return false from beforeUndoStackChange, beforeUndo, or beforeRedo to block recording or execution.

Calling loadData() clears both stacks. Calling updateData() does not, so an undo that runs after it can restore values from the previous dataset. Call clear() yourself if you don’t want that. Disabling the plugin or destroying the grid also clears both stacks.

Programmatic control

Use the plugin instance to inspect and control history:

const undoRedo = hot.getPlugin('undoRedo');
if (undoRedo.isUndoAvailable()) {
undoRedo.undo();
}
if (undoRedo.isRedoAvailable()) {
undoRedo.redo();
}
undoRedo.clear();

Registering a custom undoable action

Use done() to add an action to the undo stack that isn’t tracked by default, such as a direct setCellMeta() update.

Call done() with a function that returns an action object. The action object needs an undo() method and a redo() method, each receiving the Handsontable instance and a callback to call once the operation finishes.

function setCellBackgroundColor(row, col, className) {
const undoRedo = hot.getPlugin('undoRedo');
const previousClassName = hot.getCellMeta(row, col).className;
undoRedo.done(() => ({
actionType: 'cellBackgroundColor',
undo(instance, callback) {
instance.setCellMeta(row, col, 'className', previousClassName);
instance.render();
callback();
},
redo(instance, callback) {
instance.setCellMeta(row, col, 'className', className);
instance.render();
callback();
},
}), 'cellBackgroundColor');
hot.setCellMeta(row, col, 'className', className);
hot.render();
}

After you call setCellBackgroundColor(), pressing Ctrl/+Z reverts the color change, and pressing Ctrl/+Y reapplies it.

Known limitations

UndoRedo does not record every possible operation.

The following operations are not tracked by default:

  • Column resizing and row resizing
  • Hiding columns and hiding rows
  • Trimming rows
  • Generic cell metadata changes that don’t register an UndoRedo action (for example, most direct setCellMeta() updates)
  • Merges applied from the mergeCells setting. UndoRedo tracks the merges and unmerges that run through the context menu, the merge keyboard shortcut, and the plugin’s merge() and unmerge() methods.
  • Rows and columns that Handsontable adds on its own to satisfy minRows, minCols, minSpareRows, or minSpareCols. These carry the auto source, which UndoRedo skips. It also skips the UndoRedo.undo and UndoRedo.redo sources, so its own operations never re-enter the stack.
WindowsmacOSActionExcelSheets
Ctrl+Z+ZUndo the last action
Ctrl+Y+YRedo the last action
Ctrl+Shift+Z++ZRedo the last action

Configuration options

Hooks

Plugins

Microsoft and Excel are registered trademarks of Microsoft Corporation. Google Sheets is a trademark of Google LLC.