Tiptap Persistence: JSON, HTML, and Collaborative State
Editable JSON, rendered HTML, and collaborative Yjs state solve different persistence problems. This documentation-backed Tiptap guide explains how to choose a representation, check extension compatibility, restore deliberately, and plan tests for save acknowledgments and reconnects.
A hypothetical AI-assisted notes product needs to reopen editable notes. Later, its team wants to publish readable pages. Simultaneous editing might follow, but that is a separate decision. Which saved representation belongs to each job?
For Tiptap, start with JSON for the editable document. Use HTML where a consumer needs rendered content or interchange. If the product uses Yjs collaboration, persist its binary state. Treating these formats as interchangeable backups leaves too much unanswered.
The harder question is what must survive reopening a document. A note can look right while losing a relationship your product depends on. This Tiptap 3.x guide uses a decision table and Optijara's proposed 3R checklist to define that contract. The checks are for your own environment; this is a documentation-backed decision guide, with no executed installation or collaboration experiment.
Choose the representation your product needs
JSON for editable documents, HTML for rendering and interchange
Tiptap recommends JSON persistence for its flexibility and easier parsing. It also allows external edits without another HTML parser. Saving HTML is possible. Its readability, though, tells you little about whether the receiving editor can restore the content faithfully.
According to the editor instance API, editor.getJSON() and editor.getHTML() return the current document. JSON gives you the document tree. It does not capture the complete editor session, including selection or undo history, and it cannot replace collaborative state. HTML has the same document-versus-session boundary.
| Representation | Appropriate use | Required compatibility | What it does not preserve |
|---|---|---|---|
| Tiptap JSON | Editable document storage and structured transformations | Receiving extension schema | Complete runtime, undo history, or Yjs state |
| HTML | Rendered output or interchange with an HTML consumer | Parsing and rendering extensions, plus destination requirements | Lossless roundtrip fidelity or an editor session |
| Yjs binary state | Persistence for a Yjs collaborative document | Collaborative document and storage integration | Application authorization or recovery policy |
For rendering without an editor, check the package import. The HTML utility reference documents generateHTML from @tiptap/core as browser-only. The @tiptap/html version supports the server as well as the browser. Both require the relevant extensions. Review output safety separately; HTML generation carries no blanket sanitization guarantee.
Yjs binary state for collaborative persistence
Use the binary-state path below if simultaneous editing is a requirement. For a notes product that only needs save and reload, keep the simpler document-storage design. A collaboration server should earn its place through a product need.
Treat the extension schema as part of the saved-content contract
Check custom nodes, marks, and older documents
The Tiptap schema documentation describes a contract built from registered extensions. Those extensions determine which nodes and marks are allowed, along with their attributes and nesting. Valid JSON can still contain a structure the receiving editor cannot represent.
Return to the hypothetical notes product. Suppose its team removes a custom annotation extension, while older notes still contain annotation nodes. Someone must decide what happens to those notes before they load under the reduced extension list. Keeping support is one option. A reviewed migration or a refusal to edit pending recovery may be appropriate instead. Comparing plain text would miss the annotation relationship.
Research notes need similar care. Decide which citation identifiers belong in supported attributes and which source relationships need separate application records. The Zotero guide to inspectable research notes helps identify those relationships, without specifying a Tiptap integration. Rendered reference text alone is a poor basis for deciding what to preserve.
Understand what content checking can and cannot catch
In the schema reference, enableContentCheck is opt-in and defaults to false. JSON checking is stronger; HTML checking can miss unsupported marks that are then stripped. Keep that limitation in the restore acceptance criteria.
Initialization handling through onContentError differs from the command option errorOnInvalidContent. The documentation's collaboration error pattern uses disableCollaboration() to remove the collaboration extension. It also uses editor.setEditable(false, false) to stop input without emitting an update. This prevents stripped content from being synchronized, but cannot reconstruct what was lost.
Keep the original payload outside the editor. An application-owned schemaVersion, paired with reviewed migration rules, is a useful design choice. Tiptap does not supply that document-versioning guarantee for you. Preserve the source before accepting any transformed replacement.
The 3R playbook: Representation, Restore, Receipt
3R is Optijara's proposed editorial checklist for non-collaborative persistence. It is neither an official Tiptap feature nor a validated deployment method. Use it to ask separate questions about the content you store and the way you load it. Then establish what counts as an accepted save.
Representation: capture the document you intend to persist
Choose an authoritative representation and record its compatible extension configuration. Unless your application explicitly models metadata in the Tiptap tree, keep document identity outside it. Schema version and backend revision belong in the application contract too. An application revision has a different job from a node attribute or package version.
For notes built from extracted documents, keep source identity separate from editable prose. The Docling guide to structured content and source provenance covers that upstream ingestion boundary. Use it to decide which source references the notes need, rather than as an editor-persistence specification.
This JSON summarizes the proposed choices in machine-readable form. It is an editorial summary, with no executable save request or completed test implied.
{
"framework": "3r",
"representation": "editable-json-compatible-extensions",
"restore": "reject-incompatible-content-retain-original",
"receipt": "acknowledge-applicable-backend-revision",
"collaboration": "persist-yjs-binary-when-required"
}Restore: replace content deliberately
The setContent command reference says the command replaces the document. In 3.x, emitUpdate defaults to true; in v2 it defaulted to false. Check this command reference before copying shorthand from the general persistence page.
The example below is illustrative and unexecuted. It assumes an existing Tiptap 3.x editor with document and paragraph support, plus text support. Its payload matches that configuration. You would still need to implement the network and database work.
const savedContent = {
type: 'doc',
content: [{
type: 'paragraph',
content: [{ type: 'text', text: 'saved-note' }],
}],
}
editor.commands.setContent(savedContent, {
emitUpdate: false,
errorOnInvalidContent: true,
})Setting emitUpdate: false can avoid an update-triggered autosave while restoring content. Other application listeners still need inspection, and asynchronous write ordering remains your responsibility. If restoration is rejected, keep the original payload available for recovery. Do not overwrite it with partially accepted content.
The events reference assigns different meanings to these events. update concerns content changes. selectionUpdate concerns selection, while transaction covers state changes from any operation. Use update or the documented onUpdate callback for content-change-driven saves, and check what your other listeners actually do.
Receipt: distinguish local edits from accepted saves
A saved badge makes a promise. Under this proposed application contract, it should mean the backend accepted the applicable revision and no newer local edit is waiting. Serializing content or starting a request is too early to make that promise.
Define each transition before implementing this recommended status flow.
An acknowledgment for an older snapshot must leave a newer edit marked unsaved. Otherwise, the interface would claim acceptance for content the backend has not acknowledged.
Use backend expected-revision checks or choose another explicit conflict policy. Debouncing helps schedule requests; it cannot decide which revision the server should accept. On failure, preserve pending content. Make the conflict visible and require deliberate recovery before replacing local work with an older acknowledged snapshot. These decisions belong to the application, beyond Tiptap's event API.
Keep collaborative Yjs state separate from JSON projections
Persist binary state, not a reconstructed document on every reconnect
The Hocuspocus persistence documentation requires Yjs-encoded document state to be stored as Uint8Array binary data. Its warning is specific. Storing a Y.Doc as JSON and recreating Yjs binary when a user connects can disrupt merging and duplicate content on new connections.
Keep JSON as an additional projection if the product needs one. Persist the actual collaborative state for reconnects, and carry that distinction into backup and restore procedures.
Hocuspocus documents onStoreDocument for storage and onLoadDocument for loading. Database extensions offer another abstraction. Those APIs identify the persistence boundary; they are not a complete server setup. The non-collaborative autosave flow above is insufficient for this branch.
Decide whether collaboration is actually required
If collaboration is added later, plan the initial conversion explicitly. Retain the source and test reconnect behavior in your environment before replacing the existing save format. A silent format change gives you no useful migration acceptance criteria.
Persisting binary state still leaves authorization and retention decisions to the application. Test backups independently. Review extension migrations even after a successful reconnect, because reconnecting does not establish compatibility with a changed schema.
What teams get wrong: a roundtrip checklist before shipping
Test content fidelity and restore behavior
The following checks are proposed reader tests. They were not executed for this article. Build synthetic fixtures for the content your product supports, recording the extension configuration and retaining every original payload. Compare meaningful attributes as well as structure. Visible text is only part of the document.
| Mistake | Proposed test | Expected observation |
|---|---|---|
| Valid JSON means faithful content | Save and restore custom nodes, attributes, links, and marks | Supported structure and relationships remain intact |
| Older documents need no compatibility review | Load a fixture requiring a removed extension | Explicit rejection or reviewed migration, with original retained |
| Reload updates are user edits | Restore with emitUpdate: false; inspect actual save listeners | No unintended restore-triggered write |
| Content checking makes HTML safe | Review untrusted URLs, attributes, and output contexts separately | Security gaps remain explicit, not disguised as schema passes |
For the hypothetical notes product, begin with a plain paragraph fixture. Add a linked reference, then a custom annotation with its expected relationship written down. Include a fixture from the oldest supported schema configuration. If an attribute changes, investigate it even when the restored note looks identical.
Use the table as an implementation checklist and assign an owner to unresolved observations. A migration can produce valid content while discarding a relationship the product needs. That result should fail the product's acceptance criteria.
Test save races and collaborative reconnects
Delay and reorder save requests in your test environment. If the application uses expected revisions, verify that outdated writes are rejected and the interface reports a conflict. Also confirm that accepting an earlier snapshot leaves a later local edit marked unsaved.
With collaboration enabled, reconnect multiple clients against persisted binary state. Inspect the merge result and check for duplication. Record the observations rather than assuming the documented storage format guarantees the whole integration.
The Playwright guide to asserting UI states offers a related approach for testing the interface. Adapt it to save and recovery states, then add backend readback. Its accessibility fixture does not test persistence, and a visible saved label proves too little on its own.
Use this proposed measurement plan to keep concrete evidence. It defines observations to collect, without claiming a performance score or accuracy threshold.
| Question | Evidence to record | Follow-up |
|---|---|---|
| Did content survive? | Original and restored structures, extension configuration | Investigate missing nodes or attributes |
| Was restoration silent as intended? | Observed events and resulting write requests | Repair unintended listeners |
| Which save was accepted? | Request revision, backend receipt, subsequent readback | Resolve stale writes and misleading status |
| Did collaboration reconnect correctly? | Persisted-state identity and observed merge result | Investigate duplication before rollout |
Limitations and operational trade-offs
Document validity is not security or durability
Schema compatibility covers one boundary. Server-side authorization needs its own review, as does validation of incoming requests. Examine private-content handling and untrusted output contexts separately. Retention policy, backups, and recovery from failed writes also need application-specific decisions. This guide provides no security audit or compliance assurance.
Successful HTML rendering does not establish a lossless roundtrip. Keep the documented checking limitation in view, along with the collaborative binary-state warning. Serialization cannot supply the backend's conflict policy or storage durability.
Scope the implementation work honestly
Custom extensions carry maintenance work. Older documents need migration decisions, and conflict handling needs implementation and testing. Adding collaboration also means operating its infrastructure. Budget for those responsibilities before choosing the more involved design.
This guide reports no measured savings, performance improvement, or deployment result. The snippet remains unexecuted. Run the proposed checks against your own dependencies and fixtures before claiming verified behavior, and record unresolved outcomes. Documentation review gives you a basis for testing; it is not a passing application test.
Choose a persistence contract, then test the roundtrip
Map your current save format and extension configuration. Next, examine restore behavior and what the interface treats as an accepted save. Write down which relationships must survive. Test a representative document, then an older fixture and a failed save. Expand coverage once you understand those results. A note that renders correctly is a starting observation, not a reason to stop testing.
Key Takeaways
- 1Choose JSON for editable documents, HTML for rendering or interchange, and persisted Yjs binary state for collaboration.
- 2Document JSON is not a complete editor-session, undo-history, or collaborative-state snapshot.
- 3Treat configured extensions as part of the restore contract and preserve original payloads during migrations.
- 4In Tiptap 3.x, setContent emits an update by default; suppress it deliberately when restoring through update-based autosave.
- 5Separate local edits, in-flight writes, and backend acknowledgments under an explicit conflict policy.
- 6Run content-fidelity, stale-write, recovery, and applicable reconnect checks in your own environment before claiming verified persistence.
Conclusion
Start with the representation your product needs and make its extension contract explicit. Use 3R to review restoration and save acceptance, then test those decisions against your own fixtures and backend policy. If a note loses a relationship or a saved badge gets ahead of the backend, address that gap before adding more features. For help scoping a review of editor persistence or migration boundaries in an AI-assisted writing workflow, consider Optijara.
Frequently Asked Questions
Should I save Tiptap content as JSON or HTML, and does JSON preserve editor history?
Use JSON for flexible editable-document persistence, as Tiptap recommends. Use HTML for rendering or interchange with compatible extensions. `editor.getJSON()` returns the current document, not the full session, selection, undo history, or Yjs state. Neither format supplies backend conflict handling.
How do I restore Tiptap content without triggering an update-based autosave?
Use `editor.commands.setContent(savedContent, { emitUpdate: false, errorOnInvalidContent: true })` with an existing compatible editor. In 3.x, `emitUpdate` defaults to `true`, unlike v2. Suppressing that update does not stop every application listener or save race. Retain the original payload and handle restoration errors explicitly.
Why can nodes or marks disappear when saved content is loaded?
The receiving extension schema may not support them. Review missing extensions and migrations, and preserve the original payload. `enableContentCheck` defaults to `false`; HTML checking can miss unsupported marks. Content checking is not a migration engine or a universal recovery mechanism.
Can I generate HTML from Tiptap JSON on the server?
Yes. Use the documented `generateHTML` utility from `@tiptap/html` with the relevant extensions for server or browser use. The `@tiptap/core` variant is browser-only. HTML generation does not provide a blanket sanitization guarantee; review the output context and untrusted values separately.
Can I persist a collaborative Yjs document as JSON?
Not as a replacement for collaborative state. Hocuspocus requires Yjs-encoded `Uint8Array` binary data. JSON can be an additional projection, but recreating a `Y.Doc` from JSON on reconnect can disrupt merging and duplicate content. Authorization, backups, and schema migrations remain separate responsibilities.
Sources
- https://tiptap.dev/docs/editor/core-concepts/persistence
- https://tiptap.dev/docs/editor/core-concepts/schema
- https://tiptap.dev/docs/editor/api/commands/content/set-content
- https://tiptap.dev/docs/editor/api/events
- https://tiptap.dev/docs/editor/api/utilities/html
- https://tiptap.dev/docs/hocuspocus/guides/persistence
- https://tiptap.dev/docs/editor/api/editor
Written by
Hamza DiazHamza Diaz is the founder of Optijara, where he builds practical AI agents, automation systems, and Copilot workflows for service businesses. He writes about AI operations, agent strategy, and real-world implementation for teams that want usable systems instead of hype.
