Invalid schema handling in Tiptap
Content integrity can be a significant challenge in collaborative editing environments. Think of a scenario where users with different versions of an app are trying to edit the same document. Or a scenario common to single-page applications: some users keep their browser tabs open for long periods of time without refreshing, while others always have the latest editor version after a recent page load.
In both cases, a user with a newer version is creating content with features that are not available to others. When a user with an older version tries to access this document, how do we prevent data loss, maintain document structure, and ensure a smooth user experience?
This is where invalid schema handling becomes important. It allows developers to gracefully manage situations where the content structure doesn't match the editor's expectations, preventing issues like data corruption, unexpected content stripping, or editor crashes.
Whether you're building a note-taking app, a content management system, or any application with rich text editing capabilities, understanding and implementing proper schema handling can significantly improve your application's reliability and user experience.
Introducing content checking
Tiptap has an option called enableContentCheck. When set to true, this feature activates a mechanism to validate content against the schema derived from your registered extensions. This is particularly useful for catching and responding to content errors before they cause issues in your application.
Enable content checking
To enable this feature, simply add the enableContentCheck option when initializing your Tiptap editor:
new Editor({
enableContentCheck: true,
// ... other options
})The contentError event
When content checking is enabled, Tiptap will emit a contentError event if the initial content provided during editor setup is incompatible with the schema. With the Collaboration extension, it is also emitted when the shared document contains content that doesn't fit the schema. See For collaborative editing. This event provides you with valuable information to handle the error appropriately.
Handle contentError events
You have two options for handling these events:
Either you can use the onContentError callback:
new Editor({
enableContentCheck: true,
content: potentiallyInvalidContent,
onContentError({ editor, error, disableCollaboration }) {
// Your error handling logic here
},
// ... other options
})Or you can attach a listener to the contentError event:
const editor = new Editor({
enableContentCheck: true,
content: invalidContent,
// ... other options
})
editor.on('contentError', ({ editor, error, disableCollaboration }) => {
// Your error handling logic here
})Listen to the contentError event without enabling content checking
If you want to listen to the contentError event without enabling content checking, set emitContentError to true when initializing your Tiptap editor:
new Editor({
enableContentCheck: false,
emitContentError: true,
// ... other options
})This setting allows you to have invalid content in your editor, but still be notified when the content is invalid.
A note on content types
It's important to note that Tiptap's content checking is 100% accurate for JSON content types. However, when working with HTML content, there may be some limitations. While Tiptap does its best to alert on missing nodes, certain mark-related issues might be missed in some situations.
Recommended error handling strategies
How you handle schema errors will depend on your specific application and requirements. However, here are some general recommendations:
For non-collaborative editing
If you're not using collaborative editing features, the default behavior of stripping unknown content may be sufficient. This keeps your content in a known valid state for future editing.
For collaborative editing
A shared document can contain content that your editor can't display. For example, another user's app version added a node that your schema doesn't have. The Collaboration extension handles this content depending on enableContentCheck:
- With
enableContentCheck: false(the default), the editor removes the content it can't display and fills in missing required nodes. These changes are synced, so every other user loses that content too. A read-only user also sends these changes, and your server may reject them. - With
enableContentCheck: true, the editor keeps that content in the shared document. It shows what it can, pauses editing and sends no changes. Then it emits thecontentErrorevent. Handle that event withonContentError, because the default handler throws the error. Editing resumes when a change from another user makes the content fit the schema again.
This needs @tiptap/extension-collaboration 3.33.0 and @tiptap/y-tiptap 3.2.0 or later. With older versions, the editor removes the content even with enableContentCheck: true, and contentError isn't emitted. Only @tiptap/y-tiptap 3.0.10 and 3.0.11 behave differently: they make the editor read-only in both cases and may show an empty or outdated document, also without contentError.
For example, you can let users who can edit repair the document, and keep read-only users from sending any change:
new Editor({
extensions: [
StarterKit.configure({ undoRedo: false }),
Collaboration.configure({ document: ydoc }),
],
editable: canEdit,
enableContentCheck: !canEdit,
onContentError() {
// Tell the user why the editor is paused
notifyUser(
'This document contains content this app cannot show. Please refresh the application.',
)
},
})To stop syncing with other users completely, call disableCollaboration() in the handler. It destroys the Y.Doc you passed to the Collaboration extension, so other editors that use the same Y.Doc stop syncing too. The editor then no longer receives changes, so it doesn't resume on its own:
onContentError({ disableCollaboration }) {
disableCollaboration()
notifyUser('An error occurred. Please refresh the application.')
}