From zbeyens. The source code is available on GitHub.

Plate
PlatePliteEditorsTemplates
GitHub16kGitHub
DiscordDiscord
UNPUBLISHED
  • Overview
  • Why This Fork
  • Examples
Walkthroughs
  • Installing Plite
  • Adding Event Handlers
  • Defining Custom Elements
  • Applying Custom Formatting
  • Executing Commands
  • Saving to a Database
  • Canonical Change Substrate
  • Improving Performance
Concepts
  • Interfaces
  • Nodes
  • Locations
  • Transforms
  • Document Changes
  • Commands
  • Editor
  • Extensions
  • Rendering
  • Serializing
  • Normalizing
  • Using TypeScript
  • Roots
  • Document State
  • Editing Behavior
  • Selection And DOM
  • Clipboard And Paste
  • Projection And Overlays
  • Schema
API
  • Anchor API
  • Location API
  • Path API
  • PointEntry API
  • Point API
  • Range API
  • Selection API
  • Location Types APIs
  • Span API
  • Editor
  • Element API
  • NodeEntry API
  • Node API
  • Node Types APIs
  • Text API
  • Debug Value Scrubbing
  • Transforms API
Libraries
  • Plite DOM
  • History Editor API
  • History Extension Setup
  • History
  • Plite History
  • Plite Hyperscript
  • Plite Layout
  • Annotations
  • DOM Coverage Boundaries
  • Editable Component
  • Plite React Event Handling
  • Virtualized Rendering
  • Plite React Hooks
  • React Editor Setup
  • React Editor
  • Plite React
  • Plite Component
  • Plite Yjs
  • Plite
General
  • Migration
  • Contributing
  • Docs Proof Map
  • FAQ
  • Resources

History

PreviousNext

Understand the undo and redo batch shape stored by the history extension.

The history() extension tracks undo and redo batches for an editor.

Usage

import { createEditor } from "@platejs/plite";
import { history } from "@platejs/plite-history";
 
const editor = createEditor({
  extensions: [history()],
});
 
editor.read((state) => state.history.undos());
 
editor.update((



History Extension SetupPlite History

On This Page

UsageHistory ObjectStatic MethodsHistory.isHistory(value: unknown): value is HistoryHistory.toJSON(editor): HistoryJSONHistory.fromJSON(editor, json): HistoryEditor APIstate.history(): Historystate.history.undos(): readonly Batch[]state.history.redos(): readonly Batch[]tx.history.undo(): voidtx.history.redo(): voidtx.history.restore(history): voidtx.history.skip(): voidtx.history.merge(): voidtx.history.newBatch(): voideditor.update({ history: "skip" }, fn): voideditor.update({ history: "merge" }, fn): voideditor.update({ history: "new-batch" }, fn): voideditor.update.history.undo(): voideditor.update.history.redo(): voideditor.update.history.restore(history): voidControlled Previews
Build your editor
Production-ready AI template and reusable components.
Get all-access
tx
)
=>
{
tx.history.undo();
});
editor.update({ history: "skip" }).text.insert("draft");
import { createEditor } from "@platejs/plite";
import { history } from "@platejs/plite-history";
 
const editor = createEditor({
  extensions: [history()],
});
 
editor.read((state) => state.history.undos());
 
editor.update((tx) => {
  tx.history.undo();
});
 
editor.update({ history: "skip" }).text.insert("draft");

History Object

import type {
  DocumentChange,
  EditorEffect,
  EditorSchemaIdentity,
  Selection,
} from "@platejs/plite";
 
export interface History {
  readonly redos: readonly Batch[];
  readonly revision: number;
  readonly schema: EditorSchemaIdentity;
  readonly undos: readonly Batch[];
}
 
interface Batch {
  readonly change: DocumentChange;
  readonly effects: readonly EditorEffect[];
  readonly selectionAfter: Selection;
  readonly selectionAfterRoot?: string;
  readonly selectionBefore: Selection;
  readonly selectionBeforeRoot?: string;
}
import type {
  DocumentChange,
  EditorEffect,
  EditorSchemaIdentity,
  Selection,
} from "@platejs/plite";
 
export interface History {
  readonly redos: readonly Batch[];
  readonly revision: number;
  readonly schema: EditorSchemaIdentity;
  readonly undos: readonly Batch[];
}
 
interface Batch {
  readonly change: DocumentChange;
  readonly effects: readonly EditorEffect[];




Static Methods

History.isHistory(value: unknown): value is History

Returns true if the passed in value is a History object and acts as a type guard.

History.toJSON(editor): HistoryJSON

Encode the installed undo and redo stacks as validated version 4 JSON. The envelope records the editor's schema identity, and each batch stores its canonical DocumentChange, registered effects, and selection envelopes.

Compatible typing and same-node property updates coalesce from canonical change classifications and changed ranges. Grouping fingerprints are runtime-only; loading persisted history starts a fresh automatic group.

History.fromJSON(editor, json): History

Decode and validate history without mutating the editor. Effect codecs are discovered from installed editor-extension resources, including state fields and standalone effects. Install the returned immutable value through tx.history.restore(...).

Decoding fails before reading any batch when the persisted schema identity does not match the editor. A matching schema ID and version with a different fingerprint means the schema semantics need a version bump.

const json = History.toJSON(editor);
 
localStorage.setItem("plite.history", JSON.stringify(json));
 
const decoded = History.fromJSON(
  editor,
  JSON.parse(localStorage.getItem("plite.history")!)
);
 
editor.update((tx) => {
  tx.history.restore(decoded);
});
const json = History.toJSON(editor);
 
localStorage.setItem("plite.history", JSON.stringify(json));
 
const decoded = History.fromJSON(
  editor,
  JSON.parse(localStorage.getItem("plite.history")!)
);
 
editor.update((tx) => {
  tx.history.restore(decoded);
});

Schema identity is always derived or named; null is invalid in memory and in version 4 JSON. Schema identity, codec versions, effect keys, built-in selection payloads, and JSON values are validated during decoding. Failed decoding leaves editor state untouched.

Editor API

state.history(): History

Read the current undo and redo stacks.

state.history.undos(): readonly Batch[]

Read the undo stack.

state.history.redos(): readonly Batch[]

Read the redo stack.

tx.history.undo(): void

Undo the previous history batch.

tx.history.redo(): void

Redo the next history batch.

tx.history.restore(history): void

Replace both history branches when the surrounding update commits. Commit and snapshot subscribers observe the restored revision through that one update.

tx.history.skip(): void

Do not save the current transaction to history.

tx.history.merge(): void

Merge the current transaction into the previous compatible undo batch.

tx.history.newBatch(): void

Start a fresh undo batch for the current transaction.

editor.update({ history: "skip" }, fn): void

Run one update without saving it to history.

editor.update({ history: "merge" }, fn): void

Run one update that merges into the previous compatible undo batch.

editor.update({ history: "new-batch" }, fn): void

Run one update where the first write starts a fresh history batch, then the rest of the callback merges into that batch.

For one direct write, configure the update facade instead:

editor.update({ history: "skip" }).text.insert("draft");
editor.update({ history: "skip" }).text.insert("draft");

editor.update.history.undo(): void

Undo the previous history batch outside a larger transaction.

editor.update.history.redo(): void

Redo the next history batch outside a larger transaction.

editor.update.history.restore(history): void

Restore decoded history as one direct update.

Controlled Previews

Render proposed edits from local state, decorations, or sidecar UI until the user accepts them. Do not mutate document content for a preview and then try to make that preview history later.

Use a local defineStateField without a persistence codec and with history: 'skip' for preview state. Cancel by clearing that field. Accept by clearing the preview and applying the real document edit in one normal update.

const previewReplacement = defineStateField<string | null>({
  key: "local.preview.replacement",
  history: "skip",
  initial: () => null,
});
 
editor.update((tx) => {
  tx.setField(previewReplacement, null);
  tx.text.delete({ at: selectedRange });
  tx.text.insert(acceptedText);
});
const previewReplacement = defineStateField<string | null>({
  key: "local.preview.replacement",
  history: "skip",
  initial: () => null,
});
 
editor.update((tx) => {
  tx.setField(previewReplacement, null);
  tx.text.delete({ at: selectedRange });
  tx.text.insert(acceptedText);
});

Undo then restores the document content without resurrecting preview UI.

readonly
selectionAfter
:
Selection
;
readonly selectionAfterRoot?: string;
readonly selectionBefore: Selection;
readonly selectionBeforeRoot?: string;
}