Skip to content

Transactions & Mutations

Every change to the data model — creating a document, editing a cell, reordering columns, adding a filter — goes through the transaction system. This is not optional. Direct entity mutation is not supported.

Why: transactions enable undo/redo, debounced persistence to your data source, and automatic rollback if persistence fails.

Access the transaction from CollectionManager:

const manager = state.manager;
const transaction = manager.transaction;
// Update a document's property value
const document = manager.collection.documents.get("doc-1");
const propertyValue = document.getProp("status");
transaction.update(propertyValue, { value: "done" });
// Update a view's columns
const view = manager.views[0];
transaction.update(view, {
columns: [
{ propertyId: "name", width: 300, visible: true },
{ propertyId: "status", width: 150, visible: true },
],
});
// Update collection metadata
transaction.update(manager.collection, { name: "My Tasks" });
// Create a new property (column) on the collection
transaction.create("propertyId", manager.collection, "new-prop-id", {
label: "Due Date",
type: "date",
});
// Create a new document -- prefer the convenience method
manager.createDocument();
// Or with options
manager.createDocument({
viewId: view.id, // which view to add the row to
id: "custom-id", // optional custom document ID
meta: { source: "import" }, // optional metadata
});
// Delete a document
const document = manager.collection.documents.get("doc-1");
transaction.delete(document);
// Delete a property (column)
const property = manager.collection.schema["priority"];
transaction.delete(property);

The TransactionManager handles persistence automatically:

  1. You call transaction.update(), transaction.create(), or transaction.delete()
  2. The change is applied to the in-memory model immediately (optimistic)
  3. Operations accumulate in a buffer
  4. After a debounce period (default 250ms), accumulated operations are grouped by entity type and persisted to your data sources in parallel
  5. If persistence fails, the TransactionManager automatically rolls back by applying inverse operations in reverse order

You don’t need to think about this. It just works. But if you need to force-flush pending operations:

await manager.flush();

Set the debounce period during initialization:

const state = useCollectionManager({
collectionId: "my-collection",
user: "user-1",
initialize: async () => ({
collectionSource,
documentSource,
persistDebounceMs: 500, // default: 250
}),
});

The UndoRedoManager groups operations into batches with a 500ms window. Rapid edits within 500ms are grouped into a single undo unit.

The simplest way to enable undo/redo:

import { useUndoRedoHotkeys } from "@blocknote/block-view/react";
function MyTable({ table }) {
// Registers Ctrl+Z (undo) and Ctrl+Shift+Z (redo)
useUndoRedoHotkeys(table.undoRedoManager);
return <TableView table={table} editable />;
}
const undoRedo = manager.undoRedo;
undoRedo.canUndo(); // boolean
undoRedo.canRedo(); // boolean
undoRedo.undo(); // undo the last batch
undoRedo.redo(); // redo the last undone batch

Operations within 500ms are grouped:

// These three updates become ONE undo unit
transaction.update(prop1, { value: "a" });
transaction.update(prop2, { value: "b" });
transaction.update(prop3, { value: "c" });
// User presses Ctrl+Z → all three are undone at once
// Wait 600ms, then:
transaction.update(prop4, { value: "d" });
// This is a separate undo unit

If a persistence call fails (network error, server error), the TransactionManager automatically:

  1. Applies the inverse of each failed operation in reverse order
  2. Restores the model to its pre-mutation state
  3. Emits a mutationsRolledBack event

Listen for rollbacks:

manager.addHooks({
mutationsRolledBack: ({ entries }) => {
console.error("Changes rolled back:", entries.length, "operations");
// Show a toast notification, etc.
},
});

The transaction system emits events via hooks:

// Listen for successful persistence
manager.addHooks({
mutationsCommitted: ({ entries }) => {
console.log("Persisted", entries.length, "operations");
},
});
// Listen for errors
manager.addHooks({
error: ({ error, context }) => {
console.error(`Error during ${context}:`, error);
// context is "initialization" | "pagination" | "mutation"
},
});
import type { CollectionManager } from "@blocknote/block-view/core";
import { PropertyValue } from "@blocknote/block-view/core";
function performBulkOperations(manager: CollectionManager) {
const transaction = manager.transaction;
const collection = manager.collection;
const view = manager.views[0];
// 1. Add a new column
transaction.create("propertyId", collection, "due-date", {
label: "Due Date",
type: "date",
});
// 2. Create a new document
manager.createDocument({ viewId: view.id });
// 3. Update an existing document's value
const doc = collection.documents.get("doc-1");
if (doc) {
const statusValue = doc.getProp("status");
if (statusValue) {
transaction.update(statusValue, { value: "done" });
}
}
// 4. Delete a document
const oldDoc = collection.documents.get("doc-to-remove");
if (oldDoc) {
transaction.delete(oldDoc);
}
// All changes are applied immediately in-memory
// and will be persisted to sources after the debounce period
}