---
product: "AG Studio"
title: "Undo & Redo"
description: "Studio records each change made to a report, and can step back and forward through them."
framework: angular
version: "3.0.0"
related:
    - title: "Modes & Layout"
      url: "https://www.ag-grid.com/studio/archive/3.0.0/angular/modes-layout/"
    - title: "Theming"
      url: "https://www.ag-grid.com/studio/archive/3.0.0/angular/theming/"
    - title: "Theme Builder"
      url: "https://www.ag-grid.com/studio/archive/3.0.0/angular/theme-builder/"
    - title: "Localisation"
      url: "https://www.ag-grid.com/studio/archive/3.0.0/angular/localisation/"
    - title: "State"
      url: "https://www.ag-grid.com/studio/archive/3.0.0/angular/state/"
    - title: "Exporting"
      url: "https://www.ag-grid.com/studio/archive/3.0.0/angular/exporting/"
    - title: "Figma Design System"
      url: "https://www.ag-grid.com/studio/archive/3.0.0/angular/figma-design-system/"
llms: "https://www.ag-grid.com/studio/archive/3.0.0/llms.txt"
---

# Undo & Redo

Studio records each change made to a report, and can step back and forward through them.

Undo and redo cover the durable document: widgets, layout, filters and schema. View state such as the selected page and the current selection is left where it is, then brought back into view for the restored change.

#### Undo & Redo

```ts
import '@angular/compiler';
import { provideHttpClient } from '@angular/common/http';
import { bootstrapApplication } from '@angular/platform-browser';

import { AppComponent } from './app.component.ts';

const app = bootstrapApplication(AppComponent, {
    providers: [provideHttpClient()],
});
```

[Live example: Undo & Redo](https://www.ag-grid.com/studio/archive/3.0.0/examples/undo-redo/undo-redo/angular/)

## Keyboard Shortcuts

While focus is inside Studio:

- `⌃ Ctrl`/`⌘ Cmd` + `Z` - Undo the last change.
- `⌃ Ctrl`/`⌘ Cmd` + `⇧ Shift` + `Z` - Redo.

The shortcuts are bound to Studio's own element, so they work only while focus is inside it. Studio listens nowhere else: with focus in one of your application's inputs or controls, the key press is yours, and a text field inside Studio keeps the shortcut for its own editing history. See [Keyboard Shortcuts](https://www.ag-grid.com/studio/archive/3.0.0/angular/keyboard-shortcuts/) for Studio's other shortcuts.

### Undoing From Outside Studio

To undo while focus is elsewhere in your application, bind the shortcut yourself and call `undo()` or `redo()`:

```
document.addEventListener('keydown', (event) => {
    if (!(event.ctrlKey || event.metaKey) || event.code !== 'KeyZ') return;

    event.preventDefault();
    if (event.shiftKey) {
        api.redo();
    } else {
        api.undo();
    }
});
```

How far that binding reaches, and which of your own components it should leave alone, is yours to decide. Studio makes no assumptions about the page around it.

> **Note**
>
> A binding on the document also fires for a press made inside Studio, because Studio does not stop the event propagating. Suppress the shortcut in Studio, as below, so one press does not undo twice.

### Suppressing the Shortcuts

`suppressKeyboard` stops Studio handling a shortcut, leaving the key press for the application to bind:

```ts
<ag-studio
    [suppressKeyboard]="suppressKeyboard"
    /* other studio properties ... */ />

this.suppressKeyboard = {
    undo: true,
    redo: true,
};
```

Each is independent, so `{ redo: true }` keeps undo on `⌃ Ctrl`/`⌘ Cmd` + `Z`. A suppressed shortcut is left untouched rather than swallowed, so a handler of your own still receives the event.

## Undoing and Redoing in Code

`undo()` and `redo()` each step one change, and do nothing at the end of their stack:

```
api.undo();
api.redo();
```

Making a new change discards whatever was waiting to be redone.

## Reading the History

Studio ships no undo controls of its own, so an application that wants them builds them from `getHistory()`. It returns the live state alongside the changes each action would step through:

```
const { undo, redo } = api.getHistory();
undoButton.disabled = undo.length === 0;
redoButton.disabled = redo.length === 0;
```

Refresh the controls from the `stateUpdated` event, which fires after every committed change - undo and redo included - and also when the history is discarded, whether or not the live state itself changed. A host persisting on this event sees one write per `clearHistory()` call, and one per mode switch once it opts into `history.onModeChange: 'clear'`.

The last entry of each stack is the one that action takes next, and its `label` names the change. Labels come from the locale, so they follow the configured [language](https://www.ag-grid.com/studio/archive/3.0.0/angular/localisation/):

```
const next = api.getHistory().undo.at(-1);
undoButton.title = next ? `Undo ${next.label}` : 'Nothing to undo';
```

Each entry also carries an `id`. Passing one to `undo()` or `redo()` steps through every change up to that entry as a single step, which is what a history list needs:

```
const { undo } = api.getHistory();
api.undo(undo[0].id); // back to the start of the history
```

> **Note**
>
> The history holds the last 100 changes by default, and is cleared when Studio loads `initialState`. A later `setState()` is recorded as a change like any other, so it can be undone. It can also be cleared explicitly with `api.clearHistory()`, or automatically on a mode change with `history.onModeChange: 'clear'`, both covered below.

## Limiting the History

Each entry holds the whole state either side of its change, so a long history on a large dashboard costs memory. Set `history.maxEntries` to trade how far back a user can undo against what that retention costs:

```ts
<ag-studio
    [history]="history"
    /* other studio properties ... */ />

this.history = {
    maxEntries: 20,
};
```

Once the history holds that many changes, recording a new one discards the oldest, which can no longer be undone. Undone changes do not add to the count: undoing moves a change from the undo side to the redo side rather than creating another one, so the two stacks together never exceed the limit.

A value below one keeps no history, leaving `undo()` and `redo()` with nothing to do. Changes still apply as normal; only the ability to reverse them is given up.

## Clearing the History

Call `clearHistory()` to discard the undo and redo stacks without touching the live state:

```
api.clearHistory();
```

`getHistory()` then returns empty stacks, and `undo()`/`redo()` are no-ops until the next change. The live state is untouched, so the changes themselves remain - only the ability to step back over them goes.

Set `history.onModeChange` to have this happen automatically whenever `mode` changes:

```ts
<ag-studio
    [history]="history"
    /* other studio properties ... */ />

this.history = {
    onModeChange: 'clear',
};
```

The default, `'preserve'`, carries the history across a mode switch. `'clear'` discards it on a switch in either direction, so a change made while in view mode (possible when filter editing is enabled there) is discarded too on the way back to edit mode. Mode does not otherwise gate undo or redo - both work in either mode.

Add a chart or two below, then clear the history explicitly or switch mode with `onModeChange` set to `'clear'`. The counts show both stacks emptying while the charts stay where they are:

#### Clearing the History

```ts
import '@angular/compiler';
import { provideHttpClient } from '@angular/common/http';
import { bootstrapApplication } from '@angular/platform-browser';

import { AppComponent } from './app.component.ts';

const app = bootstrapApplication(AppComponent, {
    providers: [provideHttpClient()],
});
```

[Live example: Clearing the History](https://www.ag-grid.com/studio/archive/3.0.0/examples/undo-redo/clear-history/angular/)

## Undo & Redo API

### Properties

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `suppressKeyboard` | `AgSuppressKeyboard` |  | Keyboard shortcuts Studio should not handle, so an application can bind them itself. Each shortcut is suppressed independently, e.g. `{ redo: true }` keeps undo on ctrl/cmd+Z and leaves ctrl/cmd+shift+Z to the application. |

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `history` | `AgHistoryOptions` |  | How Studio's undo and redo history behaves. |

### API Methods

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `undo` | `Function` |  | Undo the last change to the durable document state (widgets, layout, filters, schema). View state such as the selected page or selection is left as it is, then brought back into view for the restored change. No-op when there is nothing to undo (`getHistory().undo` is empty). Pass a `getHistory().undo` entry's `id` to undo every change back through that entry in a single step; an unknown id is a no-op. |
| `redo` | `Function` |  | Redo the change most recently undone. No-op when there is nothing to redo (`getHistory().redo` is empty). Making a fresh change discards the redo branch. Pass a `getHistory().redo` entry's `id` to redo every change forward through that entry in a single step; an unknown id is a no-op. |
| `getHistory` | `Function` |  | The editing history: the live state plus what `undo()` and `redo()` would step through, each entry labelled and timestamped. Drives control enablement (an empty `undo` stack means there is nothing to undo) and a history list. History does not survive loading new state. |
| `clearHistory` | `Function` |  | Discard the undo and redo history, so `getHistory()` returns empty stacks and `undo()`/`redo()` become no-ops until the next change. The live state is untouched. |
