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.
import { HotTable } from '@handsontable/react-wrapper';import { registerAllModules } from 'handsontable/registry';
// register Handsontable's modulesregisterAllModules();
const ExampleComponent = () => { return ( <HotTable 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" /> );};
export default ExampleComponent;import { HotTable } from '@handsontable/react-wrapper';import { registerAllModules } from 'handsontable/registry';
// register Handsontable's modulesregisterAllModules();
const ExampleComponent = () => { return ( <HotTable 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" /> );};
export default ExampleComponent;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 themoveCellsoption 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
beforeChangehooks 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, andmodifyDatahooks do not run for those values.afterSetSourceDataAtCellandmodifySourceDatarun 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:
- Stack update hooks:
beforeUndoStackChange,afterUndoStackChange,beforeRedoStackChange, andafterRedoStackChange. - Action hooks:
beforeUndo,afterUndo,beforeRedo, andafterRedo.
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
mergeCellssetting. UndoRedo tracks the merges and unmerges that run through the context menu, the merge keyboard shortcut, and the plugin’smerge()andunmerge()methods. - Rows and columns that Handsontable adds on its own to satisfy
minRows,minCols,minSpareRows, orminSpareCols. These carry theautosource, which UndoRedo skips. It also skips theUndoRedo.undoandUndoRedo.redosources, so its own operations never re-enter the stack.
Related keyboard shortcuts
| Windows | macOS | Action | Excel | Sheets |
|---|---|---|---|---|
| Ctrl+Z | ⌘+Z | Undo the last action | ✓ | ✓ |
| Ctrl+Y | ⌘+Y | Redo the last action | ✓ | ✓ |
| Ctrl+Shift+Z | ⌘+⇧+Z | Redo the last action | ✓ | ✓ |
Related blog articles
Related API reference
Configuration options
Hooks
Plugins
Microsoft and Excel are registered trademarks of Microsoft Corporation. Google Sheets is a trademark of Google LLC.