---
title: Undo and redo
description: How Edytor groups edits into undo steps, the undo and redo APIs, and how undo behaves when several people edit the same document.
icon: undo-2
---

Every document has one local history. Undo reverts your own edits only, never a collaborator's, and restores the selection you had. This page covers the shortcuts and APIs, what one undo step contains, and the rules that apply when others edit at the same time.

## Undo and redo

| Action | Keys |
| --- | --- |
| Undo | <kbd>Mod</kbd>+<kbd>Z</kbd> |
| Redo | <kbd>Mod</kbd>+<kbd>Shift</kbd>+<kbd>Z</kbd>, or <kbd>Ctrl</kbd>+<kbd>Y</kbd> on Windows and Linux |

`Mod` is <kbd>Cmd</kbd> on macOS and <kbd>Ctrl</kbd> elsewhere. Undo and redo from the browser's Edit menu or a context menu go through the same history.

From code, call the view's methods:

```svelte
<button onclick={() => edytor?.historyUndo()}>Undo</button>
<button onclick={() => edytor?.historyRedo()}>Redo</button>
```

`historyUndo()` restores the selection this view had before the step; `historyRedo()` restores the one it had after. If a collaborator deleted the place a recorded caret pointed to, the view keeps its current selection instead.

Both return nothing. Each call sets [`edytor.dispatcher.last`](/docs/editor/commands#results) to `{ operation: 'undo' }` or `'redo'` with a status: `applied` when a step was replayed, `noop` when the stack was empty, and `refused` on a readonly view or a read-only document. Read it to tell a refused undo from an empty stack.

Without a view, use the document's history directly. It restores no selection:

```ts
document.history.undo();
document.history.redo();
document.history.canUndo(); // Yjs UndoManager API
document.clearHistory(); // drop both stacks
```

All views of one document share its history. An edit made in one view can be undone from another; only the view that issues the undo restores its selection.

## What one step contains

Edytor groups edits the way a text editor does:

- **Typing** at the caret joins the previous step if it continues where that step left the caret, and follows it within the capture window (500 ms by default). Moving the caret and typing again starts a new step, however short the pause.
- **Deletions** (Backspace, Delete, cut, deleting a selection or blocks) start a new step.
- **Enter** is a step of its own.
- **Paste** and **drop** each start a new step.
- **Structural edits** (inserting, duplicating, converting, removing, nesting, unnesting and moving blocks, formatting) start a new step, whether they come from a key, a menu, a toolbar or a plugin calling a block command.
- **Markdown and slash conversions** are a step of their own. Undo gives back what you typed as text: `## ` becomes a paragraph `##`, `**b**` becomes `**b*`, and `/h2` + <kbd>Enter</kbd> becomes the `/h2` text.
- **An IME composition** (Japanese, Chinese, Korean input, and so on) is one step with its commit. One left before it shows anything (a key, a click or leaving the editor right after it starts over selected text) changes nothing and adds no step.
- **Copy** records nothing.

A command that plugins refuse records nothing. One `edytor.transact` call is never split across steps.

These rules do not depend on how fast you work: a menu action a few milliseconds after typing is still its own step, and so is the kind picked from the block handle's <kbd>+</kbd> (the new block and its kind are one step; opening the menu adds nothing). Commands you call yourself are one step each: block commands (`block.insertBlockAfter`, `block.setBlock`, `block.addChildBlock`, `block.setInlineData`, deletions and the rest) and text commands such as `text.markText`. Only `text.insertText` and `block.addInlineBlock` can join the previous step, and only when they continue at the caret that step left, as typing does. To make several commands one step, run them in one `edytor.transact` call: that is the only guarantee. `edytor.dispatcher.run(kind, body)` makes what `body` runs synchronously one step, as long as its commands commit within the capture window. It starts no step between them, but each command still commits on its own, so two steps result when `captureTimeout` is `0` or when `body` takes longer than the window, and commands after an `await` in an async `body` are grouped as if you had called them on their own. Do the async work before calling `run`, or wrap the commands in `edytor.transact`. `kind` names the edit (an input type such as `insertText`, or `format`, `insertBlock`, `insertFromPaste`), and the rules above apply by it: a kind they do not cover (`run('myConvert', …)`) starts a new step, as a structural edit does, so it never joins the typing before it.

Set the capture window when you create the document. `0` makes every commit its own step:

```ts
import { createDocument } from 'edytor';

const document = createDocument({ value, history: { captureTimeout: 300 } });
```

History stacks are not trimmed automatically. For long sessions, call `document.clearHistory()` at points where undo no longer makes sense, such as after a save.

## Undo with collaborators

Remote edits never enter your history, and your undo only takes back what you did. When edits overlap, these rules apply:

- **Your undo removes only your own contributions.** If you create a block (or press Enter) and a collaborator types into it, undoing your creation leaves the block with their text, even if their text arrives after your undo. A block stays while it holds another person's text or a child.
- **Undoing a split** puts the text of the second half back into the first line, including anything a collaborator typed there.
- **Concurrent deletes.** Each person's delete of a piece of text is their own. If two people delete the same text at the same time and one of them undoes, the text stays deleted until the other undoes too; then it comes back once, not twice.
- **Explicit deletes win.** When you delete a block while someone types into it, the block stays deleted.
- **Deleting a selected parent** moves its unselected children into its place; undo puts them back under it, with any edits collaborators made to them meanwhile.
- **An emptied document** shows a local empty paragraph when concurrent edits delete every block. Nothing is written for it until someone types, so undo never has to remove it, and two people typing into an emptied document each keep their line.

[Concurrent editing](/docs/collaboration/concurrent-editing) explains these rules in detail.
