In an original exhibition-label editor, an author types an introduction and then inserts “Bring headphones.” through a dedicated button. One Undo should remove the inserted instruction without unexpectedly removing the introduction. That expectation needs an explicit history boundary when the two actions occur close together.
Register history and its commands together
For an existing ProseMirror application, add these plugins to the state's current plugin list rather than replacing the rest of the editor configuration:
import { history, undo, redo } from 'prosemirror-history';
import { keymap } from 'prosemirror-keymap';
const editingHistory = history({
depth: 100,
newGroupDelay: 500
});
const historyKeys = keymap({
'Mod-z': undo,
'Mod-Shift-z': redo,
'Mod-y': redo
});
Use compatible package versions and retain the application's schema, base editing commands, styles, and existing plugins. Mod maps to Command on macOS and Control elsewhere. Check competing keymaps: earlier plugins receive the opportunity to handle a key first. Keymap reference
The explicit values above match the documented defaults. depth controls retained history events, not characters or server versions. newGroupDelay is the delay in milliseconds after which a change starts a new group; nonadjacent changes also start a new group. Do not treat 500 milliseconds as a promise that every keystroke becomes its own Undo.
Start the inserted instruction as its own event
With view referring to the initialized editor, a button can insert the developer-controlled instruction at the current selection:
import { closeHistory } from 'prosemirror-history';
const insertInstruction = () => {
const tr = closeHistory(view.state.tr);
tr.insertText('Bring headphones.');
view.dispatch(tr.scrollIntoView());
view.dispatch(closeHistory(view.state.tr));
view.focus();
};
insertText() without explicit positions replaces the current selection. Establish the intended cursor or selected range before invoking it. The first boundary separates this insertion from earlier typing. The second, metadata-only boundary keeps subsequent typing from joining that insertion. Both transactions are constructed from the current state when dispatched.
Attach the function to an application button with type="button" when it sits inside a form. If the button changes focus, preserve the intended editor selection using the application's established interaction pattern. Test the action in the actual integration rather than assuming the DOM click target proves where the text will land.
For the exhibition label, type the introduction, insert the instruction immediately, and invoke Undo once. Confirm the instruction disappears and the introduction remains. Redo should restore the instruction at the intended position.
Exclude only changes that should survive Undo
A transaction can opt out of history:
// Apply only to an application transaction whose policy requires exclusion.
tr.setMeta('addToHistory', false);
This flag prevents that transaction from becoming something Undo rolls back. It is not a general setting for every programmatic edit. An author-triggered “Insert instruction” action should ordinarily remain undoable, even though JavaScript constructed the transaction.
ProseMirror history is selective: it can undo a recorded change while retaining later changes that were excluded. That requires position mapping. Do not implement Undo by replacing view.state.doc with an older full-document snapshot; doing so can discard changes the history system was designed to retain.
For collaborative applications, follow the history integration required by the chosen collaboration system. Adding a local history plugin does not establish network synchronization, authorship, or a shared activity log.
Keep toolbar state and saved state accurate
The history module exposes undoDepth(state) and redoDepth(state) for the number of available events. Read those values from the updated editor state when enabling toolbar buttons. Commands also follow ProseMirror's command protocol: checking undo(state) without a dispatch function checks applicability; performing it requires the application's dispatch path.
Test an empty history, a normal edit, Undo, Redo, and typing after Undo. Include the separate instruction action and the application's supported selection types. A correctly disabled Undo button should agree with the keyboard command and the actual document result.
The plugin-state guide addresses transaction-derived application labels. Such a label can change during Undo because Undo changes the document. The browser-draft recovery guide addresses a different problem: recovering words after leaving an editing page.
Finish the exhibition label by undoing and redoing the inserted instruction, then save the wording the author actually chose. Verify persistence through the application's normal save route. A working Undo stack confirms editing behavior, not that a visitor can already read the updated label.