---
title: "Undo / Redo Edits"
framework: vue
version: "36.1.0"
---

# Undo / Redo Edits

This section covers how to allow users to undo / redo their cell edits.

When [Cell Editing](https://www.ag-grid.com/vue-data-grid/cell-editing/) is enabled in the grid, it is usually desirable to allow users to undo / redo any edits.

Users can change the contents of cells through the following grid features:

- [Cell Editing](https://www.ag-grid.com/vue-data-grid/cell-editing/)
- [Copy / Paste](https://www.ag-grid.com/vue-data-grid/clipboard/)
- [Fill Handle](https://www.ag-grid.com/vue-data-grid/cell-selection-fill-handle/)

> **Note**
>
> This Undo / Redo feature is designed to be a recovery mechanism for user editing mistakes. Performing data updates (except for cell edits), or grid operations that change the row / column order, e.g. sorting, filtering and grouping, will clear the undo / redo stacks.

## Enabling Undo / Redo

The following undo / redo properties are provided in the grid options interface:

```ts
<ag-grid-vue
    :undoRedoCellEditing="undoRedoCellEditing"
    :undoRedoCellEditingLimit="undoRedoCellEditingLimit"
    /* other grid options ... */>
</ag-grid-vue>

this.undoRedoCellEditing = true;
this.undoRedoCellEditingLimit = 20;
```

As shown in the snippet above, undo / redo is enabled through the `undoRedoCellEditing` property.

The default number of undo / redo steps is `10`. To change this default the `undoRedoCellEditingLimit` property can be used.

## Undo / Redo Shortcuts

The following keyboard shortcuts are available when undo / redo is enabled:

- `^ Ctrl`+`Z`: will undo the last cell edit(s).
- : will redo the last undo.

Note that the grid needs focus for these shortcuts to have an effect.

## Undo / Redo API

It is also possible to programmatically control undo / redo and check the number of currently available undo / redo actions. These API methods are listed below:

| Property | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `undoCellEditing` | `Function` |  |  | Reverts the last cell edit. Module: [`UndoRedoEditModule`](https://www.ag-grid.com/vue-data-grid/modules/). |
| `redoCellEditing` | `Function` |  |  | Re-applies the most recently undone cell edit. Module: [`UndoRedoEditModule`](https://www.ag-grid.com/vue-data-grid/modules/). |
| `getCurrentUndoSize` | `Function` |  |  | Returns current number of available cell edit undo operations. Module: [`UndoRedoEditModule`](https://www.ag-grid.com/vue-data-grid/modules/). |
| `getCurrentRedoSize` | `Function` |  |  | Returns current number of available cell edit redo operations. Module: [`UndoRedoEditModule`](https://www.ag-grid.com/vue-data-grid/modules/). |

## Undo / Redo Events

The following events are relevant to undo / redo:

| Property | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `cellValueChanged` | `CellValueChangedEvent` |  |  | Cell value has changed. This occurs after the following scenarios: - Editing. Will not fire if any of the following are true: new value is the same as old value; `readOnlyEdit = true`; editing was cancelled (e.g. Escape key was pressed); or new value is of the wrong cell data type for the column. - Cut. - Paste. - Cell clear (pressing Delete key). - Fill handle. - Copy range down. - Undo and redo. |
| `undoStarted` | `UndoStartedEvent` |  |  | Undo operation has started. |
| `undoEnded` | `UndoEndedEvent` |  |  | Undo operation has ended. |
| `redoStarted` | `RedoStartedEvent` |  |  | Redo operation has started. |
| `redoEnded` | `RedoEndedEvent` |  |  | Redo operation has ended. |

For an undo / redo, the events will be fired as:

1. One `undoStarted` / `redoStarted` event.
2. Zero to many `cellValueChanged` events.
3. One `undoEnded` / `redoEnded` event.

When there are no undo / redo operations to perform, the started and ended events will still fire. However, the ended event will have a value of `false` for the `operationPerformed` property (compared to `true` when an operation was performed).

If the application is doing work each time it receives a `cellValueChanged` event, you can use the `undoStarted` / `redoStarted` and `undoEnded` / `redoEnded` events to suspend the application's work and then do the work for all cells impacted by the undo / redo operation afterwards.

If [Read Only Edit](https://www.ag-grid.com/vue-data-grid/value-setters/#read-only-edit) is enabled, undo / redo will not perform any operations. The started and ended events will still fire, which means that you can implement your own undo / redo by keeping track of the `cellEditRequest` events.

## Example: Undo / Redo

The example below has the following grid options enabled to demonstrate undo / redo:

```ts
<ag-grid-vue
    :defaultColDef="defaultColDef"
    :cellSelection="cellSelection"
    :undoRedoCellEditing="undoRedoCellEditing"
    :undoRedoCellEditingLimit="undoRedoCellEditingLimit"
    /* other grid options ... */>
</ag-grid-vue>

this.defaultColDef = {
    // makes all cells editable
    editable: true,
    // enables flashing to help see cell changes
    enableCellChangeFlash: true,
};
// allows copy / paste using cell ranges
this.cellSelection = {
    // enables the fill handle
    handle: {
        mode: 'fill',
    }
};
// enables undo / redo
this.undoRedoCellEditing = true;
// restricts the number of undo / redo steps to 5
this.undoRedoCellEditingLimit = 5;
```

To see undo / redo in action, try the following:

- **Cell Editing**: click and edit some cell values.
- **Fill Handle**: drag the fill handle to change a range of cells.
- **Copy / Paste**: use `^ Ctrl`+`C` / `^ Ctrl`+`V` to copy and paste a range of cells.
- **Undo Shortcut**: use `^ Ctrl`+`Z` to undo the cell edits.
- **Redo Shortcut**: use  to redo the undone cell edits.
- **Undo API**: use the 'Undo' button to invoke `gridApi.undoCellEditing()`.
- **Redo API**: use the 'Redo' button to invoke `gridApi.redoCellEditing()`.
- **Undo / Redo Limit**: only 5 actions are allowed as `undoRedoCellEditingLimit=5`.

#### Undo / Redo

```ts
import {
  createApp,
  defineComponent,
  onBeforeMount,
  ref,
  shallowRef,
} from "vue";
import { AgGridVue } from "ag-grid-vue3";
import "./styles.css";
import {
  BatchEditingStartedEvent,
  BatchEditingStoppedEvent,
  BulkEditingStartedEvent,
  BulkEditingStoppedEvent,
  CellSelectionOptions,
  CellValueChangedEvent,
  ClientSideRowModelModule,
  ColDef,
  ColGroupDef,
  GridApi,
  GridOptions,
  GridReadyEvent,
  HighlightChangesModule,
  ModuleRegistry,
  RedoEndedEvent,
  RedoStartedEvent,
  TextEditorModule,
  UndoEndedEvent,
  UndoRedoEditModule,
  UndoStartedEvent,
  enableDevValidations,
} from "ag-grid-community";
import { CellSelectionModule, ClipboardModule } from "ag-grid-enterprise";
if (process.env.NODE_ENV !== "production") {
  enableDevValidations();
}

ModuleRegistry.registerModules([
  UndoRedoEditModule,
  TextEditorModule,
  HighlightChangesModule,
  ClientSideRowModelModule,
  ClipboardModule,
  CellSelectionModule,
]);

function updateCounters(api: GridApi) {
  const undoSize = api.getCurrentUndoSize();
  setValue("#undoInput", undoSize);
  disable("#undoBtn", undoSize < 1);
  const redoSize = api.getCurrentRedoSize();
  setValue("#redoInput", redoSize);
  disable("#redoBtn", redoSize < 1);
}

function disable(id: string, disabled: boolean) {
  (document.querySelector(id) as any).disabled = disabled;
}

function setValue(id: string, value: number) {
  (document.querySelector(id) as any).value = value;
}

function getRows() {
  return Array.apply(null, Array(100)).map(function (_, i) {
    return {
      a: "a-" + i,
      b: "b-" + i,
      c: "c-" + i,
      d: "d-" + i,
      e: "e-" + i,
      f: "f-" + i,
      g: "g-" + i,
      h: "h-" + i,
    };
  });
}

const VueExample = defineComponent({
  template: `
        <div style="height: 100%">
                <div class="example-wrapper">
      <div>
        <span class="button-group">
          <label>Available Undo's</label>
          <input id="undoInput" class="undo-redo-input">
            <label>Available Redo's</label>
            <input id="redoInput" class="undo-redo-input">
              <button id="undoBtn" class="undo-btn" v-on:click="undo()">Undo</button>
              <button id="redoBtn" class="redo-btn" v-on:click="redo()">Redo</button>
            </span>
          </div>
          <ag-grid-vue
            style="width: 100%; height: 100%;"
            @grid-ready="onGridReady"
            :columnDefs="columnDefs"
            :defaultColDef="defaultColDef"
            :rowData="rowData"
            :cellSelection="cellSelection"
            :undoRedoCellEditing="true"
            :undoRedoCellEditingLimit="undoRedoCellEditingLimit"
            @first-data-rendered="onFirstDataRendered"
            @bulk-editing-started="onBulkEditingStarted"
            @bulk-editing-stopped="onBulkEditingStopped"
            @batch-editing-started="onBatchEditingStarted"
            @batch-editing-stopped="onBatchEditingStopped"
            @cell-value-changed="onCellValueChanged"
            @undo-started="onUndoStarted"
            @undo-ended="onUndoEnded"
            @redo-started="onRedoStarted"
            @redo-ended="onRedoEnded"></ag-grid-vue>
          </div>
        </div>
    `,
  components: {
    "ag-grid-vue": AgGridVue,
  },
  setup(props) {
    const gridApi = shallowRef<GridApi | null>(null);
    const columnDefs = ref<ColDef[]>([
      { field: "a" },
      { field: "b" },
      { field: "c" },
      { field: "d" },
      { field: "e" },
      { field: "f" },
      { field: "g" },
      { field: "h" },
    ]);
    const defaultColDef = ref<ColDef>({
      flex: 1,
      editable: true,
      enableCellChangeFlash: true,
    });
    const rowData = ref<any[] | null>(getRows());
    const cellSelection = ref<boolean | CellSelectionOptions>({
      handle: {
        mode: "fill",
      },
    });
    const undoRedoCellEditingLimit = ref(5);

    function onFirstDataRendered() {
      setValue("#undoInput", 0);
      disable("#undoInput", true);
      disable("#undoBtn", true);
      setValue("#redoInput", 0);
      disable("#redoInput", true);
      disable("#redoBtn", true);
    }
    function onBulkEditingStarted(event: BulkEditingStartedEvent) {
      console.log("bulkEditingStarted", event);
      updateCounters(event.api);
    }
    function onBulkEditingStopped(event: BulkEditingStoppedEvent) {
      console.log("bulkEditingStopped", event);
      updateCounters(event.api);
    }
    function onBatchEditingStarted(event: BatchEditingStartedEvent) {
      console.log("batchEditingStarted", event);
      updateCounters(event.api);
    }
    function onBatchEditingStopped(event: BatchEditingStoppedEvent) {
      console.log("batchEditingStopped", event);
      updateCounters(event.api);
    }
    function onCellValueChanged(params: CellValueChangedEvent) {
      console.log("cellValueChanged", params);
      updateCounters(params.api);
    }
    function onUndoStarted(event: UndoStartedEvent) {
      console.log("undoStarted", event);
    }
    function onUndoEnded(event: UndoEndedEvent) {
      console.log("undoEnded", event);
    }
    function onRedoStarted(event: RedoStartedEvent) {
      console.log("redoStarted", event);
    }
    function onRedoEnded(event: RedoEndedEvent) {
      console.log("redoEnded", event);
    }
    function undo() {
      gridApi.value!.undoCellEditing();
    }
    function redo() {
      gridApi.value!.redoCellEditing();
    }
    const onGridReady = (params: GridReadyEvent) => {
      gridApi.value = params.api;
    };

    return {
      gridApi,
      columnDefs,
      defaultColDef,
      rowData,
      cellSelection,
      undoRedoCellEditingLimit,
      onGridReady,
      onFirstDataRendered,
      onBulkEditingStarted,
      onBulkEditingStopped,
      onBatchEditingStarted,
      onBatchEditingStopped,
      onCellValueChanged,
      onUndoStarted,
      onUndoEnded,
      onRedoStarted,
      onRedoEnded,
      undo,
      redo,
    };
  },
});

const app = createApp(VueExample);
app.mount("#app");
```

[Live example: Undo / Redo](https://www.ag-grid.com/examples/undo-redo-edits/undo-redo/vue3)

## Complex Objects

If your cell values contain complex objects, there are a few steps necessary for undo / redo to work.

For manual editing, a [Value Parser](https://www.ag-grid.com/vue-data-grid/value-parsers/) is required to convert string values back into complex objects.

```ts
<ag-grid-vue
    :columnDefs="columnDefs"
    /* other grid options ... */>
</ag-grid-vue>

this.columnDefs = [
    {
        field: 'a',
        editable: true,
        valueParser: params => {
            // convert `params.newValue` string value into complex object
            return {
                actualValue: params.newValue,
                anotherProperty: params.data.anotherProperty,
            }
        }
    }
];
```

If a [Value Getter](https://www.ag-grid.com/vue-data-grid/value-getters/) is being used to create complex objects, a [Value Setter](https://www.ag-grid.com/vue-data-grid/value-setters/) must be used to update the data. `colDef.equals` is also needed when [Comparing Values](https://www.ag-grid.com/vue-data-grid/change-detection/#comparing-values) to determine if the cell value has changed for rendering.

```ts
<ag-grid-vue
    :columnDefs="columnDefs"
    /* other grid options ... */>
</ag-grid-vue>

this.columnDefs = [
    {
        field: 'a',
        editable: true,
        valueGetter: params => {
            // create complex object from data
            return {
                actualValue: params.data[params.colDef.field],
                anotherProperty: params.data.anotherProperty,
            }
        },
        valueSetter: params => {
            // update data from complex object
            params.data[params.colDef.field] = params.newValue.actualValue
            return true
        },
        equals: (valueA, valueB) => {
            // compare complex objects
            return valueA.actualValue === valueB.actualValue
        }
    }
];
```

Complex object cell values must be immutable. If the cell values are mutated, undo / redo will not be able to restore the original values. This means that the Value Parser must return a new complex object.

Using the [Cell Data Type](https://www.ag-grid.com/vue-data-grid/cell-data-types/) `object` presets many of the grid features to allow complex objects to work without further configuration by leveraging the [Value Formatter](https://www.ag-grid.com/vue-data-grid/value-formatters/) and Value Parser.

The following example demonstrates how to use complex objects with undo / redo.

- For column **A**:
  - A Value Getter is used to create complex objects from the data.
  - A Value Formatter is used to convert the complex objects into strings for rendering.
  - A Value Setter is used to update the data from the complex objects (the inverse of the Value Getter).
  - A Value Parser is used to convert the string values produced from cell editing into complex objects (the inverse of the Value Formatter).
  - A Column Definition `equals` function is provided to compare the complex objects (without this the grid would use reference equality, but this won't work here as the Value Getter returns a new object each time).
- For column **B**:
  - The column values are complex objects.
  - A Value Formatter is used to convert the complex objects into strings for rendering.
  - A Value Parser is used to convert the string values produced from cell editing into complex objects (the inverse of the Value Formatter).
- For all columns:
  - The cell data type is set to `object` to allow other grid features to work, such as the fill handle, copy, paste, etc.
- Try the following actions:
  - **Cell Editing**: click and edit some cell values.
  - **Fill Handle**: drag the fill handle to change a range of cells.
  - **Copy / Paste**: use `^ Ctrl`+`C` / `^ Ctrl`+`V` to copy and paste a range of cells.
  - **Undo Shortcut**: use `^ Ctrl`+`Z` to undo the cell edits.
  - **Redo Shortcut**: use  to redo the undone cell edits.
  - **Undo API**: use the 'Undo' button to invoke `gridApi.undoCellEditing()`.
  - **Redo API**: use the 'Redo' button to invoke `gridApi.redoCellEditing()`.
  - **Undo / Redo Limit**: only 5 actions are allowed as `undoRedoCellEditingLimit=5`.

#### Undo / Redo with Complex Objects

```ts
import {
  createApp,
  defineComponent,
  onBeforeMount,
  ref,
  shallowRef,
} from "vue";
import { AgGridVue } from "ag-grid-vue3";
import "./styles.css";
import {
  CellSelectionOptions,
  CellValueChangedEvent,
  ClientSideRowModelModule,
  ColDef,
  ColGroupDef,
  GridApi,
  GridOptions,
  GridReadyEvent,
  HighlightChangesModule,
  ModuleRegistry,
  TextEditorModule,
  UndoRedoEditModule,
  ValueFormatterParams,
  ValueGetterParams,
  ValueParserParams,
  ValueSetterParams,
  enableDevValidations,
} from "ag-grid-community";
import { CellSelectionModule, ClipboardModule } from "ag-grid-enterprise";
if (process.env.NODE_ENV !== "production") {
  enableDevValidations();
}

ModuleRegistry.registerModules([
  UndoRedoEditModule,
  TextEditorModule,
  HighlightChangesModule,
  ClientSideRowModelModule,
  ClipboardModule,
  CellSelectionModule,
]);

function createValueA(value: string, data: any) {
  return value == null
    ? null
    : {
        actualValueA: value,
        anotherPropertyA: data.anotherPropertyA,
      };
}

function valueFormatterA(params: ValueFormatterParams) {
  // Convert complex object to string
  return params.value ? params.value.actualValueA : "";
}

function valueGetterA(params: ValueGetterParams) {
  // Create complex object from underlying data
  return createValueA(params.data[params.colDef.field!], params.data);
}

function valueParserA(params: ValueParserParams) {
  // Convert string `newValue` back into complex object (reverse of `valueFormatterA`). `newValue` is string.
  // We have access to `data` (as well as `oldValue`) to retrieve any other properties we need to recreate the complex object.
  // For undo/redo to work, we need immutable data, so can't mutate `oldValue`
  return createValueA(params.newValue, params.data);
}

function valueSetterA(params: ValueSetterParams) {
  // Update data from complex object (reverse of `valueGetterA`)
  params.data[params.colDef.field!] = params.newValue
    ? params.newValue.actualValueA
    : null;
  return true;
}

function equalsA(valueA: any, valueB: any) {
  // Used to detect whether cell value has changed for refreshing. Needed as `valueGetter` returns different references.
  return (
    (valueA == null && valueB == null) ||
    (valueA != null &&
      valueB != null &&
      valueA.actualValueA === valueB.actualValueA)
  );
}

function createValueB(value: string, data: any) {
  return value == null
    ? null
    : {
        actualValueB: value,
        anotherPropertyB: data.anotherPropertyB,
      };
}

function valueFormatterB(params: ValueFormatterParams) {
  // Convert complex object to string
  return params.value ? params.value.actualValueB : "";
}

function valueParserB(params: ValueParserParams) {
  // Convert string `newValue` back into complex object (reverse of `valueFormatterB`). `newValue` is string
  return createValueB(params.newValue, params.data);
}

function disable(id: string, disabled: boolean) {
  (document.querySelector(id) as any).disabled = disabled;
}

function setValue(id: string, value: number) {
  (document.querySelector(id) as any).value = value;
}

function getRows() {
  return Array.apply(null, Array(100)).map(function (_, i) {
    return {
      a: "a-" + i,
      b: {
        actualValueB: "b-" + i,
        anotherPropertyB: "b",
      },
      anotherPropertyA: "a",
    };
  });
}

const VueExample = defineComponent({
  template: `
        <div style="height: 100%">
                <div class="example-wrapper">
      <div>
        <span class="button-group">
          <label>Available Undo's</label>
          <input id="undoInput" class="undo-redo-input">
            <label>Available Redo's</label>
            <input id="redoInput" class="undo-redo-input">
              <button id="undoBtn" class="undo-btn" v-on:click="undo()">Undo</button>
              <button id="redoBtn" class="redo-btn" v-on:click="redo()">Redo</button>
            </span>
          </div>
          <ag-grid-vue
            style="width: 100%; height: 100%;"
            @grid-ready="onGridReady"
            :columnDefs="columnDefs"
            :defaultColDef="defaultColDef"
            :rowData="rowData"
            :cellSelection="cellSelection"
            :undoRedoCellEditing="true"
            :undoRedoCellEditingLimit="undoRedoCellEditingLimit"
            @first-data-rendered="onFirstDataRendered"
            @cell-value-changed="onCellValueChanged"></ag-grid-vue>
          </div>
        </div>
    `,
  components: {
    "ag-grid-vue": AgGridVue,
  },
  setup(props) {
    const gridApi = shallowRef<GridApi | null>(null);
    const columnDefs = ref<ColDef[]>([
      {
        field: "a",
        valueFormatter: valueFormatterA,
        valueGetter: valueGetterA,
        valueParser: valueParserA,
        valueSetter: valueSetterA,
        equals: equalsA,
        cellDataType: "object",
      },
      {
        field: "b",
        valueFormatter: valueFormatterB,
        valueParser: valueParserB,
        cellDataType: "object",
      },
    ]);
    const defaultColDef = ref<ColDef>({
      editable: true,
      enableCellChangeFlash: true,
    });
    const rowData = ref<any[] | null>(getRows());
    const cellSelection = ref<boolean | CellSelectionOptions>({
      handle: {
        mode: "fill",
      },
    });
    const undoRedoCellEditingLimit = ref(5);

    function onFirstDataRendered() {
      setValue("#undoInput", 0);
      disable("#undoInput", true);
      disable("#undoBtn", true);
      setValue("#redoInput", 0);
      disable("#redoInput", true);
      disable("#redoBtn", true);
    }
    function onCellValueChanged(params: CellValueChangedEvent) {
      const undoSize = params.api.getCurrentUndoSize();
      setValue("#undoInput", undoSize);
      disable("#undoBtn", undoSize < 1);
      const redoSize = params.api.getCurrentRedoSize();
      setValue("#redoInput", redoSize);
      disable("#redoBtn", redoSize < 1);
    }
    function undo() {
      gridApi.value!.undoCellEditing();
    }
    function redo() {
      gridApi.value!.redoCellEditing();
    }
    const onGridReady = (params: GridReadyEvent) => {
      gridApi.value = params.api;
    };

    return {
      gridApi,
      columnDefs,
      defaultColDef,
      rowData,
      cellSelection,
      undoRedoCellEditingLimit,
      onGridReady,
      onFirstDataRendered,
      onCellValueChanged,
      undo,
      redo,
    };
  },
});

const app = createApp(VueExample);
app.mount("#app");
```

[Live example: Undo / Redo with Complex Objects](https://www.ag-grid.com/examples/undo-redo-edits/undo-redo-complex-objects/vue3)
