# Table of Contents
- [Tiptap Docs](#tiptap-docs)
- [Examples | Tiptap Editor Docs](#examples-tiptap-editor-docs)
- [JWT Authentication with Collaboration | Tiptap Collaboration Docs](#jwt-authentication-with-collaboration-tiptap-collaboration-docs)
- [Accessibility | Tiptap Editor Docs](#accessibility-tiptap-editor-docs)
- [Collaboration API | Tiptap Editor Docs](#collaboration-api-tiptap-editor-docs)
- [Create Mark | Tiptap Editor Docs](#create-mark-tiptap-editor-docs)
- [Guides | Tiptap Docs](#guides-tiptap-docs)
- [Invalid schema handling | Tiptap Editor Docs](#invalid-schema-handling-tiptap-editor-docs)
- [Frequently Asked Questions | Tiptap Editor Docs](#frequently-asked-questions-tiptap-editor-docs)
- [Migrate from CKEditor 4 | Tiptap Editor Docs](#migrate-from-ckeditor-4-tiptap-editor-docs)
- [Migrate from Draft.js | Tiptap Editor Docs](#migrate-from-draft-js-tiptap-editor-docs)
- [Offline support | Tiptap Collaboration Docs](#offline-support-tiptap-collaboration-docs)
- [Migrate from Lexical | Tiptap Editor Docs](#migrate-from-lexical-tiptap-editor-docs)
- [Name Documents | Tiptap Collaboration Docs](#name-documents-tiptap-collaboration-docs)
- [Migrate from CKEditor 5 | Tiptap Editor Docs](#migrate-from-ckeditor-5-tiptap-editor-docs)
- [Migrate from Editor.js | Tiptap Editor Docs](#migrate-from-editor-js-tiptap-editor-docs)
- [Export to JSON and HTML | Tiptap Editor Docs](#export-to-json-and-html-tiptap-editor-docs)
- [Pro Extensions | Tiptap Editor Docs](#pro-extensions-tiptap-editor-docs)
- [TypeScript | Tiptap Editor Docs](#typescript-tiptap-editor-docs)
- [Trial | Tiptap Resources](#trial-tiptap-resources)
- [Changelog | Tiptap Editor Docs](#changelog-tiptap-editor-docs)
- [Contributing | Tiptap](#contributing-tiptap)
- [Integration performance | Tiptap Editor Docs](#integration-performance-tiptap-editor-docs)
- [Patched Security Incidents | Tiptap Resources](#patched-security-incidents-tiptap-resources)
- [Awareness | Tiptap Collaboration Docs](#awareness-tiptap-collaboration-docs)
- [Documents | Tiptap Collaboration](#documents-tiptap-collaboration)
- [Migrate from Quill | Tiptap Editor Docs](#migrate-from-quill-tiptap-editor-docs)
- [Webhooks | Tiptap Collaboration Docs](#webhooks-tiptap-collaboration-docs)
- [Migrate from TinyMCE | Tiptap Editor Docs](#migrate-from-tinymce-tiptap-editor-docs)
- [Migrate from Slate | Tiptap Editor Docs](#migrate-from-slate-tiptap-editor-docs)
- [React Composable API | Tiptap Editor Docs](#react-composable-api-tiptap-editor-docs)
- [What's new | Tiptap Resources](#what-s-new-tiptap-resources)
- [Upgrade v1 to v2 | Tiptap Collaboration Docs](#upgrade-v1-to-v2-tiptap-collaboration-docs)
- [Upgrade v2 to v3 | Tiptap Collaboration Docs](#upgrade-v2-to-v3-tiptap-collaboration-docs)
- [Auth Guide | Tiptap Collaboration Docs](#auth-guide-tiptap-collaboration-docs)
- [REST API | Tiptap Collaboration Docs](#rest-api-tiptap-collaboration-docs)
- [Collaboration | Tiptap Collaboration Docs](#collaboration-tiptap-collaboration-docs)
- [Inject content API | Tiptap Collaboration Docs](#inject-content-api-tiptap-collaboration-docs)
- [Configure runtime | Tiptap Collaboration Docs](#configure-runtime-tiptap-collaboration-docs)
- [Configure | Tiptap Comments Docs](#configure-tiptap-comments-docs)
- [Metrics | Tiptap Collaboration Docs](#metrics-tiptap-collaboration-docs)
- [Style threads | Tiptap Comments Docs](#style-threads-tiptap-comments-docs)
- [Snapshot extension | Tiptap Editor Docs](#snapshot-extension-tiptap-editor-docs)
- [Provider | Tiptap Collaboration Docs](#provider-tiptap-collaboration-docs)
- [Provider events | Tiptap Collaboration Docs](#provider-events-tiptap-collaboration-docs)
- [Snapshot Compare extension | Tiptap Editor Docs](#snapshot-compare-extension-tiptap-editor-docs)
- [Install Collaboration | Tiptap Collaboration Docs](#install-collaboration-tiptap-collaboration-docs)
- [Manage threads | Tiptap Comments Docs](#manage-threads-tiptap-comments-docs)
- [Thread authentication | Tiptap Comments Docs](#thread-authentication-tiptap-comments-docs)
- [Editor commands | Tiptap Comments Docs](#editor-commands-tiptap-comments-docs)
- [Comments | Tiptap Comments Docs](#comments-tiptap-comments-docs)
- [Install comments | Tiptap Comments Docs](#install-comments-tiptap-comments-docs)
- [Webhook | Tiptap Comments Docs](#webhook-tiptap-comments-docs)
- [REST API | Tiptap Comments Docs](#rest-api-tiptap-comments-docs)
- [Conversion | Tiptap Conversion](#conversion-tiptap-conversion)
- [Authenticate | Tiptap Conversion](#authenticate-tiptap-conversion)
- [Custom LLM | Tiptap Content AI](#custom-llm-tiptap-content-ai)
- [AI | Tiptap Content AI](#ai-tiptap-content-ai)
- [Legacy DOCX | Tiptap Conversion](#legacy-docx-tiptap-conversion)
- [Privacy | Tiptap Content AI](#privacy-tiptap-content-ai)
- [Collaboration | Tiptap Content AI](#collaboration-tiptap-content-ai)
- [Clever Editor example | Tiptap Editor Docs](#clever-editor-example-tiptap-editor-docs)
- [Drawing example | Tiptap Editor Docs](#drawing-example-tiptap-editor-docs)
- [Collaborative editing example | Tiptap Editor Docs](#collaborative-editing-example-tiptap-editor-docs)
- [Mentions example | Tiptap Editor Docs](#mentions-example-tiptap-editor-docs)
- [Forced content structure example | Tiptap Editor Docs](#forced-content-structure-example-tiptap-editor-docs)
- [Interactive React & Vue views example | Tiptap Editor Docs](#interactive-react-vue-views-example-tiptap-editor-docs)
- [Node views | Tiptap Editor Docs](#node-views-tiptap-editor-docs)
- [Trailing Node extension | Tiptap Editor Docs](#trailing-node-extension-tiptap-editor-docs)
- [Install the Editor | Tiptap Editor Docs](#install-the-editor-tiptap-editor-docs)
- [React | Tiptap Editor Docs](#react-tiptap-editor-docs)
- [Build AI features on the server](#build-ai-features-on-the-server)
---
# Tiptap Docs
Build exactly the editor your product needs, start with a lightweight open-source core and expand with fully managed Cloud services and AI extensions.
Use the headless editor and more than 100 extensions directly in your app and add real-time collaboration, comments, AI integrations, file conversion, etc.
Browse by feature
-----------------
Review our docs and examples to learn how to incorporate more advanced Tiptap features into your tech stack.

Open source
### Editor
Headless and framework-agnostic rich text editor based on ProseMirror.
[Documentation](https://tiptap.dev/docs/editor/getting-started/overview)
[Example](https://tiptap.dev/docs/examples/basics/default-text-editor)

CloudOn premises
### Collaboration
Real-time document editing with offline-first support.
[Documentation](https://tiptap.dev/docs/collaboration/getting-started/overview)
[Example](https://tiptap.dev/docs/collaboration/getting-started/install)

Custom LLMCloud
### AI Generation
Smart autocompletion, custom commands, and image generation with OpenAI or your own LLM.
[Documentation](https://tiptap.dev/docs/content-ai/capabilities/generation/overview)
[View example](https://tiptap.dev/docs/content-ai/capabilities/generation/overview#example)

Custom LLM
### AI Toolkit
Build AI agents that can read, insert, and edit documents with tool definitions or primitives.
[Documentation](https://tiptap.dev/docs/content-ai/capabilities/ai-toolkit/overview)
[View demo](https://tiptap.dev/docs/content-ai/capabilities/ai-toolkit/live-demo)

Custom LLM
### Server AI Toolkit
Build AI agents that read and edit documents on the server without a browser environment.
[Documentation](https://tiptap.dev/docs/content-ai/capabilities/server-ai-toolkit/overview)

CloudOn premises
### Comments
Add comments to your editor and moderate them with the Comments REST API.
[Documentation](https://tiptap.dev/docs/comments/getting-started/overview)
[Example](https://tiptap.dev/docs/comments/getting-started/overview#comments-example)

CloudOn premises
### Documents
Manage and manipulate documents in your frontend or server-side.
[Documentation](https://tiptap.dev/docs/collaboration/documents)

CloudOn premises
### Conversion
Import and export DOCX and other file types in your Editor or REST API
[Documentation](https://tiptap.dev/docs/conversion/getting-started/overview)
Get started with a template
---------------------------
Integrate the Tiptap Editor into your application with our editor templates or pick and choose your own UI components. Once integrated you can extend it with more headless Tiptap extensions and custimze it to your liking.
[\
\
### Templates\
\
Get started fast with a prebuilt editor that includes commonly used features.](https://tiptap.dev/docs/ui-components/templates/simple-editor)
[\
\
### Components\
\
Already using Tiptap? Drop in just the pieces you need.](https://tiptap.dev/docs/ui-components/components/overview)
Integrate or deploy Tiptap Cloud
--------------------------------
Integrate Tiptap Cloud services into your environment or deploy our on-premises variant within your own infrastructure. Choose the solution that best fits your performance, management, and scalability requirements.
### Cloud
Integrate our cloud service tested everyday by hundreds of thousands of users.
### Dedicated Cloud
Access a dedicated server, optimized for handling larger projects and higher loads.
### On-Premises
Implement Tiptap Cloud using Docker Images on your own servers.
Try every Tiptap feature free for 30 days
-----------------------------------------
Create a Tiptap Cloud account, open your dashboard, and start the 30 day trial to test every paid feature Tiptap has to offer.
* 30 day trial: No credit card required
* Editor features:
Integrate all Pro and Cloud Extensions.
* All paid features:
Test Collaboration, Comments, Version history and more.
* AI features: Integrate AI features in your editor.
[Start trial](https://cloud.tiptap.dev/register)
Examples
--------
[Browse examples](https://tiptap.dev/docs/examples)
[\
\
### Text editor\
\
Develop a rich text editor using Tiptap with minimal code.](https://tiptap.dev/docs/examples/basics/default-text-editor)
[\
\
### Collaborative editing\
\
Build a text editor featuring multi-user collaboration.](https://tiptap.dev/docs/examples/advanced/collaborative-editing)
Guides
------
[Browse guides](https://tiptap.dev/docs/guides)
[First Steps\
\
### How to set up and configure the Tiptap editor?](https://tiptap.dev/docs/editor/getting-started/configure)
[First Steps\
\
### How to integrate Pro Extensions?](https://tiptap.dev/docs/guides/pro-extensions)
[First Steps\
\
### How to make your Editor collaborative?](https://tiptap.dev/docs/collaboration/getting-started/install)
[Styling\
\
### How to apply styling to the headless Tiptap Editor](https://tiptap.dev/docs/editor/getting-started/style-editor)
Community
---------
[### Twitter X\
\
Follow us on X for the latest news and updates.](https://x.com/tiptap_editor)
[### Discord\
\
Connect with the Tiptap community.](https://tiptap.dev/discord)
[### GitHub\
\
Follow progress and contribute to the codebase.](https://github.com/ueberdosis/tiptap)
[Next upWhat's new in 3.0](https://tiptap.dev/docs/resources/whats-new)
On this page
[Introduction](https://tiptap.dev/docs#page-title)
[Browse by feature](https://tiptap.dev/docs#browse-by-feature)
[Get started with a template](https://tiptap.dev/docs#get-started-with-a-template)
[Integrate or deploy Tiptap Cloud](https://tiptap.dev/docs#integrate-or-deploy-tiptap-cloud)
[Examples](https://tiptap.dev/docs#examples)
[Guides](https://tiptap.dev/docs#guides)
[Community](https://tiptap.dev/docs#community)
---
# Examples | Tiptap Editor Docs
It's hard to get started with a new library. We know that and that's why we created a bunch of examples for you. They cover a wide range of use cases and show you how to use Tiptap in your project.
AllEditorCollaboration
[\
\
### Default text editor\
\
Learn how to create a default text editor with Tiptap.](https://tiptap.dev/docs/examples/basics/default-text-editor)
[\
\
### Markdown shortcuts\
\
Add markdown shortcuts to your Tiptap Editor.](https://tiptap.dev/docs/examples/basics/markdown-shortcuts)
[\
\
### Tables\
\
Add tables to your Tiptap Editor.](https://tiptap.dev/docs/examples/basics/tables)
[\
\
### Images\
\
Add images to your Tiptap Editor.](https://tiptap.dev/docs/examples/basics/images)
[\
\
### Formatting\
\
Add content formatting to your Tiptap Editor.](https://tiptap.dev/docs/examples/basics/formatting)
[\
\
### Tasks\
\
Add task lists to your Tiptap Editor.](https://tiptap.dev/docs/examples/basics/tasks)
[\
\
### Text direction & RTL support\
\
Add support for right-to-left languages and bidirectional text.](https://tiptap.dev/docs/examples/basics/text-direction)
[\
\
### Long texts\
\
Keep a good performance with huge amount of content.](https://tiptap.dev/docs/examples/basics/long-texts)
[\
\
### Minimal setup\
\
Keep it simple and clean with a minimal Tiptap Editor setup.](https://tiptap.dev/docs/examples/basics/minimal-setup)
[\
\
### Collaborative editing\
\
Build collaborative editors with Tiptap Editor.](https://tiptap.dev/docs/examples/advanced/collaborative-editing)
[\
\
### Menus\
\
Add bubble and floating menus to your editor.](https://tiptap.dev/docs/examples/advanced/menus)
[\
\
### Drawing\
\
Add a custom NodeView with drawing support to your Tiptap Editor.](https://tiptap.dev/docs/examples/advanced/drawing)
[\
\
### Mentions\
\
Allow users to mention others in your documents.](https://tiptap.dev/docs/examples/advanced/mentions)
[\
\
### Forced content structure\
\
Enforce specific content structures like headings in your editor.](https://tiptap.dev/docs/examples/advanced/forced-content-structure)
[\
\
### Clever Editor\
\
Make your editor clever with custom replacement extensions.](https://tiptap.dev/docs/examples/advanced/clever-editor)
[\
\
### Interactive React & Vue views\
\
Use React or Vue components in your Tiptap Editor content.](https://tiptap.dev/docs/examples/advanced/interactive-react-and-vue-views)
[\
\
### Syntax highlighting\
\
Implement syntax highlighting to codeblocks in your Tiptap Editor.](https://tiptap.dev/docs/examples/advanced/syntax-highlighting)
[](https://tiptap.dev/docs/examples#experiments)
Experiments
------------------------------------------------------------
The following examples are **experiments**, meaning they are not supported or maintained. They are here to show you what's possible with Tiptap and to inspire you to create your own extensions.
AllEditorCollaboration
[\
\
### Collaborative fields\
\
Save separate data to one collaborative Yjs document with Tiptap via fields.](https://tiptap.dev/docs/examples/experiments/collaborative-fields)
[\
\
### Figure\
\
Create image nodes and enhance them with figures.](https://tiptap.dev/docs/examples/experiments/figure)
[\
\
### Generic Figure\
\
Create nodes with figure support via a generic figure extension.](https://tiptap.dev/docs/examples/experiments/generic-figure)
[\
\
### iFrame\
\
Embed iframes in your editor content](https://tiptap.dev/docs/examples/experiments/iframe)
[\
\
### Linting\
\
Create a document linter for your Tiptap editor.](https://tiptap.dev/docs/examples/experiments/linting)
[\
\
### Slash commands\
\
Add a toolbar that pops up at the slash position](https://tiptap.dev/docs/examples/experiments/slash-commands)
[Next upDefault text editor](https://tiptap.dev/docs/examples/basics/default-text-editor)
---
# JWT Authentication with Collaboration | Tiptap Collaboration Docs
The JWT that is given by Collaboration is valid for just a few hours, which is enough for testing, but certainly not enough for a real live application.
For a quick setup guide, follow these instructions. For detailed information, visit the [Authentication](https://tiptap.dev/docs/collaboration/getting-started/authenticate)
page.
[](https://tiptap.dev/docs/guides/authentication#authenticate-with-jwt)
Authenticate with JWT
---------------------------------------------------------------------------------------------
In a short explanation, a JWT (JSON Web Token) is a json object that is cryptographically signed, which means a generated JWT cannot be altered.
[](https://tiptap.dev/docs/guides/authentication#generate-a-jwt)
Generate a JWT
-------------------------------------------------------------------------------
The JWT **must** be generated on the server side, as your `secret` **must not** leave your server (i.e. don't even try to generate the JWT on the frontend). You can use the following snippet on a NodeJS server and build an API around it.
import jsonwebtoken from 'jsonwebtoken'
const jwt = jsonwebtoken.sign(
{
/* object to be encoded in the JWT */
},
'your_secret',
)
// this JWT should be sent in the `token` field of the provider. Never expose 'your_secret' to a frontend!
A full server / API example is available [here](https://github.com/ueberdosis/tiptap-collab-replit/tree/main/src)
. Make sure to put the `secret` inside the server environment variable (or just make it a constant in the server file, don't transfer it from the client). You probably want to create an API call like `GET /getCollabToken` which will generate the JWT based on the server secret and the list of documents that the user is allowed to access.
You can find more information about short-lived tokens or token revocation [here](https://tiptap.dev/docs/collaboration/getting-started/authenticate#short-lived-jwts)
[](https://tiptap.dev/docs/guides/authentication#limit-access-to-specific-documents)
Limit access to specific documents
-----------------------------------------------------------------------------------------------------------------------
Documents can only be accessed by knowing the exact document name, as there is no way to get a list of documents from Collaboration. Thus, it's a good practice to name them like `userUuid/documentUuid` (i.e. `1500c624-8f9f-496a-b196-5e5dd8ec3c25/7865975c-38d0-4bb5-846b-df909cdc66d3`), which already makes it impossible to open random documents by guessing the name.
If you want to further limit which documents can be accessed using which JWT, you can encode the `allowedDocumentNames` property in the JWT, as in the following example. The created JWT will only allow access to the document(s) specified.
import jsonwebtoken from 'jsonwebtoken'
const jwt = jsonwebtoken.sign(
{
allowedDocumentNames: [\
'1500c624-8f9f-496a-b196-5e5dd8ec3c25/7865975c-38d0-4bb5-846b-df909cdc66d3', // userUuid/documentUuid\
'1500c624-8f9f-496a-b196-5e5dd8ec3c25/*', // userUuid/*\
],
},
'your_secret',
)
// this JWT should be sent in the `token` field of the provider. Never expose 'your_secret' to a frontend!
[PreviouslyExport JSON or HTML](https://tiptap.dev/docs/guides/output-json-html)
[Next upNaming documents](https://tiptap.dev/docs/guides/naming-documents)
---
# Accessibility | Tiptap Editor Docs
We strive to make Tiptap accessible to everyone. But this is at odds with the fact that we are a headless editor. We don’t provide an interface, so it’s up to you to make sure that the editor is accessible. Here are some guidelines to help you with that.
[](https://tiptap.dev/docs/guides/accessibility#demo-of-an-accessible-editor)
Demo of an accessible editor
----------------------------------------------------------------------------------------------------------
[](https://tiptap.dev/docs/guides/accessibility#guidelines)
Guidelines
----------------------------------------------------------------------
Here are a non-exhaustive list of guidelines to make your editor accessible:
### [](https://tiptap.dev/docs/guides/accessibility#keyboard-accessibility)
Keyboard accessibility
Make sure that all editor features are accessible via the keyboard. This includes navigating the editor, selecting text, menus, and using all editor features. If you look at the demo above, you can see that you can navigate the editor with the keyboard by:
* `Tab` to focus within the editor
* `Alt + F10` to focus the editor's toolbar
* Arrow keys to navigate the toolbar
* `Enter` to select a menu item
* `Tab` or `Esc` to navigate back the editor content
* `Shift + Arrow keys` to select text
* `Tab` to navigate to the text selection menu
* `Enter` to select a menu item
* `Tab` or `Esc` to navigate back to the editor content
* `Enter` to create a new paragraph
* `Tab` to navigate to the insert content menu
* Arrow keys to navigate the toolbar
* `Enter` to select a menu item, inserting the content
* `Tab` or `Esc` to navigate back to the editor content
* All other editor keyboard shortcuts as described in the [keyboard shortcuts](https://tiptap.dev/docs/editor/core-concepts/keyboard-shortcuts)
### [](https://tiptap.dev/docs/guides/accessibility#semantic-markup-and-aria-roles)
Semantic markup & Aria Roles
All of the default Tiptap extensions produce semantic markup. This means that the editor produces HTML that is easy to understand for screen readers.
To help screen readers even more, your editor & menus should provide appropriate Aria roles. Some examples are:
* The editor should have the `role="textbox"`
* The toolbar should have the `role="toolbar"`
* The menu should have the `role="menu"`
* The menu items should have the `role="menuitem"`
### [](https://tiptap.dev/docs/guides/accessibility#interface)
Interface
An interface needs to have well-defined contrasts, big enough click areas. Currently, we don’t provide an interface, so for now that’s totally up to you.
### [](https://tiptap.dev/docs/guides/accessibility#gotchas)
Gotchas
We've found a few gotchas, when implementing accessibility, that you should be aware of:
#### [](https://tiptap.dev/docs/guides/accessibility#voiceover-concatenates-words-across-block-elements)
VoiceOver concatenates words across block elements
When using VoiceOver on macOS, it is important to be aware that it may concatenate words across block elements. This can lead to unexpected reading experiences for users relying on screen readers.
For example, consider the following HTML structure:
Heading
Paragraph
VoiceOver would read this as "HeadingParagraph" instead of "Heading Paragraph" (notice the space). To fix this, you can add a zero-width space after each block element:
.tiptap {
h1::after,
h2::after,
h3::after,
h4::after,
h5::after,
h6::after,
p::after {
content: '\200B';
}
}
#### [](https://tiptap.dev/docs/guides/accessibility#keyboard-traps)
Keyboard traps
A keyboard trap is a situation where a user can’t navigate away from a certain element using the keyboard. This can be a problem if you have a modal or a menu that can be opened with the keyboard. Make sure that the user can always navigate away from these elements.
### [](https://tiptap.dev/docs/guides/accessibility#writing-assistance-optional)
Writing assistance (optional)
An optional writing assitance could help people writing content semanticly correct, for example pointing out an incorrect usage of heading levels. With that kind of assistance provided by the core developers, we could help to improve the content of a lot of applications.
[](https://tiptap.dev/docs/guides/accessibility#resources)
Resources
--------------------------------------------------------------------
| Document | Section | Heading |
| --- | --- | --- |
| WCAG 3.0 | 7.1 | [Text Alternatives](https://www.w3.org/TR/wcag-3.0/#text-alternatives) |
| WCAG 2.1 | 1.1.1 | [Non-text Content](https://www.w3.org/WAI/WCAG21/Understanding/non-text-content) |
| WCAG 2.1 | 2.1 | [Keyboard Accessible](https://www.w3.org/WAI/WCAG21/Understanding/keyboard-accessible) |
| WCAG 2.1 | 2.1.1 | [Keyboard](https://www.w3.org/WAI/WCAG21/Understanding/keyboard) |
| WCAG 2.1 | 4.1.1 | [Parsing](https://www.w3.org/WAI/WCAG21/Understanding/parsing) |
| WCAG 2.1 | 4.1.2 | [Name, Role, Value](https://www.w3.org/WAI/WCAG21/Understanding/name-role-value) |
| WCAG 2.1 | 2.1.2 | [No Keyboard Trap](https://www.w3.org/TR/WCAG21/#no-keyboard-trap) |
[PreviouslyFAQ](https://tiptap.dev/docs/guides/faq)
[Next upPerformance](https://tiptap.dev/docs/guides/performance)
---
# Collaboration API | Tiptap Editor Docs
[](https://tiptap.dev/docs/guides/collaboration-api#add-initial-content-to-a-document)
Add initial content to a document
------------------------------------------------------------------------------------------------------------------------
In order to add initial content to a document (or create an empty document), you can use our create document API.
Note that you need to replace `APP_ID`, `DOCUMENT_NAME` and `API_SECRET` with your own values. If you want to just create an empty document, you can send an empty array as `content`.
The payload of the request should be Tiptap JSON, which you can get by calling `editor.getJSON()` (see [Get JSON](https://tiptap.dev/docs/guides/output-json-html#option-1-json)
)
curl --location \
'https://APP_ID.collab.tiptap.cloud/api/documents/DOCUMENT_NAME?format=json' \
--header 'Content-Type: application/json' \
--header 'Authorization: API_SECRET' \
--data '{
"type": "doc",
"content": [\
{\
"type": "paragraph",\
"attrs": {\
"indent": 0,\
"textAlign": "left"\
},\
"content": [\
{\
"text": "This is my initial content.",\
"type": "text"\
}\
]\
}\
]
}'
[](https://tiptap.dev/docs/guides/collaboration-api#update-the-full-content-of-a-document)
Update the full content of a document
--------------------------------------------------------------------------------------------------------------------------------
Updating a document is as simple as fetching the current JSON document, applying your changes to it, and sending it back to us.
Use the following request to fetch the current JSON document.
curl --location \
'https://APP_ID.collab.tiptap.cloud/api/documents/DOCUMENT_NAME?format=json' \
--header 'Authorization: API_SECRET' \
Now, you can apply your changes to the JSON, and then send it back to us. We will calculate the diff between your new JSON and the current state of the document, and apply it in a collaborative way. If you only want to update a single node, see [Update a single node in a document](https://tiptap.dev/docs/guides/collaboration-api#update-a-single-node-in-a-document)
.
curl --location --request PATCH \
'https://APP_ID.collab.tiptap.cloud/api/documents/DOCUMENT_NAME?format=json' \
--header 'Content-Type: application/json' \
--header 'Authorization: API_SECRET' \
--data 'UPDATED_JSON'
[](https://tiptap.dev/docs/guides/collaboration-api#update-a-single-node-in-a-document)
Update a single node in a document
--------------------------------------------------------------------------------------------------------------------------
Use the following request to fetch the current JSON document.
curl --location \
'https://APP_ID.collab.tiptap.cloud/api/documents/DOCUMENT_NAME?format=json' \
--header 'Authorization: API_SECRET'
Now, you can identify the node that you want to update and apply your update in the JSON. You then need to send only that updated node back to us, but in the full Tiptap JSON format. To tell us which node you want to update, you need to pass query parameters `nodeAttributeName` and `nodeAttributeValue`. For example, if you use our unique-id extension and the target node has an `id` attribute with value `12345`, you would pass `nodeAttributeName=id&nodeAttributeValue=12345`. Note that this only works for top-level nodes.
curl --location --request PATCH \
'https://APP_ID.collab.tiptap.cloud/api/documents/DOCUMENT_NAME?format=json&nodeAttributeName=id&nodeAttributeValue=12345' \
--header 'Content-Type: application/json' \
--header 'Authorization: API_SECRET' \
--data 'UPDATED_JSON'
An example of `UPDATED_JSON` can be found below. Only include the node that should be updated and omit all others (from the "content" array).
{
"type": "doc",
"content": [\
{\
"type": "paragraph",\
"attrs": {\
"id": "12345"\
},\
"content": [\
{\
"text": "The API will update the node that matches the query filters (nodeAttributeName and nodeAttributeValue from the query) with the content of this node.",\
"type": "text"\
}\
]\
}\
]
}
[](https://tiptap.dev/docs/guides/collaboration-api#delete-a-node-in-a-document)
Delete a node in a document
------------------------------------------------------------------------------------------------------------
In order to delete an entire node in a document, you can send a request like this.
Make sure to replace `ATTR_NAME` and `ATTR_VALUE` with your own values, for example "id" and "12345".
curl --location --request PATCH \
'https://APP_ID.collab.tiptap.cloud/api/documents/DOCUMENT_NAME?format=json&nodeAttributeName=ATTR_NAME&nodeAttributeValue=ATTR_VALUE&mode=delete' \
--header 'Authorization: API_SECRET'
[](https://tiptap.dev/docs/guides/collaboration-api#update-attributes-of-a-node-in-a-document)
Update attributes of a node in a document
----------------------------------------------------------------------------------------------------------------------------------------
If you want to update the attributes of a node, you can send a request like below. Note that this deletes all attributes that are not part of the request, if you don't want that, you can add `mergeAttributes=1` to the URL.
Make sure to replace `ATTR_NAME` and `ATTR_VALUE` with your own values, for example "id" and "12345".
curl --location --request PATCH \
'https://APP_ID.collab.tiptap.cloud/api/documents/DOCUMENT_NAME?format=json&nodeAttributeName=ATTR_NAME&nodeAttributeValue=ATTR_VALUE&mode=attrs' \
--header 'Content-Type: application/json' \
--header 'Authorization: API_SECRET' \
--data '{
"level": 3
}'
[PreviouslyUpgrade Tiptap V2](https://tiptap.dev/docs/guides/upgrade-tiptap-v2)
[Next upExtend with TypeScript](https://tiptap.dev/docs/guides/typescript)
---
# Create Mark | Tiptap Editor Docs
[](https://tiptap.dev/docs/guides/create-mark#creating-a-mark-extension)
Creating a Mark Extension
==================================================================================================
Marks are used to add inline formatting to text in Tiptap. Common examples include bold, italic, and underline formatting. Let's learn how to create our own mark extension step by step.
The options available can be found in the [Mark API](https://tiptap.dev/docs/editor/extensions/custom-extensions/mark)
.
[](https://tiptap.dev/docs/guides/create-mark#basic-structure)
Basic Structure
------------------------------------------------------------------------------
First, we need to create the basic structure of a mark extension:
// filepath: src/HighlightMark.ts
import { Mark } from '@tiptap/core'
const HighlightMark = Mark.create({
name: 'highlight',
addOptions() {
return {
HTMLAttributes: {},
}
},
})
export default HighlightMark
What we've done here is:
* Created a new mark extension named `HighlightMark`
* Added an `addOptions` method to define the mark's options which are configurable by the user
[](https://tiptap.dev/docs/guides/create-mark#adding-styling)
Adding Styling
----------------------------------------------------------------------------
Let's add styling capabilities by defining how our mark renders and parses HTML:
// filepath: src/HighlightMark.ts
const HighlightMark = Mark.create({
// ...existing code...
parseHTML() {
return [\
{\
tag: 'mark',\
},\
]
},
renderHTML({ HTMLAttributes }) {
return ['mark', HTMLAttributes, 0]
},
})
[](https://tiptap.dev/docs/guides/create-mark#adding-attributes)
Adding Attributes
----------------------------------------------------------------------------------
We can make our mark more flexible by adding customizable attributes:
// filepath: src/HighlightMark.ts
const HighlightMark = Mark.create({
// ...existing code...
addAttributes() {
return {
color: {
default: 'yellow',
parseHTML: (element) => element.getAttribute('data-color'),
renderHTML: (attributes) => ({
'data-color': attributes.color,
style: `background-color: ${attributes.color}`,
}),
},
}
},
})
[](https://tiptap.dev/docs/guides/create-mark#adding-commands)
Adding Commands
------------------------------------------------------------------------------
Make the mark interactive with commands:
// filepath: src/HighlightMark.ts
// We need to extend the Commands interface to add our custom commands to the editor
declare module '@tiptap/core' {
interface Commands {
setHighlight: (attributes: { color: string }) => ReturnType
toggleHighlight: (attributes: { color: string }) => ReturnType
unsetHighlight: () => ReturnType
}
}
const HighlightMark = Mark.create({
// ...existing code...
addCommands() {
return {
setHighlight:
(attributes) =>
({ commands }) => {
return commands.setMark(this.name, attributes)
},
toggleHighlight:
(attributes) =>
({ commands }) => {
return commands.toggleMark(this.name, attributes)
},
unsetHighlight:
() =>
({ commands }) => {
return commands.unsetMark(this.name)
},
}
},
})
This adds commands which are available on the editor instance like:
* `editor.commands.setHighlight({ color: 'pink' })` Using the commands API
* `editor.chain().toggleHighlight().run()` Using the chaining API
[](https://tiptap.dev/docs/guides/create-mark#adding-keyboard-shortcuts)
Adding Keyboard Shortcuts
--------------------------------------------------------------------------------------------------
Add keyboard shortcuts for quick formatting:
// filepath: src/HighlightMark.ts
const HighlightMark = Mark.create({
// ...existing code...
addKeyboardShortcuts() {
return {
'Mod-h': () => this.editor.commands.toggleHighlight(),
}
},
})
[](https://tiptap.dev/docs/guides/create-mark#adding-input-rules)
Adding Input Rules
------------------------------------------------------------------------------------
Support Markdown-style syntax:
// filepath: src/HighlightMark.ts
import { markInputRule } from '@tiptap/core'
const HighlightMark = Mark.create({
// ...existing code...
addInputRules() {
return [\
markInputRule({\
find: /(?:==)((?:[^=]+))(?:==)$/,\
type: this.type,\
}),\
]
},
})
[](https://tiptap.dev/docs/guides/create-mark#using-the-mark)
Using the Mark
----------------------------------------------------------------------------
Here's how to use your new mark extension:
// filepath: src/Editor.ts
import { Editor } from '@tiptap/core'
import HighlightMark from './HighlightMark'
new Editor({
extensions: [\
HighlightMark,\
// ... other extensions\
],
})
Now you can:
* Use `==text==` to highlight text (input rule)
* Press Cmd+H (Ctrl+H on Windows) to toggle highlighting
* Programmatically control highlighting:
editor.commands.setHighlight({ color: 'pink' })
editor.commands.toggleHighlight()
editor.commands.unsetHighlight()
[](https://tiptap.dev/docs/guides/create-mark#testing)
Testing
--------------------------------------------------------------
Create tests to ensure your mark works as expected:
// filepath: src/HighlightMark.test.ts
import { Editor } from '@tiptap/core'
import HighlightMark from './HighlightMark'
describe('HighlightMark', () => {
let editor: Editor
beforeEach(() => {
editor = new Editor({
extensions: [HighlightMark],
content: '',
})
})
test('can toggle highlight mark', () => {
editor.commands.setContent('test')
editor.commands.selectAll()
editor.commands.toggleHighlight()
expect(editor.getHTML()).toBe('test')
})
})
This mark extension provides a foundation that you can build upon for your specific use case. You can extend it further by adding more attributes, commands, or changing how it renders in the editor.
---
# Guides | Tiptap Docs
Tiptap Guides provide practical advice on configuring your editor, enhancing the user experience with our features, custom extensions, interactive elements, and ensuring accessibility, all aimed at helping you build inclusive and dynamic rich text editors for your applications.
AllEditorCollaborationMigration
[First Steps\
\
### How to integrate Pro Extensions](https://tiptap.dev/docs/guides/pro-extensions)
[Essential\
\
### \
\
How to ensure the content created with Tiptap is accessible?](https://tiptap.dev/docs/guides/accessibility)
[Essential\
\
### \
\
How to make sure your editor is integrated performantly?](https://tiptap.dev/docs/guides/performance)
[React\
\
### \
\
Using Tiptap with the React Composable API](https://tiptap.dev/docs/guides/react-composable-api)
[Essential\
\
### How to handle invalid schemas in Tiptap](https://tiptap.dev/docs/guides/invalid-schema)
[Essential\
\
### \
\
See how to export and store your content as JSON or HTML](https://tiptap.dev/docs/guides/output-json-html)
[Essential\
\
### \
\
How to implement authentication for document access control?](https://tiptap.dev/docs/guides/authentication)
[Documents\
\
### \
\
How to effectively name and organize documents in your collaborative platform?](https://tiptap.dev/docs/guides/naming-documents)
[Offline\
\
### \
\
How to incorporate offline capabilities and automatic sync?](https://tiptap.dev/docs/guides/offline-support)
[Essential\
\
### How to upgrade from Tiptap v1 to v2?](https://tiptap.dev/docs/guides/upgrade-tiptap-v1)
[Customization\
\
### \
\
How to code and extend your Editor with TypeScript?](https://tiptap.dev/docs/guides/typescript)
[Migration\
\
### Migrate from TinyMCE](https://tiptap.dev/docs/guides/migrate-from-tinymce)
[Migration\
\
### Migrate from CKEditor 4](https://tiptap.dev/docs/guides/migrate-from-ckeditor4)
[Migration\
\
### Migrate from CKEditor 5](https://tiptap.dev/docs/guides/migrate-from-ckeditor5)
[Migration\
\
### Migrate from Quill](https://tiptap.dev/docs/guides/migrate-from-quill)
[Migration\
\
### Migrate from Slate](https://tiptap.dev/docs/guides/migrate-from-slate)
[Migration\
\
### Migrate from Lexical](https://tiptap.dev/docs/guides/migrate-from-lexical)
[Migration\
\
### Migrate from Editor.js](https://tiptap.dev/docs/guides/migrate-from-editorjs)
[Migration\
\
### Migrate from Draft.js](https://tiptap.dev/docs/guides/migrate-from-draftjs)
---
# Invalid schema handling | Tiptap Editor Docs
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.
[](https://tiptap.dev/docs/guides/invalid-schema#introducing-content-checking)
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.
### [](https://tiptap.dev/docs/guides/invalid-schema#enable-content-checking)
Enable content checking
To enable this feature, simply add the `enableContentCheck` option when initializing your Tiptap editor:
new Editor({
enableContentCheck: true,
// ... other options
})
[](https://tiptap.dev/docs/guides/invalid-schema#the-contenterror-event)
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. This event provides you with valuable information to handle the error appropriately.
### [](https://tiptap.dev/docs/guides/invalid-schema#handle-contenterror-events)
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
})
[](https://tiptap.dev/docs/guides/invalid-schema#listen-to-the-contenterror-event-without-enabling-content-checking)
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.
[](https://tiptap.dev/docs/guides/invalid-schema#a-note-on-content-types)
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.
[](https://tiptap.dev/docs/guides/invalid-schema#recommended-error-handling-strategies)
Recommended error handling strategies
-----------------------------------------------------------------------------------------------------------------------------
How you handle schema errors will depend on your specific application and requirements. However, here are some general recommendations:
### [](https://tiptap.dev/docs/guides/invalid-schema#for-non-collaborative-editing)
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.
### [](https://tiptap.dev/docs/guides/invalid-schema#for-collaborative-editing)
For collaborative editing
When using collaborative features, it's crucial to handle content errors to prevent synchronization issues. Here's an example of how you might handle a content error in a collaborative environment:
onContentError({ editor, error, disableCollaboration }) {
// Disable collaboration to prevent syncing invalid content
disableCollaboration()
// Prevent emitting updates
const emitUpdate = false
// Disable further user input
editor.setEditable(false, emitUpdate)
// Notify the user about the issue
notifyUser("An error occurred. Please refresh the application.")
}
This approach:
1. Disables collaboration to prevent syncing invalid content
2. Prevents updates from being emitted
3. Disables the editor to prevent further user input
4. Notifies the user about the issue
[PreviouslyReact Composable API](https://tiptap.dev/docs/guides/react-composable-api)
[Next upExport JSON or HTML](https://tiptap.dev/docs/guides/output-json-html)
---
# Frequently Asked Questions | Tiptap Editor Docs
[](https://tiptap.dev/docs/guides/faq#when-i-copy-the-content-of-the-editor-into-a-text-field-i-get-a-bunch-of-newlines)
When I copy the content of the editor, into a text field I get a bunch of newlines
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
By default, in Tiptap, the clipboard text serializer is set to produce paragraphs with 2 newlines in between (like you would do in Markdown).
So you'd paste content and it would look like this:
This is a paragraph.
This is another paragraph.
When you probably want it to look like this (with no extra newline between paragraphs):
This is a paragraph.
This is another paragraph.
To do this you have to change the `clipboardTextSerializer` to use a single newline instead of two.
Like this:
new Editor({
// other options
coreExtensionOptions: {
clipboardTextSerializer: {
blockSeparator: '\n',
},
},
})
Which will make the clipboard serializer use a single newline as a separator between blocks.
[](https://tiptap.dev/docs/guides/faq#my-drag-and-drop-isnt-working)
My drag and drop isn't working
---------------------------------------------------------------------------------------------------
Some libraries like `react-dnd` or `react-beautiful-dnd` might interfere with the drag & drop functionality of Tiptap, by registering themselves globally. If you're using one of these libraries, you might have to disable them (or at least confine the elements that they listen to) for the drag and drop functionality of the editor to work properly.
[](https://tiptap.dev/docs/guides/faq#why-both-parsehtml-and-renderhtml)
Why both `parseHTML` and `renderHTML`
--------------------------------------------------------------------------------------------------------------
`parseHTML` and `renderHTML` can be thought of as opposites:
* `parseHTML`: HTML -> JSON, taking the HTML representation and parsing that as JSON.
* `renderHTML`: JSON -> HTML, taking the JSON representation and outputting that as HTML to render in the editor.
This is what allows Tiptap to be so flexible with its content representation. You can define your content as JSON, and then render it as HTML, or you can define your content as HTML, and then parse it into JSON.
Additionally, `parseHTML` and `renderHTML` can also be used in different contexts: `renderHTML` is used during _copy events_, because what you copy from an editor needs to be serialized as HTML to go into your clipboard. `parseHTML` is used during _paste events_, because that HTML in your clipboard is serialized as HTML and needs to be parsed into JSON.
On initial render, if your content is defined as JSON, `parseHTML` is completely skipped, and would only be used for pastes. Whereas your `renderHTML` method determines how the content is represented as HTML within the editor.
`renderHTML` can be overridden within the editor view with node views and mark views, to allow for custom rendering of nodes and marks.
[](https://tiptap.dev/docs/guides/faq#i-want-to-edit-html-content-with-tiptap)
I want to edit HTML content with Tiptap
----------------------------------------------------------------------------------------------------------------------
Tiptap is based on Prosemirror, and it is not an arbitrary HTML editor, it is a rich text editor that needs to have full control of the HTML that it is editing to make changes to it. Prosemirror does this by attempting to parse the content in such a way that it conforms to a schema (which is derived from the nodes & marks that you specify as Tiptap extensions), this means content which does not conform to the schema WILL BE LOST. This is a tradeoff, Prosemirror can accept a fair amount of HTML content & parse it into a structured format but it cannot reliably do so with arbitrary HTML (liberal in what it accepts, strict in what it outputs). One common situation where this can occur, is attempting to nest marks of different types like: `editable text` these two marks cannot be nested like this and Prosemirror will attempt to correct it by dropping one of them to abide by the rule that marks cannot be wrapping each other. To fix this, the HTML that is input into the editor should be in the format of one element with multiple attributes like: `editable text` , this would be correctly parsed by Prosemirror as attempting to apply two marks to the text.
There is not a way to configure this behavior in Prosemirror, as it is already complex enough to parse arbitrary HTML. Depending on the source of the HTML you are attempting to parse, you can:
* Rely on the default behavior & drop the unknown content
* Attempt to parse the HTML yourself with a custom HTML parser and make it conform to what Prosemirror would expect (e.g. to merge spans)
[](https://tiptap.dev/docs/guides/faq#react-context-is-not-working-with-nodeviews)
React context is not working with NodeViews
------------------------------------------------------------------------------------------------------------------------------
To use React context within a NodeView, you need to wrap the EditorContent component with the context provider. Within the nodeview, the context should be available within the NodeView component.
import React from 'react'
import { EditorContent } from '@tiptap/react'
const TiptapEditor = ({ editor }) => {
return (
)
}
[](https://tiptap.dev/docs/guides/faq#why-are-there-extra-divs-when-i-use-a-react-node-view)
Why are there extra divs when I use a React Node View
--------------------------------------------------------------------------------------------------------------------------------------------------
The reason that these divs exist is that ProseMirror expects a node view to be generated synchronously, whereas React can only render asynchronously. So we inject an element, and later let React asynchronously render into that element. Causing there to be a wrapping div in the DOM.
Similarly, when using a `NodeViewContent` there is an element, which has created by React, and then Prosemirror renders into that element. This has to exist as a handoff from React to Prosemirror, because React's default behavior is to clear the element that it is rendering into, and we need to not have React destroy the element that ProseMirror is trying to render the rich text into.
So, all of this to say, there is no good way to get rid of these intermediate elements because of how React works. You'd either have to use the JS NodeView API, or Vue because as either of those have more sane rendering approaches.
[PreviouslyIntegrate Pro Extensions](https://tiptap.dev/docs/guides/pro-extensions)
[Next upAccessibility](https://tiptap.dev/docs/guides/accessibility)
---
# Migrate from CKEditor 4 | Tiptap Editor Docs
Migrating from CKEditor 4 to Tiptap is straightforward and offers significant benefits in terms of modern architecture and flexibility. This guide will walk you through the migration process step by step.
[](https://tiptap.dev/docs/guides/migrate-from-ckeditor4#content-migration)
Content migration
---------------------------------------------------------------------------------------------
### [](https://tiptap.dev/docs/guides/migrate-from-ckeditor4#html-content-compatibility)
HTML content compatibility
CKEditor 4 stores content as HTML, which makes migration to Tiptap seamless. Tiptap can directly use HTML content without any conversion:
// Your existing CKEditor 4 content
const existingContent =
'
Hello world!
Item 1
Item 2
'
// Use directly in Tiptap
const editor = new Editor({
content: existingContent,
extensions: [StarterKit],
})
While HTML works perfectly, we recommend converting it to Tiptap's JSON format for better performance and readability. For batch conversion of existing content, use the [HTML utility](https://tiptap.dev/docs/editor/api/utilities/html)
to convert HTML to JSON programmatically.
[](https://tiptap.dev/docs/guides/migrate-from-ckeditor4#editor-setup)
Editor setup
-----------------------------------------------------------------------------------
### [](https://tiptap.dev/docs/guides/migrate-from-ckeditor4#installation)
Installation
First, install Tiptap and its dependencies:
npm install @tiptap/core @tiptap/starter-kit
Tiptap supports all modern frontend UI frameworks like React and Vue. Follow the framework-specific installation instructions in our [installation guides](https://tiptap.dev/docs/editor/getting-started/install)
.
### [](https://tiptap.dev/docs/guides/migrate-from-ckeditor4#basic-editor-setup)
Basic editor setup
Replace your CKEditor 4 initialization with Tiptap:
// CKEditor 4 (before)
CKEDITOR.replace('editor', {
toolbar: [\
{ name: 'basicstyles', items: ['Bold', 'Italic', 'Underline'] },\
{ name: 'paragraph', items: ['NumberedList', 'BulletedList'] },\
{ name: 'links', items: ['Link', 'Unlink'] },\
],
height: 300,
})
// Tiptap (after)
import { Editor } from '@tiptap/core'
import StarterKit from '@tiptap/starter-kit'
const editor = new Editor({
element: document.querySelector('#editor'),
extensions: [StarterKit],
content: '
Hello World!
',
})
[](https://tiptap.dev/docs/guides/migrate-from-ckeditor4#extensions)
Extensions
-------------------------------------------------------------------------------
### [](https://tiptap.dev/docs/guides/migrate-from-ckeditor4#understanding-tiptaps-extension-system)
Understanding Tiptap's extension system
Tiptap uses a modular extension system that replaces CKEditor 4's plugin architecture. Each feature is an independent extension that can be configured and customized.
The [StarterKit](https://tiptap.dev/docs/editor/extensions/functionality/starterkit)
is a bundle of all the basic extensions, and you can add or remove other extensions as needed.
Explore all available extensions in our [extensions guide](https://tiptap.dev/docs/editor/extensions/overview)
, or [create your own](https://tiptap.dev/docs/editor/extensions/custom-extensions)
to support custom functionality and HTML elements.
### [](https://tiptap.dev/docs/guides/migrate-from-ckeditor4#common-ckeditor-4-plugin-equivalents)
Common CKEditor 4 plugin equivalents
| CKEditor 4 Plugin | Tiptap Extension | Notes |
| --- | --- | --- |
| `basicstyles` | `Bold`, `Italic`, `Underline` | Included in StarterKit |
| `list` | `BulletList`, `OrderedList`, `ListItem` | Included in StarterKit |
| `link` | `Link` | Included in StarterKit |
| `image` | `Image` | Available separately |
| `table` | `Table` | Available separately |
| `sourcearea` | `CodeBlock` | Included in StarterKit |
| `format` | `Heading` | Included in StarterKit |
| `blockquote` | `Blockquote` | Included in StarterKit |
| `justify` | `TextAlign` | Available separately |
| `colorbutton` | `TextStyle`, `Color` | Available separately |
| `font` | `TextStyle`, `FontSize` | Available separately |
| `indentlist` | Built into list extensions | Included in StarterKit |
| `horizontalrule` | `HorizontalRule` | Included in StarterKit |
### [](https://tiptap.dev/docs/guides/migrate-from-ckeditor4#extension-configuration)
Extension configuration
import { Editor } from '@tiptap/core'
import StarterKit from '@tiptap/starter-kit'
import Image from '@tiptap/extension-image'
import Table from '@tiptap/extension-table'
import TableRow from '@tiptap/extension-table-row'
import TableHeader from '@tiptap/extension-table-header'
import TableCell from '@tiptap/extension-table-cell'
import TextAlign from '@tiptap/extension-text-align'
import Underline from '@tiptap/extension-underline'
const editor = new Editor({
extensions: [\
StarterKit,\
Underline,\
Image.configure({\
inline: true,\
allowBase64: true,\
}),\
Table.configure({\
resizable: true,\
}),\
TableRow,\
TableHeader,\
TableCell,\
TextAlign.configure({\
types: ['heading', 'paragraph'],\
}),\
],
})
### [](https://tiptap.dev/docs/guides/migrate-from-ckeditor4#custom-extensions)
Custom extensions
For CKEditor 4 custom plugins, create custom Tiptap extensions. See our [custom extensions guide](https://tiptap.dev/docs/editor/extensions/custom-extensions)
for detailed instructions.
[](https://tiptap.dev/docs/guides/migrate-from-ckeditor4#ui-implementation)
UI implementation
---------------------------------------------------------------------------------------------
### [](https://tiptap.dev/docs/guides/migrate-from-ckeditor4#toolbar-implementation)
Toolbar implementation
CKEditor 4's toolbar configuration translates to custom UI components in Tiptap:
// CKEditor 4 toolbar config
toolbar: [\
{ name: 'basicstyles', items: ['Bold', 'Italic', 'Underline'] },\
{ name: 'paragraph', items: ['NumberedList', 'BulletedList'] },\
{ name: 'links', items: ['Link', 'Unlink'] },\
]
// Tiptap equivalent (React example)
function Toolbar({ editor }) {
if (!editor) return null
return (
)
}
### [](https://tiptap.dev/docs/guides/migrate-from-ckeditor4#pre-built-ui-components)
Pre-built UI components
For faster development, consider using Tiptap's pre-built UI components:
* Explore our [UI components](https://tiptap.dev/docs/ui-components/getting-started/overview)
for ready-to-use toolbar and menu components
* Check out practical examples in our [default text editor example](https://tiptap.dev/docs/examples/basics/default-text-editor)
[](https://tiptap.dev/docs/guides/migrate-from-ckeditor4#migration-checklist)
Migration checklist
-------------------------------------------------------------------------------------------------
1. Install Tiptap packages
2. Replace CKEditor 4 initialization with Tiptap setup
3. Map CKEditor 4 plugins to Tiptap extensions
4. Migrate toolbar configuration to custom UI components
5. Test content compatibility
6. Convert existing content to JSON format (recommended)
7. Implement custom extensions for any missing functionality
8. Update event handlers and API calls
9. Test image upload and handling if used
10. Verify table functionality if used
11. Test user interactions
[](https://tiptap.dev/docs/guides/migrate-from-ckeditor4#next-steps)
Next steps
-------------------------------------------------------------------------------
* Explore the [extension overview](https://tiptap.dev/docs/editor/extensions/overview)
to discover all available extensions
* Learn about [custom extensions](https://tiptap.dev/docs/editor/extensions/custom-extensions)
for advanced customization
* Check out our [examples](https://tiptap.dev/docs/examples)
for implementation inspiration
* Review the [performance guide](https://tiptap.dev/docs/guides/performance)
for optimization tips
[PreviouslyMigrate from TinyMCE](https://tiptap.dev/docs/guides/migrate-from-tinymce)
[Next upMigrate from CKEditor 5](https://tiptap.dev/docs/guides/migrate-from-ckeditor5)
---
# Migrate from Draft.js | Tiptap Editor Docs
Tiptap is a popular alternative to Draft.js and migrating from Draft.js to Tiptap is straightforward. This guide will help you transition from Draft.js's immutable state model to Tiptap's extension system.
[](https://tiptap.dev/docs/guides/migrate-from-draftjs#content-migration)
Content migration
-------------------------------------------------------------------------------------------
### [](https://tiptap.dev/docs/guides/migrate-from-draftjs#html-content-compatibility)
HTML content compatibility
Draft.js uses ContentState which needs to be converted to HTML for Tiptap:
// Convert Draft.js ContentState to HTML
import { convertFromRaw } from 'draft-js'
import { stateToHTML } from 'draft-js-export-html'
// If you have ContentState
const htmlContent = stateToHTML(contentState)
// If you have raw state (JSON)
const contentState = convertFromRaw(rawContentState)
const htmlContent = stateToHTML(contentState)
// Use HTML content in Tiptap
const editor = new Editor({
content: htmlContent,
extensions: [StarterKit],
})
If you already have HTML output from Draft.js, you can use it directly:
// Your existing Draft.js HTML content
const existingContent = '
Hello world!
'
// Use directly in Tiptap
const editor = new Editor({
content: existingContent,
extensions: [StarterKit],
})
While HTML works perfectly, we recommend converting it to Tiptap's JSON format for better performance and readability. For batch conversion of existing content, use the [HTML utility](https://tiptap.dev/docs/editor/api/utilities/html)
to convert HTML to JSON programmatically.
[](https://tiptap.dev/docs/guides/migrate-from-draftjs#editor-setup)
Editor setup
---------------------------------------------------------------------------------
### [](https://tiptap.dev/docs/guides/migrate-from-draftjs#installation)
Installation
First, install Tiptap and its dependencies:
npm install @tiptap/core @tiptap/starter-kit
Tiptap supports all modern frontend UI frameworks like React and Vue. Follow the framework-specific installation instructions in our [installation guides](https://tiptap.dev/docs/editor/getting-started/install)
.
### [](https://tiptap.dev/docs/guides/migrate-from-draftjs#basic-editor-setup)
Basic editor setup
Replace your Draft.js editor with Tiptap:
// Draft.js (before)
import React, { useState } from 'react'
import { Editor, EditorState, RichUtils } from 'draft-js'
function MyDraftEditor() {
const [editorState, setEditorState] = useState(EditorState.createEmpty())
const handleKeyCommand = (command) => {
const newState = RichUtils.handleKeyCommand(editorState, command)
if (newState) {
setEditorState(newState)
return 'handled'
}
return 'not-handled'
}
return (
)
}
// Tiptap (after)
import { useEditor, EditorContent } from '@tiptap/react'
import StarterKit from '@tiptap/starter-kit'
function MyTiptapEditor() {
const editor = useEditor({
extensions: [StarterKit],
content: '
Hello World!
',
})
return
}
[](https://tiptap.dev/docs/guides/migrate-from-draftjs#extensions)
Extensions
-----------------------------------------------------------------------------
### [](https://tiptap.dev/docs/guides/migrate-from-draftjs#understanding-tiptaps-extension-system)
Understanding Tiptap's extension system
Tiptap uses a modular extension system that resembles Draft.js's entity and decorator system. Each feature is an independent extension with clear APIs.
The [StarterKit](https://tiptap.dev/docs/editor/extensions/functionality/starterkit)
is a bundle of all the basic extensions, and you can add or remove other extensions as needed.
Explore all available extensions in our [extensions guide](https://tiptap.dev/docs/editor/extensions/overview)
, or [create your own](https://tiptap.dev/docs/editor/extensions/custom-extensions)
to support custom functionality and HTML elements.
### [](https://tiptap.dev/docs/guides/migrate-from-draftjs#common-draftjs-feature-equivalents)
Common Draft.js feature equivalents
| Draft.js Feature | Tiptap Extension | Notes |
| --- | --- | --- |
| Bold inline style | `Bold` | Included in StarterKit |
| Italic inline style | `Italic` | Included in StarterKit |
| Underline inline style | `Underline` | Included in StarterKit |
| Code inline style | `Code` | Included in StarterKit |
| Link entity | `Link` | Included in StarterKit |
| Image entity | `Image` | Available separately |
| Header block | `Heading` | Included in StarterKit |
| Blockquote block | `Blockquote` | Included in StarterKit |
| Code block | `CodeBlock` | Included in StarterKit |
| Unordered list | `BulletList`, `ListItem` | Included in StarterKit |
| Ordered list | `OrderedList`, `ListItem` | Included in StarterKit |
### [](https://tiptap.dev/docs/guides/migrate-from-draftjs#extension-configuration)
Extension configuration
import { useEditor } from '@tiptap/react'
import StarterKit from '@tiptap/starter-kit'
import Image from '@tiptap/extension-image'
import { Color } from '@tiptap/extension-color'
import TextStyle from '@tiptap/extension-text-style'
import Highlight from '@tiptap/extension-highlight'
const editor = useEditor({
extensions: [\
StarterKit,\
Image.configure({\
inline: true,\
allowBase64: true,\
}),\
TextStyle,\
Color.configure({\
types: [TextStyle.name],\
}),\
Highlight.configure({\
multicolor: true,\
}),\
],
})
### [](https://tiptap.dev/docs/guides/migrate-from-draftjs#custom-extensions)
Custom extensions
For Draft.js custom entities or decorators, create custom Tiptap extensions. See our [custom extensions guide](https://tiptap.dev/docs/editor/extensions/custom-extensions)
for detailed instructions.
[](https://tiptap.dev/docs/guides/migrate-from-draftjs#ui-implementation)
UI implementation
-------------------------------------------------------------------------------------------
### [](https://tiptap.dev/docs/guides/migrate-from-draftjs#toolbar-implementation)
Toolbar implementation
Draft.js's RichUtils-based toolbar translates to Tiptap UI components:
// Draft.js toolbar (before)
import { RichUtils } from 'draft-js'
function DraftToolbar({ editorState, onChange }) {
const toggleInlineStyle = (style) => {
onChange(RichUtils.toggleInlineStyle(editorState, style))
}
const toggleBlockType = (blockType) => {
onChange(RichUtils.toggleBlockType(editorState, blockType))
}
return (
)
}
// Tiptap equivalent (React example)
function Toolbar({ editor }) {
if (!editor) return null
return (
)
}
### [](https://tiptap.dev/docs/guides/migrate-from-draftjs#pre-built-ui-components)
Pre-built UI components
For faster development, use Tiptap's pre-built UI components:
* Explore our [UI components](https://tiptap.dev/docs/ui-components/getting-started/overview)
for ready-to-use components
* Check out practical examples in our [default text editor example](https://tiptap.dev/docs/examples/basics/default-text-editor)
### [](https://tiptap.dev/docs/guides/migrate-from-draftjs#inline-toolbar)
Inline toolbar
Create an inline toolbar similar to Draft.js's inline styling:
import { BubbleMenu } from '@tiptap/react'
function MyEditor() {
const editor = useEditor({
extensions: [StarterKit],
})
return (
<>
>
)
}
### [](https://tiptap.dev/docs/guides/migrate-from-draftjs#entity-replacement-node-views)
Entity replacement (node views)
Draft.js entities can be replaced with Tiptap's [Node Views](https://tiptap.dev/docs/editor/extensions/custom-extensions/node-views)
:
// Draft.js entity (before)
const LinkEntity = (props) => {
const { url } = props.contentState.getEntity(props.entityKey).getData()
return {props.children}
}
// Tiptap Node View (after)
import { Node } from '@tiptap/core'
import { ReactNodeViewRenderer } from '@tiptap/react'
const LinkComponent = ({ node }) => {
return {node.textContent}
}
const CustomLink = Node.create({
name: 'customLink',
addNodeView() {
return ReactNodeViewRenderer(LinkComponent)
},
})
[](https://tiptap.dev/docs/guides/migrate-from-draftjs#migration-checklist)
Migration checklist
-----------------------------------------------------------------------------------------------
Install Tiptap packages
Convert Draft.js ContentState to HTML
Replace Draft.js editor with Tiptap setup
Map Draft.js inline styles to Tiptap marks
Map Draft.js block types to Tiptap nodes
Migrate entities to [Node Views](https://tiptap.dev/docs/editor/extensions/custom-extensions/node-views)
or extensions
Migrate toolbar components to Tiptap UI
Test content compatibility
Convert existing content to JSON format (recommended)
Implement custom extensions for any missing functionality
Update event handlers and key commands
Test inline toolbar functionality
Verify link and image handling
Test list functionality
[](https://tiptap.dev/docs/guides/migrate-from-draftjs#next-steps)
Next steps
-----------------------------------------------------------------------------
* Explore the [extension overview](https://tiptap.dev/docs/editor/extensions/overview)
to discover all available extensions
* Learn about [custom extensions](https://tiptap.dev/docs/editor/extensions/custom-extensions)
for advanced customization
* Check out our [examples](https://tiptap.dev/docs/examples)
for implementation inspiration
* Review the [performance guide](https://tiptap.dev/docs/guides/performance)
for optimization tips
[PreviouslyMigrate from Editor.js](https://tiptap.dev/docs/guides/migrate-from-editorjs)
---
# Offline support | Tiptap Collaboration Docs
Easily add offline functionality to your collaborative editor by using the [Y IndexedDB](https://docs.yjs.dev/ecosystem/database-provider/y-indexeddb)
extension. This tool from the Y.js ecosystem enhances your editor with offline data storage and sync capabilities.
[](https://tiptap.dev/docs/guides/offline-support#integrating-offline-support)
Integrating offline support
----------------------------------------------------------------------------------------------------------
Begin by adding the Y IndexedDB adapter to your project:
npm install y-indexeddb
Connect Y Indexeddb with a Y document to store it locally.
import { Editor } from '@tiptap/core'
import Collaboration from '@tiptap/extension-collaboration'
import * as Y from 'yjs'
import { IndexeddbPersistence } from 'y-indexeddb'
const ydoc = new Y.Doc()
// Set up IndexedDB for local storage of the Y document
new IndexeddbPersistence('example-document', ydoc)
const editor = new Editor({
extensions: [\
// Other extensions...\
Collaboration.configure({\
document: ydoc,\
}),\
],
})
The IndexedDB adapter ensures that every change to your document is stored locally in the browser. This means your work is saved even if you close the tab, lose your internet connection, or edit offline. When you're back online, it automatically syncs these changes.
[PreviouslyNaming documents](https://tiptap.dev/docs/guides/naming-documents)
[Next upUpgrade Tiptap V1](https://tiptap.dev/docs/guides/upgrade-tiptap-v1)
---
# Migrate from Lexical | Tiptap Editor Docs
Replace your Lexical editor with a better alternative like Tiptap. Migrating from Lexical to Tiptap is straightforward. This guide will help you transition from Lexical's node-based architecture to Tiptap's extension system.
[](https://tiptap.dev/docs/guides/migrate-from-lexical#content-migration)
Content migration
-------------------------------------------------------------------------------------------
### [](https://tiptap.dev/docs/guides/migrate-from-lexical#html-content-compatibility)
HTML content compatibility
Lexical uses its own JSON structure that needs to be converted for Tiptap. You can serialize Lexical content to HTML:
// Convert Lexical JSON to HTML
import { $generateHtmlFromNodes } from '@lexical/html'
import { $getRoot } from 'lexical'
// Assuming you have a Lexical editor instance
const htmlContent = editor.update(() => {
const root = $getRoot()
return $generateHtmlFromNodes(editor, root)
})
// Use HTML content in Tiptap
const tiptapEditor = new Editor({
content: htmlContent,
extensions: [StarterKit],
})
If you already have HTML output from Lexical, you can use it directly:
// Your existing Lexical HTML content
const existingContent = '
Hello world!
'
// Use directly in Tiptap
const editor = new Editor({
content: existingContent,
extensions: [StarterKit],
})
While HTML works perfectly, we recommend converting it to Tiptap's JSON format for better performance and readability. For batch conversion of existing content, use the [HTML utility](https://tiptap.dev/docs/editor/api/utilities/html)
to convert HTML to JSON programmatically.
[](https://tiptap.dev/docs/guides/migrate-from-lexical#editor-setup)
Editor setup
---------------------------------------------------------------------------------
### [](https://tiptap.dev/docs/guides/migrate-from-lexical#installation)
Installation
First, install Tiptap and its dependencies:
npm install @tiptap/core @tiptap/starter-kit
Tiptap supports all modern frontend UI frameworks like React and Vue. Follow the framework-specific installation instructions in our [installation guides](https://tiptap.dev/docs/editor/getting-started/install)
.
### [](https://tiptap.dev/docs/guides/migrate-from-lexical#basic-editor-setup)
Basic editor setup
Replace your Lexical editor with Tiptap:
// Lexical (before)
import { createEditor } from 'lexical'
import { LexicalComposer } from '@lexical/react/LexicalComposer'
import { RichTextPlugin } from '@lexical/react/LexicalRichTextPlugin'
import { ContentEditable } from '@lexical/react/LexicalContentEditable'
const initialConfig = {
namespace: 'MyEditor',
theme: {},
onError: console.error,
}
function MyLexicalEditor() {
return (
}
placeholder={
Enter some text...
}
/>
)
}
// Tiptap (after)
import { Editor } from '@tiptap/core'
import StarterKit from '@tiptap/starter-kit'
const editor = new Editor({
element: document.querySelector('#editor'),
extensions: [StarterKit],
content: '
Hello World!
',
})
[](https://tiptap.dev/docs/guides/migrate-from-lexical#extensions)
Extensions
-----------------------------------------------------------------------------
### [](https://tiptap.dev/docs/guides/migrate-from-lexical#understanding-tiptaps-extension-system)
Understanding Tiptap's extension system
Tiptap uses a modular extension system that resembles Lexical's node and plugin architecture. Each feature is an independent extension with clear APIs.
The [StarterKit](https://tiptap.dev/docs/editor/extensions/functionality/starterkit)
is a bundle of all the basic extensions, and you can add or remove other extensions as needed.
Explore all available extensions in our [extensions guide](https://tiptap.dev/docs/editor/extensions/overview)
, or [create your own](https://tiptap.dev/docs/editor/extensions/custom-extensions)
to support custom functionality and HTML elements.
### [](https://tiptap.dev/docs/guides/migrate-from-lexical#common-lexical-plugin-equivalents)
Common Lexical plugin equivalents
| Lexical Plugin/Node | Tiptap Extension | Notes |
| --- | --- | --- |
| `@lexical/rich-text` | `Bold`, `Italic` | Included in StarterKit |
| `@lexical/link` | `Link` | Included in StarterKit |
| `@lexical/list` | `BulletList`, `OrderedList`, `ListItem` | Included in StarterKit |
| `@lexical/code` | `Code`, `CodeBlock` | Included in StarterKit |
| `@lexical/table` | `Table` | Available separately |
| `HeadingNode` | `Heading` | Included in StarterKit |
| `QuoteNode` | `Blockquote` | Included in StarterKit |
| `ImageNode` | `Image` | Available separately |
| `@lexical/history` | `History` | Included in StarterKit |
| `@lexical/text` | `TextStyle`, `Color` | Available separately |
### [](https://tiptap.dev/docs/guides/migrate-from-lexical#extension-configuration)
Extension configuration
import { Editor } from '@tiptap/core'
import StarterKit from '@tiptap/starter-kit'
import Image from '@tiptap/extension-image'
import Table from '@tiptap/extension-table'
import TableRow from '@tiptap/extension-table-row'
import TableHeader from '@tiptap/extension-table-header'
import TableCell from '@tiptap/extension-table-cell'
import { Color } from '@tiptap/extension-color'
import TextStyle from '@tiptap/extension-text-style'
const editor = new Editor({
extensions: [\
StarterKit,\
Image.configure({\
inline: true,\
allowBase64: true,\
}),\
Table.configure({\
resizable: true,\
}),\
TableRow,\
TableHeader,\
TableCell,\
TextStyle,\
Color.configure({\
types: [TextStyle.name],\
}),\
],
})
### [](https://tiptap.dev/docs/guides/migrate-from-lexical#custom-extensions)
Custom extensions
For Lexical custom nodes or plugins, create custom Tiptap extensions. See our [custom extensions guide](https://tiptap.dev/docs/editor/extensions/custom-extensions)
for detailed instructions.
[](https://tiptap.dev/docs/guides/migrate-from-lexical#ui-implementation)
UI implementation
-------------------------------------------------------------------------------------------
### [](https://tiptap.dev/docs/guides/migrate-from-lexical#toolbar-implementation)
Toolbar implementation
Lexical's toolbar plugins translate to custom UI components in Tiptap:
// Lexical toolbar (before)
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
import { FORMAT_TEXT_COMMAND } from 'lexical'
function ToolbarPlugin() {
const [editor] = useLexicalComposerContext()
return (
)
}
// Tiptap equivalent (React example)
function Toolbar({ editor }) {
if (!editor) return null
return (
)
}
### [](https://tiptap.dev/docs/guides/migrate-from-lexical#pre-built-ui-components)
Pre-built UI components
For faster development, use Tiptap's pre-built UI components:
* Explore our [UI components](https://tiptap.dev/docs/ui-components/getting-started/overview)
for ready-to-use components
* Check out practical examples in our [default text editor example](https://tiptap.dev/docs/examples/basics/default-text-editor)
### [](https://tiptap.dev/docs/guides/migrate-from-lexical#floating-toolbar)
Floating toolbar
Replicate Lexical's floating toolbar using Tiptap's BubbleMenu:
import { BubbleMenu } from '@tiptap/react'
function MyEditor() {
const editor = useEditor({
extensions: [StarterKit],
})
return (
<>
>
)
}
### [](https://tiptap.dev/docs/guides/migrate-from-lexical#node-views-custom-nodes)
Node views (custom nodes)
Lexical's custom nodes can be replaced with Tiptap's [Node Views](https://tiptap.dev/docs/editor/extensions/custom-extensions/node-views)
:
// Lexical custom node (before)
class ImageNode extends DecoratorNode {
static getType() {
return 'image'
}
createDOM() {
const img = document.createElement('img')
img.src = this.__src
return img
}
}
// Tiptap Node View (after)
import { Node } from '@tiptap/core'
import { ReactNodeViewRenderer } from '@tiptap/react'
const ImageComponent = ({ node }) => {
return
}
const CustomImage = Node.create({
name: 'customImage',
addNodeView() {
return ReactNodeViewRenderer(ImageComponent)
},
})
[](https://tiptap.dev/docs/guides/migrate-from-lexical#migration-checklist)
Migration checklist
-----------------------------------------------------------------------------------------------
Install Tiptap packages
Convert Lexical JSON content to HTML
Replace Lexical editor initialization with Tiptap setup
Map Lexical plugins to Tiptap extensions
Migrate custom nodes to [Node Views](https://tiptap.dev/docs/editor/extensions/custom-extensions/node-views)
Migrate toolbar plugins to Tiptap UI components
Test content compatibility
Convert existing content to JSON format (recommended)
Implement custom extensions for any missing functionality
Update event handlers and command dispatching
Test floating toolbar functionality
Verify table functionality if used
Test history (undo/redo) functionality
[](https://tiptap.dev/docs/guides/migrate-from-lexical#next-steps)
Next steps
-----------------------------------------------------------------------------
* Explore the [extension overview](https://tiptap.dev/docs/editor/extensions/overview)
to discover all available extensions
* Learn about [custom extensions](https://tiptap.dev/docs/editor/extensions/custom-extensions)
for advanced customization
* Check out our [examples](https://tiptap.dev/docs/examples)
for implementation inspiration
* Review the [performance guide](https://tiptap.dev/docs/guides/performance)
for optimization tips
[PreviouslyMigrate from Slate](https://tiptap.dev/docs/guides/migrate-from-slate)
[Next upMigrate from Editor.js](https://tiptap.dev/docs/guides/migrate-from-editorjs)
---
# Name Documents | Tiptap Collaboration Docs
This guide outlines best practices for naming documents and organizing content within a single document, to help you define your own document structure.
For a comprehensive understanding of how to choose document names, you should review our [authorization guide](https://tiptap.dev/docs/collaboration/getting-started/authenticate#set-up-authorization)
, as document naming plays a crucial role in access control as well.
[](https://tiptap.dev/docs/guides/naming-documents#structure-document-names)
Structure document names
-----------------------------------------------------------------------------------------------------
Tiptap Collaboration uses document names to facilitate collaborative sessions, they serve as unique identifiers that link users to the same document. In theory it could be any string.
While the following example uses an entity's name combined with a unique ID, typical for CMS applications, you're free to adopt any naming convention that suits your application's requirements.
New documents are automatically generated as needed; you only need to provide a string identifier to the provider.
const documentName = 'article.123'
This naming format allows you to separate out the key details easily:
const documentName = 'article.123'
// Splitting the document name into separate parts
const [entityType, entityID] = documentName.split('.')
console.log(entityType) // Output: "article"
console.log(entityID) // Output: "123"
[](https://tiptap.dev/docs/guides/naming-documents#manage-nested-documents-with-fragments)
Manage nested documents with fragments
---------------------------------------------------------------------------------------------------------------------------------
Yjs's fragments are ideal for handling complex documents with distinct sections. This might be relevant in case you want to nest your documents, like for example a blog post with separate `title` and `content` parts.
With fragments, you can use one Y.Doc instance (e.g. one document) and use different editors for its distinct sections.
For example, in this blog post setup:
const ydoc = new Y.Doc()
// Title editor
const titleEditor = new Editor({
extensions: [\
Collaboration.configure({\
document: this.ydoc,\
field: 'title',\
}),\
],
})
// Content editor
const bodyEditor = new Editor({
extensions: [\
Collaboration.configure({\
document: this.ydoc,\
field: 'content',\
}),\
],
})
For complex setups with nested fragments, you can directly use a raw Y.js fragment, bypassing the `document` and `field` settings.
// a raw Y.js fragment
Collaboration.configure({
fragment: ydoc.getXmlFragment('custom'),
})
To fully grasp how document naming influences access control in Tiptap Collaboration, it's essential to consult our [authorization guide](https://tiptap.dev/docs/collaboration/getting-started/authenticate)
.
[PreviouslyCollaboration Auth](https://tiptap.dev/docs/guides/authentication)
[Next upOffline Collaboration](https://tiptap.dev/docs/guides/offline-support)
---
# Migrate from CKEditor 5 | Tiptap Editor Docs
If you're moving away from CKEditor, Tiptap is a great alternative.Migrating from CKEditor 5 to Tiptap is straightforward. This guide covers everything you need to know for a smooth transition.
[](https://tiptap.dev/docs/guides/migrate-from-ckeditor5#content-migration)
Content migration
---------------------------------------------------------------------------------------------
### [](https://tiptap.dev/docs/guides/migrate-from-ckeditor5#html-content-compatibility)
HTML content compatibility
CKEditor 5 typically outputs HTML content, which Tiptap can use directly without any conversion:
// Your existing CKEditor 5 content
const existingContent =
'
Hello world!
Item 1
Item 2
'
// Use directly in Tiptap
const editor = new Editor({
content: existingContent,
extensions: [StarterKit],
})
While HTML works perfectly, we recommend converting it to Tiptap's JSON format for better performance and readability. For batch conversion of existing content, use the [HTML utility](https://tiptap.dev/docs/editor/api/utilities/html)
to convert HTML to JSON programmatically.
[](https://tiptap.dev/docs/guides/migrate-from-ckeditor5#editor-setup)
Editor setup
-----------------------------------------------------------------------------------
### [](https://tiptap.dev/docs/guides/migrate-from-ckeditor5#installation)
Installation
First, install Tiptap and its dependencies:
npm install @tiptap/core @tiptap/starter-kit
Tiptap supports all modern frontend UI frameworks like React and Vue. Follow the framework-specific installation instructions in our [installation guides](https://tiptap.dev/docs/editor/getting-started/install)
.
### [](https://tiptap.dev/docs/guides/migrate-from-ckeditor5#basic-editor-setup)
Basic editor setup
Replace your CKEditor 5 initialization with Tiptap:
// CKEditor 5 (before)
import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor'
import Essentials from '@ckeditor/ckeditor5-essentials/src/essentials'
import Bold from '@ckeditor/ckeditor5-basic-styles/src/bold'
import Italic from '@ckeditor/ckeditor5-basic-styles/src/italic'
ClassicEditor.create(document.querySelector('#editor'), {
plugins: [Essentials, Bold, Italic],
toolbar: ['bold', 'italic'],
})
// Tiptap (after)
import { Editor } from '@tiptap/core'
import StarterKit from '@tiptap/starter-kit'
const editor = new Editor({
element: document.querySelector('#editor'),
extensions: [StarterKit],
content: '
Hello World!
',
})
[](https://tiptap.dev/docs/guides/migrate-from-ckeditor5#extensions)
Extensions
-------------------------------------------------------------------------------
### [](https://tiptap.dev/docs/guides/migrate-from-ckeditor5#understanding-tiptaps-extension-system)
Understanding Tiptap's extension system
Tiptap uses a modular extension system that resembles CKEditor 5's plugin architecture. Each feature is an independent extension that can be configured and customized.
The [StarterKit](https://tiptap.dev/docs/editor/extensions/functionality/starterkit)
is a bundle of all the basic extensions, and you can add or remove other extensions as needed.
Explore all available extensions in our [extensions guide](https://tiptap.dev/docs/editor/extensions/overview)
, or [create your own](https://tiptap.dev/docs/editor/extensions/custom-extensions)
to support custom functionality and HTML elements.
### [](https://tiptap.dev/docs/guides/migrate-from-ckeditor5#common-ckeditor-5-plugin-equivalents)
Common CKEditor 5 plugin equivalents
| CKEditor 5 Plugin | Tiptap Extension | Notes |
| --- | --- | --- |
| `Bold` | `Bold` | Included in StarterKit |
| `Italic` | `Italic` | Included in StarterKit |
| `List` | `BulletList`, `OrderedList`, `ListItem` | Included in StarterKit |
| `Link` | `Link` | Included in StarterKit |
| `Image` | `Image` | Available separately |
| `Table` | `Table` | Available separately |
| `CodeBlock` | `CodeBlock` | Included in StarterKit |
| `Heading` | `Heading` | Included in StarterKit |
| `Blockquote` | `Blockquote` | Included in StarterKit |
| `Alignment` | `TextAlign` | Available separately |
| `FontColor` | `TextStyle`, `Color` | Available separately |
| `FontSize` | `TextStyle`, `FontSize` | Available separately |
### [](https://tiptap.dev/docs/guides/migrate-from-ckeditor5#extension-configuration)
Extension configuration
import { Editor } from '@tiptap/core'
import StarterKit from '@tiptap/starter-kit'
import Image from '@tiptap/extension-image'
import Table from '@tiptap/extension-table'
import TableRow from '@tiptap/extension-table-row'
import TableHeader from '@tiptap/extension-table-header'
import TableCell from '@tiptap/extension-table-cell'
import TextAlign from '@tiptap/extension-text-align'
const editor = new Editor({
extensions: [\
StarterKit,\
Image.configure({\
inline: true,\
allowBase64: true,\
}),\
Table.configure({\
resizable: true,\
}),\
TableRow,\
TableHeader,\
TableCell,\
TextAlign.configure({\
types: ['heading', 'paragraph'],\
}),\
],
})
### [](https://tiptap.dev/docs/guides/migrate-from-ckeditor5#custom-extensions)
Custom extensions
For CKEditor 5 custom plugins, create custom Tiptap extensions. See our [custom extensions guide](https://tiptap.dev/docs/editor/extensions/custom-extensions)
for detailed instructions.
[](https://tiptap.dev/docs/guides/migrate-from-ckeditor5#ui-implementation)
UI implementation
---------------------------------------------------------------------------------------------
### [](https://tiptap.dev/docs/guides/migrate-from-ckeditor5#toolbar-implementation)
Toolbar implementation
CKEditor 5's toolbar configuration translates to custom UI components in Tiptap:
// CKEditor 5 toolbar config
toolbar: ['heading', 'bold', 'italic', 'link', 'bulletedList', 'numberedList']
// Tiptap equivalent (React example)
function Toolbar({ editor }) {
if (!editor) return null
return (
)
}
### [](https://tiptap.dev/docs/guides/migrate-from-ckeditor5#pre-built-ui-components)
Pre-built UI components
For faster development, use Tiptap's pre-built UI components:
* Explore our [UI components](https://tiptap.dev/docs/ui-components/getting-started/overview)
for ready-to-use components
* Check out practical examples in our [default text editor example](https://tiptap.dev/docs/examples/basics/default-text-editor)
### [](https://tiptap.dev/docs/guides/migrate-from-ckeditor5#balloon-editor-equivalent)
Balloon editor equivalent
CKEditor 5's balloon editor can be replicated using Tiptap's BubbleMenu:
import { BubbleMenu } from '@tiptap/react'
function MyEditor() {
const editor = useEditor({
extensions: [StarterKit],
})
return (
<>
>
)
}
[](https://tiptap.dev/docs/guides/migrate-from-ckeditor5#migration-checklist)
Migration checklist
-------------------------------------------------------------------------------------------------
Install Tiptap packages
Replace CKEditor 5 initialization with Tiptap setup
Map CKEditor 5 plugins to Tiptap extensions
Migrate toolbar configuration to custom UI components
Test content compatibility
Convert existing content to JSON format (recommended)
Implement custom extensions for any missing functionality
Update event handlers and API calls
Test balloon/bubble menu functionality if used
Verify image upload and handling
Test table functionality if used
[](https://tiptap.dev/docs/guides/migrate-from-ckeditor5#next-steps)
Next steps
-------------------------------------------------------------------------------
* Explore the [extension overview](https://tiptap.dev/docs/editor/extensions/overview)
to discover all available extensions
* Learn about [custom extensions](https://tiptap.dev/docs/editor/extensions/custom-extensions)
for advanced customization
* Check out our [examples](https://tiptap.dev/docs/examples)
for implementation inspiration
* Review the [performance guide](https://tiptap.dev/docs/guides/performance)
for optimization tips
[PreviouslyMigrate from CKEditor 4](https://tiptap.dev/docs/guides/migrate-from-ckeditor4)
[Next upMigrate from Quill](https://tiptap.dev/docs/guides/migrate-from-quill)
---
# Migrate from Editor.js | Tiptap Editor Docs
If you want to replace your Editor.js editor, Tiptap is a popular alternative.Migrating from Editor.js to Tiptap is straightforward. This guide will help you transition from Editor.js's block-based structure to Tiptap's document model.
[](https://tiptap.dev/docs/guides/migrate-from-editorjs#content-migration)
Content migration
--------------------------------------------------------------------------------------------
### [](https://tiptap.dev/docs/guides/migrate-from-editorjs#html-content-compatibility)
HTML content compatibility
Editor.js uses a block-based JSON structure that needs to be converted to HTML for Tiptap:
// Convert Editor.js blocks to HTML
function editorJsToHtml(editorJsData) {
let html = ''
editorJsData.blocks.forEach((block) => {
switch (block.type) {
case 'paragraph':
html += `
${block.data.text}
`
break
case 'header':
html += `${block.data.text}`
break
case 'list':
const listType = block.data.style === 'ordered' ? 'ol' : 'ul'
const items = block.data.items.map((item) => `
${item}
`).join('')
html += `<${listType}>${items}${listType}>`
break
case 'quote':
html += `
${block.data.text}
`
break
case 'code':
html += `
${block.data.code}
`
break
case 'image':
html += ``
break
// Add more block types as needed
}
})
return html
}
// Use converted HTML in Tiptap
const htmlContent = editorJsToHtml(editorJsData)
const editor = new Editor({
content: htmlContent,
extensions: [StarterKit],
})
If you already have HTML output from Editor.js, you can use it directly:
// Your existing Editor.js HTML content
const existingContent = '
Hello world!
'
// Use directly in Tiptap
const editor = new Editor({
content: existingContent,
extensions: [StarterKit],
})
While HTML works perfectly, we recommend converting it to Tiptap's JSON format for better performance and readability. For batch conversion of existing content, use the [HTML utility](https://tiptap.dev/docs/editor/api/utilities/html)
to convert HTML to JSON programmatically.
[](https://tiptap.dev/docs/guides/migrate-from-editorjs#editor-setup)
Editor setup
----------------------------------------------------------------------------------
### [](https://tiptap.dev/docs/guides/migrate-from-editorjs#installation)
Installation
First, install Tiptap and its dependencies:
npm install @tiptap/core @tiptap/starter-kit
Tiptap supports all modern frontend UI frameworks like React and Vue. Follow the framework-specific installation instructions in our [installation guides](https://tiptap.dev/docs/editor/getting-started/install)
.
### [](https://tiptap.dev/docs/guides/migrate-from-editorjs#basic-editor-setup)
Basic editor setup
Replace your Editor.js initialization with Tiptap:
// Editor.js (before)
import EditorJS from '@editorjs/editorjs'
import Header from '@editorjs/header'
import List from '@editorjs/list'
import Quote from '@editorjs/quote'
import Code from '@editorjs/code'
import Image from '@editorjs/image'
const editor = new EditorJS({
holder: 'editorjs',
tools: {
header: Header,
list: List,
quote: Quote,
code: Code,
image: Image,
},
})
// Tiptap (after)
import { Editor } from '@tiptap/core'
import StarterKit from '@tiptap/starter-kit'
const editor = new Editor({
element: document.querySelector('#editor'),
extensions: [StarterKit],
content: '
Hello World!
',
})
[](https://tiptap.dev/docs/guides/migrate-from-editorjs#extensions)
Extensions
------------------------------------------------------------------------------
### [](https://tiptap.dev/docs/guides/migrate-from-editorjs#understanding-tiptaps-extension-system)
Understanding Tiptap's extension system
Tiptap uses a modular extension system that differs from Editor.js's tool-based approach. Instead of discrete blocks, Tiptap uses a continuous document model with extensions providing formatting and functionality.
The [StarterKit](https://tiptap.dev/docs/editor/extensions/functionality/starterkit)
is a bundle of all the basic extensions, and you can add or remove other extensions as needed.
Explore all available extensions in our [extensions guide](https://tiptap.dev/docs/editor/extensions/overview)
, or [create your own](https://tiptap.dev/docs/editor/extensions/custom-extensions)
to support custom functionality and HTML elements.
### [](https://tiptap.dev/docs/guides/migrate-from-editorjs#common-editorjs-tool-equivalents)
Common Editor.js tool equivalents
| Editor.js Tool | Tiptap Extension | Notes |
| --- | --- | --- |
| `@editorjs/paragraph` | `Paragraph` | Included in StarterKit |
| `@editorjs/header` | `Heading` | Included in StarterKit |
| `@editorjs/list` | `BulletList`, `OrderedList`, `ListItem` | Included in StarterKit |
| `@editorjs/quote` | `Blockquote` | Included in StarterKit |
| `@editorjs/code` | `CodeBlock` | Included in StarterKit |
| `@editorjs/image` | `Image` | Available separately |
| `@editorjs/table` | `Table` | Available separately |
| `@editorjs/link` | `Link` | Included in StarterKit |
| `@editorjs/marker` | `Highlight` | Available separately |
| `@editorjs/delimiter` | `HorizontalRule` | Included in StarterKit |
### [](https://tiptap.dev/docs/guides/migrate-from-editorjs#extension-configuration)
Extension configuration
import { Editor } from '@tiptap/core'
import StarterKit from '@tiptap/starter-kit'
import Image from '@tiptap/extension-image'
import Table from '@tiptap/extension-table'
import TableRow from '@tiptap/extension-table-row'
import TableHeader from '@tiptap/extension-table-header'
import TableCell from '@tiptap/extension-table-cell'
import Highlight from '@tiptap/extension-highlight'
const editor = new Editor({
extensions: [\
StarterKit,\
Image.configure({\
inline: true,\
allowBase64: true,\
}),\
Table.configure({\
resizable: true,\
}),\
TableRow,\
TableHeader,\
TableCell,\
Highlight.configure({\
multicolor: true,\
}),\
],
})
### [](https://tiptap.dev/docs/guides/migrate-from-editorjs#custom-extensions)
Custom extensions
For Editor.js custom tools, create custom Tiptap extensions. See our [custom extensions guide](https://tiptap.dev/docs/editor/extensions/custom-extensions)
for detailed instructions.
[](https://tiptap.dev/docs/guides/migrate-from-editorjs#ui-implementation)
UI implementation
--------------------------------------------------------------------------------------------
### [](https://tiptap.dev/docs/guides/migrate-from-editorjs#toolbar-implementation)
Toolbar implementation
Editor.js's inline toolbar can be replicated with Tiptap's toolbar components:
// Tiptap toolbar (React example)
function Toolbar({ editor }) {
if (!editor) return null
return (
)
}
### [](https://tiptap.dev/docs/guides/migrate-from-editorjs#pre-built-ui-components)
Pre-built UI components
For faster development, use Tiptap's pre-built UI components:
* Explore our [UI components](https://tiptap.dev/docs/ui-components/getting-started/overview)
for ready-to-use components
* Check out practical examples in our [default text editor example](https://tiptap.dev/docs/examples/basics/default-text-editor)
### [](https://tiptap.dev/docs/guides/migrate-from-editorjs#inline-toolbar-editorjs-style)
Inline toolbar (Editor.js style)
Replicate Editor.js's inline toolbar using Tiptap's BubbleMenu:
import { BubbleMenu } from '@tiptap/react'
function MyEditor() {
const editor = useEditor({
extensions: [StarterKit],
})
return (
<>
>
)
}
### [](https://tiptap.dev/docs/guides/migrate-from-editorjs#block-based-ui-alternative-approach)
Block-based UI (alternative approach)
If you prefer Editor.js's block-based approach, use our [Block editor template](https://tiptap.dev/docs/ui-components/getting-started/overview)
(coming soon) to achieve the UI of a Notion-like block-based text editor out of the box.
[](https://tiptap.dev/docs/guides/migrate-from-editorjs#migration-checklist)
Migration checklist
------------------------------------------------------------------------------------------------
Install Tiptap packages
Convert Editor.js JSON content to HTML
Replace Editor.js initialization with Tiptap setup
Map Editor.js tools to Tiptap extensions
Migrate custom tools to custom extensions or [Node Views](https://tiptap.dev/docs/editor/extensions/custom-extensions/node-views)
Implement toolbar UI components
Test content compatibility
Convert existing content to JSON format (recommended)
Implement custom extensions for any missing functionality
Update event handlers and API calls
Test inline toolbar functionality
Verify image upload and handling
Test block-specific features (quotes, code blocks, etc.)
[](https://tiptap.dev/docs/guides/migrate-from-editorjs#next-steps)
Next steps
------------------------------------------------------------------------------
* Explore the [extension overview](https://tiptap.dev/docs/editor/extensions/overview)
to discover all available extensions
* Learn about [custom extensions](https://tiptap.dev/docs/editor/extensions/custom-extensions)
for advanced customization
* Check out our [examples](https://tiptap.dev/docs/examples)
for implementation inspiration
* Review the [performance guide](https://tiptap.dev/docs/guides/performance)
for optimization tips
[PreviouslyMigrate from Lexical](https://tiptap.dev/docs/guides/migrate-from-lexical)
[Next upMigrate from Draft.js](https://tiptap.dev/docs/guides/migrate-from-draftjs)
---
# Export to JSON and HTML | Tiptap Editor Docs
You can store your content as a JSON object or as a good old HTML string. Both work fine. And of course, you can pass both formats to the editor to restore your content. Here is an interactive example, that exports the content as HTML and JSON when the document is changed:
[](https://tiptap.dev/docs/guides/output-json-html#export)
Export
-----------------------------------------------------------------
### [](https://tiptap.dev/docs/guides/output-json-html#option-1-json)
Option 1: JSON
JSON is probably easier to loop through, for example to look for a mention and it’s more like what Tiptap uses under the hood. Anyway, if you want to use JSON to store the content we provide a method to retrieve the content as JSON:
const json = editor.getJSON()
You can store that in your database (or send it to an API) and restore the document initially:
new Editor({
content: {
type: 'doc',
content: [\
// …\
],
},
})
Or if you need to wait for something, you can do it later through the editor instance:
editor.commands.setContent({
type: 'doc',
content: [\
// …\
],
})
Here is an interactive example where you can see that in action:
### [](https://tiptap.dev/docs/guides/output-json-html#option-2-html)
Option 2: HTML
HTML can be easily rendered in other places, for example in emails and it’s widely used, so it’s probably easier to switch the editor at some point. Anyway, every editor instance provides a method to get HTML from the current document:
const html = editor.getHTML()
This can then be used to restore the document initially:
new Editor({
content: `
Example Text
`,
})
Or if you want to restore the content later (e. g. after an API call has finished), you can do that too:
editor.commands.setContent(`
Example Text
`)
Use this interactive example to fiddle around:
### [](https://tiptap.dev/docs/guides/output-json-html#option-3-yjs)
Option 3: Y.js
Our editor has top notch support for Y.js, which is amazing to add features like [realtime collaboration, offline editing, or syncing between devices](https://tiptap.dev/docs/collaboration/getting-started/overview)
.
Internally, Y.js stores a history of all changes. That can be in the browser, on a server, synced with other connected clients, or on a USB stick. But, it’s important to know that Y.js needs those stored changes. A simple JSON document is not enough to merge changes.
Sure, you can import existing JSON documents to get started and get a JSON out of Y.js, but that’s more like an import/export format. It won’t be your single source. That’s important to consider when adding Y.js for one of the mentioned use cases.
That said, it’s amazing and we’re about to provide an amazing backend, that makes all that a breeze.
### [](https://tiptap.dev/docs/guides/output-json-html#markdown)
Markdown
Tiptap already provides import, export, and REST API conversions for Markdown (including GitHub Flavored Markdown). This lets you:
* **Import `.md` or GFM** files into a Tiptap editor, converting them to Tiptap JSON
* **Export** Tiptap JSON to standard Markdown or GFM, letting you save or share your editor content as a `.md` file.
* **Integrate server-side** (no editor required) by sending or retrieving `.md` content via our Conversion REST API.
See [Markdown Conversion](https://tiptap.dev/docs/conversion/import/markdown/rest-api)
for details on how to handle other Markdown features, plus examples for both in-editor workflows and server-side usage.
**Tiptap v3 will deepen Markdown support.** We’re committed to making Markdown more robust and easier to integrate for advanced (AI) use cases.
If you are looking to convert ProseMirror JSON to Markdown, you can use the `@tiptap/static-renderer` package, which can convert ProseMirror JSON to Markdown. See more info and examples in it's [documentation](https://tiptap.dev/docs/editor/api/utilities/static-renderer)
.
[](https://tiptap.dev/docs/guides/output-json-html#listen-for-changes)
Listen for changes
-----------------------------------------------------------------------------------------
If you want to continuously store the updated content while people write, you can [hook into events](https://tiptap.dev/docs/editor/api/events)
. Here is an example how that could look like:
const editor = new Editor({
// intial content
content: `
Example Content
`,
// triggered on every change
onUpdate: ({ editor }) => {
const json = editor.getJSON()
// send the content to an API here
},
})
[](https://tiptap.dev/docs/guides/output-json-html#render)
Render
-----------------------------------------------------------------
### [](https://tiptap.dev/docs/guides/output-json-html#option-1-read-only-instance-of-tiptap)
Option 1: Read-only instance of Tiptap
To render the saved content, set the editor to read-only. That’s how you can achieve the exact same rendering as it’s in the editor, without duplicating your CSS and other code.
### [](https://tiptap.dev/docs/guides/output-json-html#option-2-generate-html-from-prosemirror-json)
Option 2: Generate HTML from ProseMirror JSON
If you need to render the content on the server side, for example to generate the HTML for a blog post, which has been written in Tiptap, you’ll probably want to do just that without an actual editor instance.
That’s what the `generateHTML()` is for. It’s a helper function which renders HTML without an actual editor instance.
By the way, the other way is possible, too. The below examples shows how to generate JSON from HTML.
### [](https://tiptap.dev/docs/guides/output-json-html#option-3-static-renderer)
Option 3: Static Renderer
Generate HTML, React Elements or Markdown from ProseMirror JSON
If you want to render the content in a static way, for example in a blog post, you can use the `@tiptap/static-renderer` package. It can convert ProseMirror JSON to HTML, React Elements or Markdown.
npm install @tiptap/static-renderer
To learn more about the static renderer, check out the [static renderer documentation](https://tiptap.dev/docs/editor/api/utilities/static-renderer)
.
[](https://tiptap.dev/docs/guides/output-json-html#migrate)
Migrate
-------------------------------------------------------------------
### [](https://tiptap.dev/docs/guides/output-json-html#from-draftjs)
From Draft.js
If you’re migrating from Draft.js to Tiptap, you probably have your content stored as JSON. That’s great, because Tiptap can handle that with the [draft-js-to-tiptap package](https://github.com/ueberdosis/draft-js-to-tiptap)
. See more info and examples in it's [documentation](https://github.com/ueberdosis/draft-js-to-tiptap/blob/main/README.md)
.
### [](https://tiptap.dev/docs/guides/output-json-html#others)
Others
If you’re migrating existing content to Tiptap we would recommend to get your existing output to HTML. That’s probably the best format to get your initial content into Tiptap, because ProseMirror ensures there is nothing wrong with it. Even if there are some tags or attributes that aren’t allowed (based on your configuration), Tiptap just throws them away quietly.
We’re about to go through a few cases to help with that, for example we provide a PHP package to convert HTML to a compatible JSON structure: [ueberdosis/prosemirror-to-html](https://github.com/ueberdosis/html-to-prosemirror)
.
[Share your experiences with us!](mailto:humans@tiptap.dev)
We’d like to add more information here.
[](https://tiptap.dev/docs/guides/output-json-html#security)
Security
---------------------------------------------------------------------
There is no reason to use one or the other because of security concerns. If someone wants to send malicious content to your server, it doesn’t matter if it’s JSON or HTML. It doesn’t even matter if you’re using Tiptap or not. You should always validate user input.
[PreviouslyInvalid schema handling](https://tiptap.dev/docs/guides/invalid-schema)
[Next upCollaboration Auth](https://tiptap.dev/docs/guides/authentication)
---
# Pro Extensions | Tiptap Editor Docs
Tiptap Pro extensions add advanced capabilities to the Tiptap Editor such as versioning and AI-assisted content generation. A Tiptap account is required to access Pro extensions. Select extensions such as Snapshots, Comments, and the Content AI extension also require an active subscription.
Tiptap Pro and Cloud Extensions unlock features like collaboration, version history, comments, AI-assisted editing and more. You install them from Tiptap's private NPM registry with your personal access token.
### Security warning
Treat your authentication tokens like passwords to prevent unauthorized use. Each Tiptap user has a unique authentication token that does not expire. We recommend creating a dedicated user for CI/CD applications.
[](https://tiptap.dev/docs/guides/pro-extensions#get-your-access-token)
Get your access token
---------------------------------------------------------------------------------------------
1. Sign in to your Tiptap account (or [sign up](https://cloud.tiptap.dev/register)
).
2. Start your [trial or subscribe](https://cloud.tiptap.dev/v2/billing)
to a Tiptap plan.
3. Copy your personal NPM token from [My features → Pro Extensions](https://cloud.tiptap.dev/v2/features/pro-extensions)
.
[](https://tiptap.dev/docs/guides/pro-extensions#configure-authentication-per-project)
Configure authentication per project
---------------------------------------------------------------------------------------------------------------------------
Add the registry and token to the package-manager config in your project root.
### Tip
Reference `TIPTAP_PRO_TOKEN` as an environment variable to avoid committing credentials.
### [](https://tiptap.dev/docs/guides/pro-extensions#npm-pnpm-or-yarn-classic)
npm, pnpm, or Yarn Classic
Create or update .npmrc:
@tiptap-pro:registry=https://registry.tiptap.dev/
//registry.tiptap.dev/:_authToken=${TIPTAP_PRO_TOKEN}
### [](https://tiptap.dev/docs/guides/pro-extensions#yarn-modern-yarn-2)
Yarn Modern (Yarn 2+)
Create or update .yarnrc.yml:
npmScopes:
tiptap-pro:
npmAlwaysAuth: true
npmRegistryServer: "https://registry.tiptap.dev/"
npmAuthToken: ${TIPTAP_PRO_TOKEN}
Add `.npmrc` or `.yarnrc.yml` to `.gitignore`.
### Warning
This is essential to avoid leaking your credentials if you specify the authentication token directly in the configuration file.
Once you've configured authentication for a project, you can install Pro Extensions like any other Editor extension.
If you use environment variables, pass the authentication token during installation:
TIPTAP_PRO_TOKEN=actual-auth-token npm install --save @tiptap-pro/extension-comments
[](https://tiptap.dev/docs/guides/pro-extensions#configure-global-authentication)
Configure global authentication
-----------------------------------------------------------------------------------------------------------------
You can set up authentication once for **all** of your projects by updating the package manager configuration file at the user level. This is useful for CI/CD environments.
1. Sign in to your Tiptap account (or create a free one).
2. Start your [trial or subscribe](https://cloud.tiptap.dev/v2/billing)
to a Tiptap plan.
3. (Optional) [Invite a team member](https://cloud.tiptap.dev/v2/team/members)
for your CI/CD pipeline.
4. Copy your personal NPM token from [My features → Pro Extensions](https://cloud.tiptap.dev/v2/features/pro-extensions)
.
### [](https://tiptap.dev/docs/guides/pro-extensions#npm-or-yarn-classic)
npm or Yarn Classic
npm config set "@tiptap-pro:registry" https://registry.tiptap.dev/
npm config set "//registry.tiptap.dev/:_authToken" actual-auth-token
### [](https://tiptap.dev/docs/guides/pro-extensions#yarn-modern)
Yarn Modern
yarn config set --home npmScopes.@tiptap-pro.npmRegistryServer "https://registry.tiptap.dev/"
yarn config set --home npmScopes.@tiptap-pro.npmAlwaysAuth "true"
yarn config set --home npmScopes.@tiptap-pro.npmAuthToken "actual-auth-token"
### [](https://tiptap.dev/docs/guides/pro-extensions#pnpm)
pnpm
pnpm config set --global "@tiptap-pro:registry" https://registry.tiptap.dev/
pnpm config set "//registry.tiptap.dev/:_authToken" actual-auth-token
You can now install any Tiptap Pro extension in any repository:
npm install --save @tiptap-pro/extension-comments
[Next upFAQ](https://tiptap.dev/docs/guides/faq)
---
# TypeScript | Tiptap Editor Docs
The whole Tiptap codebase is written in TypeScript. If you haven’t heard of it or never used it, no worries. You don’t have to.
TypeScript extends JavaScript by adding types (hence the name). It adds new syntax, which doesn’t exist in Vanilla JavaScript. It’s actually removed before running in the browser, but this step – the compilation – is important to find bugs early. It checks if you pass the right types of data to functions. For a big and complex project, that’s very valuable. It means we’ll get notified of lots of bugs, before shipping code to you.
**Anyway, if you don’t use TypeScript in your project, that’s fine.** You will still be able to use Tiptap and nevertheless get a nice autocomplete for the Tiptap API (if your editor supports it, but most do).
If you are using TypeScript in your project and want to extend Tiptap, there are two types that are good to know about.
[](https://tiptap.dev/docs/guides/typescript#types)
Types
---------------------------------------------------------
### [](https://tiptap.dev/docs/guides/typescript#options-types)
Options types
To extend or create default options for an extension, you’ll need to define a custom type, here is an example:
import { Extension } from '@tiptap/core'
export interface CustomExtensionOptions {
awesomeness: number
}
const CustomExtension = Extension.create({
addOptions() {
return {
awesomeness: 100,
}
},
})
### [](https://tiptap.dev/docs/guides/typescript#storage-types)
Storage types
To add types for your extension storage, you’ll have to both pass that as a second type parameter and define the storage type, here is an example:
import { Extension } from '@tiptap/core'
export interface CustomExtensionStorage {
awesomeness: number
}
// This extends the Storage interface, enabling `editor.storage.customExtension` to be of type `CustomExtensionStorage`
declare module '@tiptap/core' {
interface Storage {
customExtension: CustomExtensionStorage
}
}
// This creates the extension, with the storage type passed as the second type parameter
// Enabling `this.storage` to be of type `CustomExtensionStorage`
const CustomExtension = Extension.create<{}, CustomExtensionStorage>({
name: 'customExtension',
addStorage() {
return {
awesomeness: 100,
}
},
})
Now, using storage outside of the extension will be type-safe:
const customStorage = editor.storage.customExtension
customStorage.awesomeness = 200 // ✅
### [](https://tiptap.dev/docs/guides/typescript#command-type)
Command type
The core package also exports a `Command` type, which needs to be added to all commands that you specify in your code. Here is an example:
import { Extension } from '@tiptap/core'
declare module '@tiptap/core' {
interface Commands {
customExtension: {
/**
* Comments will be added to the autocomplete.
*/
yourCommand: (someProp: any) => ReturnType
}
}
}
const CustomExtension = Extension.create({
addCommands() {
return {
yourCommand:
(someProp) =>
({ commands }) => {
// …
},
}
},
})
That’s basically it. We’re doing all the rest automatically.
[PreviouslyCollaboration API](https://tiptap.dev/docs/guides/collaboration-api)
[Next upMigrate from TinyMCE](https://tiptap.dev/docs/guides/migrate-from-tinymce)
---
# Trial | Tiptap Resources
Your Tiptap trial gives you 30 days of free access to all paid Tiptap products, Pro extensions, Pro UI components, and cloud features. You don’t need a credit card to get started.
[](https://tiptap.dev/docs/resources/tiptap-trial#benefits)
Benefits
--------------------------------------------------------------------
* **No credit card required** Activate your trial instantly without payment details.
* **Full access to Pro and Cloud features** Collaboration, Comments, Content AI, History, Conversion, and more. Pick a plan, and you get everything that plan offers.
* **Staging and development** Use the trial for development, staging, or testing. Just don’t ship it to production until you’re on a paid plan.
* **No migration needed** Your documents and settings remain intact when you upgrade to a paid plan.
[](https://tiptap.dev/docs/resources/tiptap-trial#how-to-start-your-trial)
How to start your trial
--------------------------------------------------------------------------------------------------
1. Create a free Tiptap account at [cloud.tiptap.dev](https://cloud.tiptap.dev/)
.
2. Go to your [billing dashboard](https://cloud.tiptap.dev/v2/billing)
.
3. Select the plan you want to trial.
4. Confirm to activate your trial.
### Keep it going
Add payment details in your [billing dashboard](https://cloud.tiptap.dev/v2/billing)
before day 30 to keep using Tiptap Pro and Cloud without interruption.
[](https://tiptap.dev/docs/resources/tiptap-trial#manage-your-trial)
Manage your trial
--------------------------------------------------------------------------------------
1. In your [billing dashboard](https://cloud.tiptap.dev/v2/billing)
, switch to a different plan any time before your trial ends.
2. Upgrade to a paid plan anytime before day 30 to avoid interruption.
3. Continue using Tiptap without migrating your documents or configuration.
[](https://tiptap.dev/docs/resources/tiptap-trial#whats-included)
What's included?
----------------------------------------------------------------------------------
You get access to the features of the plan you choose. Need a breakdown? [Check the pricing page](https://tiptap.dev/docs/docs/resources/pricing)
.
[PreviouslyExamples](https://tiptap.dev/docs/examples)
[Next upContributing](https://tiptap.dev/docs/resources/contributing)
---
# Changelog | Tiptap Editor Docs
Tiptap consists of more than 50 separate packages. Here is everything you need to follow changes:
1. Check [GitHub releases page](https://github.com/ueberdosis/tiptap/releases)
to get the best overview.
2. For even more details, head over to [Tiptaps CHANGELOG on GitHub](https://github.com/ueberdosis/tiptap/blob/main/CHANGELOG.md)
.
3. In addition, all packages have [a separate `CHANGELOG.md`](https://github.com/ueberdosis/tiptap/blob/main/packages)
, too.
4. It’s also helpful to keep an eye on the [merged Pull Requests on GitHub](https://github.com/ueberdosis/tiptap/pulls?q=is%3Apr+is%3Aclosed)
.
[We’ll write blog posts](https://tiptap.dev/blog)
about bigger changes and keep them in a changelog here.
[PreviouslyContributing](https://tiptap.dev/docs/resources/contributing)
[Next upPro license](https://tiptap.dev/pro-license)
---
# Contributing | Tiptap
Tiptap would be nothing without its lively community. Contributions have always been and will always be welcome. Here is a little bit you should know, before you send your contribution:
[](https://tiptap.dev/docs/resources/contributing#welcome-examples)
Welcome examples
------------------------------------------------------------------------------------
* Failing regression tests as bug reports
* Documentation improvements, e. g. fix a typo, add a section
* New features for existing extensions, e. g. a new configureable option
* Well explained, non-breaking changes to the core
[](https://tiptap.dev/docs/resources/contributing#wont-merge)
Won’t merge
-------------------------------------------------------------------------
* New extensions, which we then need to support and maintain
[](https://tiptap.dev/docs/resources/contributing#submit-ideas)
Submit ideas
----------------------------------------------------------------------------
Make sure to open an issue and outline your idea first. We’ll get back to you quickly and let you know if there is a chance we can merge your contribution.
[](https://tiptap.dev/docs/resources/contributing#set-up-the-development-environment)
Set up the development environment
------------------------------------------------------------------------------------------------------------------------
It’s not too hard to tinker around with the official repository. You’ll need [Git](https://github.com/git-guides/install-git)
, [Node](https://nodejs.org/en/download/)
and [pnpm](https://pnpm.io/installation)
installed. Here is what you need to do then:
1. Copy the code to your local machine: `$ git clone git@github.com:ueberdosis/tiptap.git`
2. Install dependencies: `$ pnpm install`
3. Start the development environment: `$ pnpm run start`
4. Open [http://localhost:3000](http://localhost:3000/)
in your favorite browser.
5. Start playing around!
[](https://tiptap.dev/docs/resources/contributing#our-code-style)
Our code style
--------------------------------------------------------------------------------
There is an eslint config that ensures a consistent code style. To check for errors, run `$ pnpm run lint`. That’ll be checked when you send a pull request, too. Make sure it’s passing, before sending a pull request.
[](https://tiptap.dev/docs/resources/contributing#test-for-errors)
Test for errors
----------------------------------------------------------------------------------
Your pull request will automatically execute all our existing tests. Make sure that they all pass, before sending a pull request. Run all tests locally with `$ pnpm run test` or run single tests (e. g. when writing new ones) with `$ pnpm run test:open`.
[](https://tiptap.dev/docs/resources/contributing#create-your-own-extensions)
Create your own extensions
--------------------------------------------------------------------------------------------------------
If you want to create and maintain your own extensions, you can use your `create-tiptap-extension` CLI tool. It will create a new extension boilerplate with all necessary files and the build process. It's as easy as running
pnpm init tiptap-extension
If you want to let us know about your extension you can give us a hint on [X](https://x.com/tiptap_editor)
or [Discord](https://tiptap.dev/discord)
.
[](https://tiptap.dev/docs/resources/contributing#document-your-contributions)
Document your contributions
----------------------------------------------------------------------------------------------------------
When contributing to Tiptap, it's important to understand that Tiptap's codebase and its documentation are managed in two separate repositories. If you make changes or enhancements to Tiptap, documenting these changes is of course important for clarity and usability by others. Ensure you update the documentation repository corresponding to any alterations you make in the code.
1. Tiptap Repository: [Tiptap Code Repository](https://github.com/ueberdosis/tiptap)
- Modify the code here.
2. Documentation Repository: [Tiptap Documentation Repository](https://github.com/ueberdosis/tiptap-docs)
- Update or add documentation here to reflect changes made in the Tiptap repository.
In your PR to the Tiptap code repository, include a reference to the corresponding updates you've made in the Documentation repository. Alternatively, when submitting a PR to the Documentation repository, make sure to include references to any associated code changes in the Tiptap repository if they are relevant to the documentation updates.
[](https://tiptap.dev/docs/resources/contributing#further-questions)
Further questions
--------------------------------------------------------------------------------------
Any further questions? Create a [new issue](https://github.com/ueberdosis/tiptap/issues)
or [discussion](https://github.com/ueberdosis/tiptap/discussions)
in the repository. We’ll get back to you.
[PreviouslyTiptap trial](https://tiptap.dev/docs/resources/tiptap-trial)
[Next upEditor changelog](https://tiptap.dev/docs/resources/changelog)
---
# Integration performance | Tiptap Editor Docs
Tiptap is a very performant editor (even able to edit an entire book!), often when you run into performance issues, it's not Tiptap itself, but the way you integrate it into your app. Here are some tips to make sure your editor runs smoothly.
[](https://tiptap.dev/docs/guides/performance#react-tiptap-editor-integration)
React Tiptap Editor Integration
--------------------------------------------------------------------------------------------------------------
When using Tiptap with React, the most common performance issue is that the editor is re-rendered too often. This can happen for several reasons:
* When using the `useEditor` hook, it by default will re-render the editor on every change. So, you should isolate the editor (and things that depend on it) in a separate component to prevent unnecessary re-renders.
* The editor should be isolated from renders that don't affect it. For example, if you have a sidebar that doesn't interact with the editor, it should be in a separate component.
Luckily, the solution for most of these issues is the same: isolate the editor in a separate component. Here is an example of how you can do this:
DO: isolate the editor in a separate component
import { EditorContent, useEditor } from '@tiptap/react'
const TiptapEditor = () => {
const editor = useEditor({
extensions,
content,
})
return (
<>
{/* Other components that depend on the editor instance */}
>
)
}
export default TiptapEditor
DON'T: render the editor in the same component as other components
import { EditorContent, useEditor } from '@tiptap/react'
const App = () => {
const [sidebarOpen, setSidebarOpen] = React.useState(false)
const editor = useEditor({
extensions,
content,
})
return (
<>
>
)
}
export default App
These unrelated components will cause the editor to re-render more often than necessary, and make each render more expensive.
### [](https://tiptap.dev/docs/guides/performance#track-down-performance-issues)
Track down performance issues
You can use the React DevTools Profiler to see which components are re-rendering and why. Another strategy is to put a `console.count('editor render')` in the editor component and see how often it is re-rendered. This can help you identify which components are causing unnecessary re-renders.
If it is re-rendered more often than you expect, you can take the following steps:
* Check if the editor is rendering because of its parent component.
* Isolate the editor from unrelated state changes (e.g. opening a sidebar should not cause the editor to re-render).
* Use `useEditorState` to prevent unnecessary re-renders within the editor component.
Hopefully, these tips will help you track down and fix any performance issues you encounter.
### [](https://tiptap.dev/docs/guides/performance#use-useeditorstate-to-prevent-unnecessary-re-renders)
Use `useEditorState` to prevent unnecessary re-renders
The `useEditorState` hook allows you to subscribe to changes in the editor state and re-render only when necessary. This can help you prevent unnecessary re-renders of the editor and its components.
import { useEditor, useEditorState } from '@tiptap/react'
function Component() {
const editor = useEditor({
extensions,
content,
})
const editorState = useEditorState({
editor,
// This function will be called every time the editor state changes
selector: ({ editor }: { editor: Editor }) => ({
// It will only re-render if the bold or italic state changes
isBold: editorInstance.isActive('bold'),
isItalic: editorInstance.isActive('italic'),
}),
})
return (
<>
>
)
}
The `selector` function allows you to specify which parts of the editor state you want to subscribe to. By default this will be deeply compared with the previous selected state, and only re-render if it has changed. You can select any part of the editor state, or even derive new values from it.
### [](https://tiptap.dev/docs/guides/performance#gain-more-control-over-rendering)
Gain more control over rendering
As of Tiptap v2.5.0, you can gain more control over rendering by using the `immediatelyRender` and `shouldRerenderOnTransaction` options. This can be useful if you want to prevent the editor from rendering immediately or on every transaction.
import { useEditor } from '@tiptap/react'
function Component() {
const editor = useEditor({
extensions,
content,
/**
* This option gives us the control to enable the default behavior of rendering the editor immediately.
*/
immediatelyRender: true,
/**
* This option gives us the control to disable the default behavior of re-rendering the editor on every transaction.
*/
shouldRerenderOnTransaction: false,
})
return
}
[](https://tiptap.dev/docs/guides/performance#react-node-views-performance)
React node views performance
--------------------------------------------------------------------------------------------------------
Node views allow you to render custom components in place of nodes within the editor. This enables you to embed any kind of content in your editor. However, when using React components, be aware of potential performance implications.
For technical reasons, node views are expected to be rendered synchronously. Tiptap will create new elements for each node view and mount your React component in them. This can be expensive, especially if you have many instances of node views throughout your editor.
We've optimized as much as possible on our side, but if you find that rendering node views is causing performance issues, consider using plain HTML elements or a different approach to render your content within your node view.
[PreviouslyAccessibility](https://tiptap.dev/docs/guides/accessibility)
[Next upReact Composable API](https://tiptap.dev/docs/guides/react-composable-api)
---
# Patched Security Incidents | Tiptap Resources
All known security incidents that have been identified and resolved in Tiptap releases are listed below.
Each incident includes details about the vulnerability type, severity level, and resolution status.
AllUI Component
[### XSS vulnerability in link popover\
\
HIGH\
\
The Link Popover UI Component accepted malicious URLs, allowing potential XSS attacks.\
\
Resolved Jun 25, 2025, 9:39 PM GMT+2•UI Component](https://tiptap.dev/docs/resources/incidents/06-25-2025-link-popover)
---
# Awareness | Tiptap Collaboration Docs
Awareness in Tiptap Collaboration, powered by Yjs, is helping you share real-time info on users' activities within a collaborative space. This can include details like user presence, cursor positions, and custom user states.
At its core, awareness utilizes its own Conflict-Free Replicated Data Type (CRDT) to ensure that this shared meta-information remains consistent and immediate across all users, without maintaining a historical record of these states.
You can read more about Awareness in the [Yjs documentation on awareness](https://docs.yjs.dev/getting-started/adding-awareness)
.
[](https://tiptap.dev/docs/collaboration/core-concepts/awareness#necessary-provider-events)
Necessary provider events
---------------------------------------------------------------------------------------------------------------------
Awareness updates trigger specific [provider events](https://tiptap.dev/docs/collaboration/provider/events)
to develop interactive features based on user actions and presence:
* `awarenessUpdate`: This event signals that a user is active. It triggers without actual state changes, serving as a 'heartbeat' to inform others the user is in the document.
* `awarenessChange`: This event alerts you to any additions, updates, or deletions in the awareness state, reflecting both your local changes and those from remote users.
These events serve as hooks for integrating custom Awareness features.
[](https://tiptap.dev/docs/collaboration/core-concepts/awareness#integrate-awareness)
Integrate awareness
---------------------------------------------------------------------------------------------------------
With your [collaborative environment](https://tiptap.dev/docs/collaboration/getting-started/install)
set up, you're all set to integrate Awareness, which is natively supported by the Collaboration Provider.
To kick things off, update the Awareness state with any relevant information. As an example we'll use a user's name, cursor color, and mouse position as examples.
### [](https://tiptap.dev/docs/collaboration/core-concepts/awareness#set-the-awareness-field)
Set the awareness field
Let's assign a name, color, and mouse position to the user. This is just an example; feel free to use any data relevant to your application.
// Set the awareness field for the current user
provider.setAwarenessField('user', {
// Share any information you like
name: 'Kevin James',
color: '#ffcc00',
})
### [](https://tiptap.dev/docs/collaboration/core-concepts/awareness#listen-for-changes)
Listen for changes
Set up an event listener to track changes in the Awareness states across all connected users:
// Listen for updates to the states of all users
provider.on('awarenessChange', ({ states }) => {
console.log(states)
})
You can now view these updates in your browser's console as you move on to the next step.
### [](https://tiptap.dev/docs/collaboration/core-concepts/awareness#track-mouse-movement)
Track mouse movement
Next, we'll add an event listener to our app to track mouse movements and update the awareness' information accordingly.
document.addEventListener('mousemove', (event) => {
// Share any information you like
provider.setAwarenessField('user', {
name: 'Kevin James',
color: '#ffcc00',
mouseX: event.clientX,
mouseY: event.clientY,
})
})
Check your browser's console to see the stream of events as users move their mice.
[](https://tiptap.dev/docs/collaboration/core-concepts/awareness#add-carets-and-selections)
Add carets and selections
---------------------------------------------------------------------------------------------------------------------
With basic Awareness in place, consider adding the [Collaboration Caret](https://tiptap.dev/docs/editor/extensions/functionality/collaboration-caret)
extension to your editor. This extension adds caret positions, text selections, and personalized details (such as names and colors) of all participating users to your editor.
[PreviouslyEvents](https://tiptap.dev/docs/collaboration/provider/events)
[Next upWebhooks](https://tiptap.dev/docs/collaboration/core-concepts/webhooks)
---
# Documents | Tiptap Collaboration
Collaboration Documents form the backbone of Tiptap Collaboration, storing everything from content and comments to versions and metadata using the Yjs format.
Typically, users manage these documents using the REST API or track changes with the Collaboration Webhook, which sends detailed updates. Tiptap converts the documents into HTML or JSON for you, so you don't have to deal directly with the Yjs format.
* **Host your documents:** Choose between cloud, dedicated cloud or on-premises deployment.
* **Document REST API:** Create, update, and delete documents programmatically.
* **Webhooks:** Automate responses to real-time document and comment events.
* **Document versioning and comparison:** Track changes in documents through automatic or manual versioning, and visually compare differences between snapshots.
* **Content injection:** Modify document content server-side with the REST API, even during active collaboration sessions.
Enterprise on-premises solution
-------------------------------
Integrate Collaboration and all other Tiptap features into your infrastructure.
* On-premises:
Deploy our docker images in your own stack
* High availability cluster:
Scale confidently to millions of users
* Dedicated support:
Custom development and integration support in Chat
[Let's talk](https://tiptap.dev/contact-sales)
[](https://tiptap.dev/docs/collaboration/documents#integrate-documents)
Integrate documents
-------------------------------------------------------------------------------------------
Integrating documents into your editor and application with Tiptap is straightforward. By adding the Tiptap Collaboration provider to your setup, documents are automatically stored and managed within the Tiptap Collaboration framework.
This integration immediately enables you to use all document features, like storing collaborative documents, managing version histories, using the REST API, and injecting content.
### Note
You can easily migrate your documents from our cloud to an on-premises server at a later time.
1. **Integrate the Tiptap Editor:** Follow the [installation guide](https://tiptap.dev/docs/collaboration/getting-started/install)
to setup an editor.
2. **Activate a plan:** Begin a [free trial or choose a subscription](https://cloud.tiptap.dev/v2/billing)
.
3. **Add an environment.** On your [Dashboard](https://cloud.tiptap.dev/)
, click the »Add environment« button, enter a name and pick the region that is closest to your users.
4. **Check the configuration.** As soon as you save the environment, your document server boots up. Visit the [configuration page](https://cloud.tiptap.dev/v2/configuration/document-server)
to copy your document server ID, API keys, and other connection details.
And now, you are all set to use the document features.
[](https://tiptap.dev/docs/collaboration/documents#retrieve-and-manage-documents)
Retrieve and manage documents
---------------------------------------------------------------------------------------------------------------
Use the [REST API](https://tiptap.dev/docs/collaboration/documents/rest-api)
to fetch documents in `JSON` or `HTML` format for easy integration with your system. For immediate updates on changes, configure [webhooks](https://tiptap.dev/docs/collaboration/core-concepts/webhooks)
to receive real-time notifications.
**Track changes in documents:** The [Snapshot](https://tiptap.dev/docs/collaboration/documents/snapshot)
extension in Tiptap Collaboration automatically captures and stores snapshots of documents at designated intervals. It also allows for manual versioning, enabling users to track detailed changes and document evolution.
**Compare snapshots:** The [compare snapshots](https://tiptap.dev/docs/collaboration/documents/snapshot-compare)
extension lets you visually compare two versions of a document, highlighting changes and their authors, helping you see modifications over time.
**Inject content:** Update the content of active documents with an [Patch Document endpoint](https://tiptap.dev/docs/collaboration/documents/content-injection)
, which allows server-side modifications even during active user collaboration.
[PreviouslyWebhooks](https://tiptap.dev/docs/collaboration/core-concepts/webhooks)
[Next upREST API](https://tiptap.dev/docs/collaboration/documents/rest-api)
---
# Migrate from Quill | Tiptap Editor Docs
Tiptap is a popular Quill alternative and migrating from Quill to Tiptap is straightforward. This guide will help you transition smoothly from Quill's Delta format to Tiptap's extension system.
[](https://tiptap.dev/docs/guides/migrate-from-quill#content-migration)
Content migration
-----------------------------------------------------------------------------------------
### [](https://tiptap.dev/docs/guides/migrate-from-quill#html-content-compatibility)
HTML content compatibility
Quill uses the Delta format for content storage, which needs to be converted to HTML for Tiptap:
// Convert Quill Delta to HTML first
const quill = new Quill('#temp-editor')
quill.setContents(existingDeltaContent)
const htmlContent = quill.root.innerHTML
// Use HTML content in Tiptap
const editor = new Editor({
content: htmlContent,
extensions: [StarterKit],
})
If you already have HTML output from Quill, you can use it directly:
// Your existing Quill HTML content
const existingContent = '
Hello world!
'
// Use directly in Tiptap
const editor = new Editor({
content: existingContent,
extensions: [StarterKit],
})
While HTML works perfectly, we recommend converting it to Tiptap's JSON format for better performance and readability. For batch conversion of existing content, use the [HTML utility](https://tiptap.dev/docs/editor/api/utilities/html)
to convert HTML to JSON programmatically.
[](https://tiptap.dev/docs/guides/migrate-from-quill#editor-setup)
Editor setup
-------------------------------------------------------------------------------
### [](https://tiptap.dev/docs/guides/migrate-from-quill#installation)
Installation
First, install Tiptap and its dependencies:
npm install @tiptap/core @tiptap/starter-kit
Tiptap supports all modern frontend UI frameworks like React and Vue. Follow the framework-specific installation instructions in our [installation guides](https://tiptap.dev/docs/editor/getting-started/install)
.
### [](https://tiptap.dev/docs/guides/migrate-from-quill#basic-editor-setup)
Basic editor setup
Replace your Quill initialization with Tiptap:
// Quill (before)
const quill = new Quill('#editor', {
theme: 'snow',
modules: {
toolbar: [\
['bold', 'italic', 'underline'],\
['link', 'image'],\
[{ list: 'ordered' }, { list: 'bullet' }],\
[{ header: [1, 2, 3, false] }],\
],
},
})
// Tiptap (after)
import { Editor } from '@tiptap/core'
import StarterKit from '@tiptap/starter-kit'
const editor = new Editor({
element: document.querySelector('#editor'),
extensions: [StarterKit],
content: '
Hello World!
',
})
[](https://tiptap.dev/docs/guides/migrate-from-quill#extensions)
Extensions
---------------------------------------------------------------------------
### [](https://tiptap.dev/docs/guides/migrate-from-quill#understanding-tiptaps-extension-system)
Understanding Tiptap's extension system
Tiptap uses a modular extension system that resembles Quill's module system. Each feature is an independent extension that can be configured and customized.
The [StarterKit](https://tiptap.dev/docs/editor/extensions/functionality/starterkit)
is a bundle of all the basic extensions, and you can add or remove other extensions as needed.
Explore all available extensions in our [extensions guide](https://tiptap.dev/docs/editor/extensions/overview)
, or [create your own](https://tiptap.dev/docs/editor/extensions/custom-extensions)
to support custom functionality and HTML elements.
### [](https://tiptap.dev/docs/guides/migrate-from-quill#common-quill-module-equivalents)
Common Quill module equivalents
| Quill Feature | Tiptap Extension | Notes |
| --- | --- | --- |
| Bold | `Bold` | Included in StarterKit |
| Italic | `Italic` | Included in StarterKit |
| Underline | `Underline` | Included in StarterKit |
| Link | `Link` | Included in StarterKit |
| Image | `Image` | Available separately |
| List (bullet/ordered) | `BulletList`, `OrderedList`, `ListItem` | Included in StarterKit |
| Header | `Heading` | Included in StarterKit |
| Blockquote | `Blockquote` | Included in StarterKit |
| Code Block | `CodeBlock` | Included in StarterKit |
| Strike | `Strike` | Included in StarterKit |
| Color | `TextStyle`, `Color` | Available separately |
| Background | `TextStyle`, `Highlight` | Available separately |
| Align | `TextAlign` | Available separately |
### [](https://tiptap.dev/docs/guides/migrate-from-quill#extension-configuration)
Extension configuration
import { Editor } from '@tiptap/core'
import StarterKit from '@tiptap/starter-kit'
import Image from '@tiptap/extension-image'
import TextAlign from '@tiptap/extension-text-align'
import { Color } from '@tiptap/extension-color'
import TextStyle from '@tiptap/extension-text-style'
const editor = new Editor({
extensions: [\
StarterKit,\
Image.configure({\
inline: true,\
allowBase64: true,\
}),\
TextAlign.configure({\
types: ['heading', 'paragraph'],\
}),\
TextStyle,\
Color.configure({\
types: [TextStyle.name, ListItem.name],\
}),\
],
})
### [](https://tiptap.dev/docs/guides/migrate-from-quill#custom-extensions)
Custom extensions
For Quill custom modules or blots, create custom Tiptap extensions. See our [custom extensions guide](https://tiptap.dev/docs/editor/extensions/custom-extensions)
for detailed instructions.
[](https://tiptap.dev/docs/guides/migrate-from-quill#ui-implementation)
UI implementation
-----------------------------------------------------------------------------------------
### [](https://tiptap.dev/docs/guides/migrate-from-quill#toolbar-implementation)
Toolbar implementation
Quill's toolbar configuration translates to custom UI components in Tiptap:
// Quill toolbar config
toolbar: [\
['bold', 'italic', 'underline'],\
['link', 'image'],\
[{ list: 'ordered' }, { list: 'bullet' }],\
[{ header: [1, 2, 3, false] }],\
]
// Tiptap equivalent (React example)
function Toolbar({ editor }) {
if (!editor) return null
return (
)
}
### [](https://tiptap.dev/docs/guides/migrate-from-quill#pre-built-ui-components)
Pre-built UI components
For faster development, use Tiptap's pre-built UI components:
* Explore our [UI components](https://tiptap.dev/docs/ui-components/getting-started/overview)
for ready-to-use components
* Check out practical examples in our [default text editor example](https://tiptap.dev/docs/examples/basics/default-text-editor)
### [](https://tiptap.dev/docs/guides/migrate-from-quill#bubble-toolbar-quills-bubble-theme)
Bubble toolbar (Quill's bubble theme)
Replicate Quill's bubble theme using Tiptap's BubbleMenu:
import { BubbleMenu, useEditor } from '@tiptap/react'
function MyEditor() {
const editor = useEditor({
extensions: [StarterKit],
})
return (
<>
>
)
}
[](https://tiptap.dev/docs/guides/migrate-from-quill#migration-checklist)
Migration checklist
---------------------------------------------------------------------------------------------
Install Tiptap packages
Convert Quill Delta content to HTML
Replace Quill initialization with Tiptap setup
Map Quill modules to Tiptap extensions
Migrate toolbar configuration to custom UI components
Test content compatibility
Convert existing content to JSON format (recommended)
Implement custom extensions for any missing functionality
Update event handlers and API calls
Test bubble menu functionality if using bubble theme
Verify image upload and handling
Test formatting and styling features
[](https://tiptap.dev/docs/guides/migrate-from-quill#next-steps)
Next steps
---------------------------------------------------------------------------
* Explore the [extension overview](https://tiptap.dev/docs/editor/extensions/overview)
to discover all available extensions
* Learn about [custom extensions](https://tiptap.dev/docs/editor/extensions/custom-extensions)
for advanced customization
* Check out our [examples](https://tiptap.dev/docs/examples)
for implementation inspiration
* Review the [performance guide](https://tiptap.dev/docs/guides/performance)
for optimization tips
[PreviouslyMigrate from CKEditor 5](https://tiptap.dev/docs/guides/migrate-from-ckeditor5)
[Next upMigrate from Slate](https://tiptap.dev/docs/guides/migrate-from-slate)
---
# Webhooks | Tiptap Collaboration Docs
You can define a URL and we will call it every time a document has changed. This is useful for getting the JSON representation of the Yjs document in your own application.
We call your webhook URL when the document is saved to our database. This operation is debounced by 2-10 seconds. So your application won't be flooded by us. Right now we're only exporting the fragment `default` of the Yjs document.
[](https://tiptap.dev/docs/collaboration/core-concepts/webhooks#configure-webhooks)
Configure Webhooks
------------------------------------------------------------------------------------------------------
To configure webhooks for document and comments notifications:
1. Navigate to the [Collaboration settings](https://cloud.tiptap.dev/v2/configuration/document-server)
in your account.
2. Find the webhooks section and add your desired endpoint URL.
After adding your URL, the webhook is immediately live. You'll start receiving notifications for the specified events without any delay.
### Add Comments support to your webhook
If you want to add webhook support for the comments feature and your Document server was created before March 2024, please upgrade your webhook as described [below](https://tiptap.dev/docs/collaboration/core-concepts/webhooks#enable-the-comments-webhook)
.
[](https://tiptap.dev/docs/collaboration/core-concepts/webhooks#example-payload)
Example payload
------------------------------------------------------------------------------------------------
All requests to your webhook URL will contain a header called `X-Hocuspocus-Signature-256` that signs the entire message with your secret. You can find it in the [settings](https://collab.tiptap.dev/apps/settings)
of your document server.
{
"appName": "", // name of your document server
"name": "", // name of the document (URI encoded if necessary)
"time": 0, // current time as ISOString (Date.getTime())
"tiptapJson": {}, // JSON output from Tiptap (see https://tiptap.dev/guide/output#option-1-json): TiptapTransformer.fromYdoc()
"ydocState"?: {}, // optionally contains the entire yDoc as base64. This can be enabled in the runtime configuration (see https://tiptap.dev/docs/collaboration/operations/configure `webhook_include_ydoc_state`)
"clientsCount": 100,// number of currently connected clients
"type": "", // the payload type (if the document was changed, this is DOCUMENT) ; only available if you are on webhooks v2
"trigger": "", // what triggered the event (usually "document.saved") ; only available if you are on webhooks v2
"users": [] // list of users who changed the content since the last webhook ("sub" field from the JWT)
}
[](https://tiptap.dev/docs/collaboration/core-concepts/webhooks#retries)
Retries
--------------------------------------------------------------------------------
Webhooks are not retried by default, but you can enable retries by setting `webhook_retries` to `1` (see [Configure Runtime](https://tiptap.dev/docs/collaboration/operations/configure)
). The retry schedule is as follows:
* 1st retry: 5 seconds after the initial attempt
* 2nd retry: 15 seconds after the last attempt
* 3rd retry: 2 minutes after the last attempt
* 4th retry: 10 minute after the last attempt
* 5th retry: 30 minutes after the last attempt
* 6th retry: 3 hours after the last attempt
All retries include a header `X-Hocuspocus-Retry` with the current retry count. The `time` property in the payload is the timestamp of the initial attempt.
[](https://tiptap.dev/docs/collaboration/core-concepts/webhooks#enable-the-comments-webhook)
Enable the Comments webhook
------------------------------------------------------------------------------------------------------------------------
The webhook that supports comments is automatically enabled for all users that have created their account after March, 2024.
If your account was created before March, 2024 and you're using an older version of the webhook system, you'll need to manually enable the new comments webhooks. Here's how:
1. In case you’ve already implemented a previous Collaboration webhook, make sure to check the `type` and `trigger` fields when processing incoming webhooks.
2. Navigate to the [Collaboration settings](https://cloud.tiptap.dev/v2/configuration/document-server)
in your account.
3. Locate the Webhook section and click on the "Update" button.
This upgrade is necessary to accommodate the introduction of multiple new events being routed to the same webhook endpoint, distinguished by a new `type` and `trigger` field.
If you do not wish to use the comments webhook, no upgrade is necessary.
[](https://tiptap.dev/docs/collaboration/core-concepts/webhooks#loader-webhook)
Loader Webhook
----------------------------------------------------------------------------------------------
In order to initialize documents, you can use the `webhook_loader_url` setting (see [configure runtime](https://tiptap.dev/docs/collaboration/operations/configure)
). This URL will be called if a new document is requested. The webhook will contain a header `Authorization` with your secret, and `document-name` with the name of the requested document.
If you return a yjs update (Y.encodeStateAsUpdate on your side), it will be applied to the document. You can also return Tiptap JSON, if you send `Content-Type: application/json` (from January 2026 / v3.67.0). If you return anything else, the document will be initialized with an empty document. Note that the loader webhook is called only once when the document is created.
The request looks like this:
GET {{webhook_loader_url}}
Authorization: {{jwt secret}}
document-name: {{requested document name}}
[](https://tiptap.dev/docs/collaboration/core-concepts/webhooks#awareness-webhooks)
Awareness Webhooks
------------------------------------------------------------------------------------------------------
If you want to get notified whenever a user connects to or disconnects from a document, you can enable awareness webhooks [here](https://tiptap.dev/docs/collaboration/operations/configure#settings-overview)
. If you need the user parameter, please make sure to pass it to the TiptapCollabProvider, as mentioned [here](https://tiptap.dev/docs/collaboration/provider/integration#configure-the-collaboration-provider)
.
The events look like this:
{
"trigger": "user.connected", // or user.disconnected
"user": "user_1",
"numConnectedUsers": 0,
"appName": "",
"name": "testdocument",
"time": "2025-04-21T19:32:55.632Z",
"type": "DOCUMENT"
}
[](https://tiptap.dev/docs/collaboration/core-concepts/webhooks#custom-fields)
Custom fields
--------------------------------------------------------------------------------------------
If you use custom fields in Yjs, you can add them to the webhook by setting `webhook_include_fields=1` in the [runtime configuration](https://tiptap.dev/docs/collaboration/operations/configure)
. In order for us to identify the correct type of the yjs field, you need to configure them in the Tiptap Typemap.
// store any data in Yjs. See the official docs: https://docs.yjs.dev/api/shared-types
// "config" here can be replaced by any string. A map gives you a key-value store.
const ydoc = new Y.Doc()
ydoc.getMap('config').set('mycustomoption', 'value123');
// In the provider, you need to tell us the yjs type.
// We currently support possible options: map, array, text, xmlfragment, xmlelement
const provider = new TiptapCollabProvider({document: ydoc})
provider.setFieldType('config', 'map');
[PreviouslyAwareness](https://tiptap.dev/docs/collaboration/core-concepts/awareness)
[Next upOverview](https://tiptap.dev/docs/collaboration/documents)
---
# Migrate from TinyMCE | Tiptap Editor Docs
Tiptap is a powerful alternative to TinyMCE. Migrating from TinyMCE to Tiptap is straightforward. This guide will walk you through the migration process step by step.
[](https://tiptap.dev/docs/guides/migrate-from-tinymce#content-migration)
Content migration
-------------------------------------------------------------------------------------------
### [](https://tiptap.dev/docs/guides/migrate-from-tinymce#html-content-compatibility)
HTML content compatibility
TinyMCE typically stores content as HTML, which makes migration to Tiptap seamless. Tiptap can directly use HTML content without any conversion:
// Your existing TinyMCE content
const existingContent = '
Hello world!
'
// Use directly in Tiptap
const editor = new Editor({
content: existingContent,
extensions: [StarterKit],
})
While HTML works perfectly, we recommend converting it to Tiptap's JSON format for better performance and readability. For batch conversion of existing content, use the [HTML utility](https://tiptap.dev/docs/editor/api/utilities/html)
to convert HTML to JSON programmatically.
[](https://tiptap.dev/docs/guides/migrate-from-tinymce#editor-setup)
Editor setup
---------------------------------------------------------------------------------
### [](https://tiptap.dev/docs/guides/migrate-from-tinymce#installation)
Installation
First, install Tiptap and its dependencies:
npm install @tiptap/core @tiptap/starter-kit
Tiptap supports all modern frontend UI frameworks like React and Vue. Follow the framework-specific installation instructions in our [installation guides](https://tiptap.dev/docs/editor/getting-started/install)
.
### [](https://tiptap.dev/docs/guides/migrate-from-tinymce#basic-editor-setup)
Basic editor setup
Replace your TinyMCE initialization with Tiptap:
// TinyMCE (before)
tinymce.init({
selector: '#editor',
plugins: 'lists link image table code',
toolbar: 'undo redo | bold italic | bullist numlist | link image',
})
// Tiptap (after)
import { Editor } from '@tiptap/core'
import StarterKit from '@tiptap/starter-kit'
const editor = new Editor({
element: document.querySelector('#editor'),
extensions: [StarterKit],
content: '
Hello World!
',
})
[](https://tiptap.dev/docs/guides/migrate-from-tinymce#extensions)
Extensions
-----------------------------------------------------------------------------
### [](https://tiptap.dev/docs/guides/migrate-from-tinymce#understanding-tiptaps-extension-system)
Understanding Tiptap's extension system
Tiptap uses a modular extension system that resembles TinyMCE's plugin architecture. Each feature is an extension that can be configured independently.
The [StarterKit](https://tiptap.dev/docs/editor/extensions/functionality/starterkit)
is a bundle of all the basic extensions, and you can add or remove other extensions as needed.
Explore all available extensions in our [extensions guide](https://tiptap.dev/docs/editor/extensions/overview)
, or [create your own](https://tiptap.dev/docs/editor/extensions/custom-extensions)
to support custom functionality and HTML elements.
### [](https://tiptap.dev/docs/guides/migrate-from-tinymce#common-tinymce-plugin-equivalents)
Common TinyMCE plugin equivalents
| TinyMCE Plugin | Tiptap Extension | Notes |
| --- | --- | --- |
| `lists` | `BulletList`, `OrderedList`, `ListItem` | Included in StarterKit |
| `link` | `Link` | Included in StarterKit |
| `image` | `Image` | Available separately |
| `table` | `Table` | Available separately |
| `code` | `Code`, `CodeBlock` | Included in StarterKit |
| `textcolor` | `TextStyle`, `Color` | Available separately |
| `fontsize` | `TextStyle`, `FontSize` | Available separately |
| `align` | `TextAlign` | Available separately |
### [](https://tiptap.dev/docs/guides/migrate-from-tinymce#extension-configuration)
Extension configuration
import { Editor } from '@tiptap/core'
import StarterKit from '@tiptap/starter-kit'
import Image from '@tiptap/extension-image'
import Table from '@tiptap/extension-table'
import TableRow from '@tiptap/extension-table-row'
import TableHeader from '@tiptap/extension-table-header'
import TableCell from '@tiptap/extension-table-cell'
const editor = new Editor({
extensions: [\
StarterKit,\
// Add and configure extensions\
Image.configure({\
inline: true,\
allowBase64: true,\
}),\
Table.configure({\
resizable: true,\
}),\
TableRow,\
TableHeader,\
TableCell,\
],
})
### [](https://tiptap.dev/docs/guides/migrate-from-tinymce#custom-extensions)
Custom extensions
For TinyMCE custom plugins, you can create custom Tiptap extensions. See our [custom extensions guide](https://tiptap.dev/docs/editor/extensions/custom-extensions)
for detailed instructions.
[](https://tiptap.dev/docs/guides/migrate-from-tinymce#ui-implementation)
UI implementation
-------------------------------------------------------------------------------------------
### [](https://tiptap.dev/docs/guides/migrate-from-tinymce#toolbar-implementation)
Toolbar implementation
TinyMCE's toolbar configuration translates to custom UI components in Tiptap:
// TinyMCE toolbar config
toolbar: 'undo redo | bold italic | bullist numlist'
// Tiptap equivalent (React example)
function Toolbar({ editor }) {
if (!editor) return null
return (
)
}
### [](https://tiptap.dev/docs/guides/migrate-from-tinymce#pre-built-ui-components)
Pre-built UI components
For faster development, consider using Tiptap's pre-built UI components:
* Explore our [UI components](https://tiptap.dev/docs/ui-components/getting-started/overview)
for ready-to-use toolbar and menu components
* Check out practical examples in our [default text editor example](https://tiptap.dev/docs/examples/basics/default-text-editor)
### [](https://tiptap.dev/docs/guides/migrate-from-tinymce#menu-systems)
Menu systems
TinyMCE's context menus can be replaced with Tiptap's BubbleMenu and FloatingMenu:
import { BubbleMenu } from '@tiptap/react'
function MyEditor() {
const editor = useEditor({
extensions: [StarterKit],
})
return (
<>
>
)
}
[](https://tiptap.dev/docs/guides/migrate-from-tinymce#migration-checklist)
Migration checklist
-----------------------------------------------------------------------------------------------
Install Tiptap packages
Replace TinyMCE initialization with Tiptap setup
Map TinyMCE plugins to Tiptap extensions
Migrate toolbar configuration to custom UI components
Test content compatibility
Convert existing content to JSON format (recommended)
Implement custom extensions for any missing functionality
Update event handlers and API calls
Test thoroughly across all use cases
[](https://tiptap.dev/docs/guides/migrate-from-tinymce#next-steps)
Next steps
-----------------------------------------------------------------------------
* Explore the [extension overview](https://tiptap.dev/docs/editor/extensions/overview)
to discover all available extensions
* Learn about [custom extensions](https://tiptap.dev/docs/editor/extensions/custom-extensions)
for advanced customization
* Check out our [examples](https://tiptap.dev/docs/examples)
for implementation inspiration
* Review the [performance guide](https://tiptap.dev/docs/guides/performance)
for optimization tips
[PreviouslyExtend with TypeScript](https://tiptap.dev/docs/guides/typescript)
[Next upMigrate from CKEditor 4](https://tiptap.dev/docs/guides/migrate-from-ckeditor4)
---
# Migrate from Slate | Tiptap Editor Docs
Tiptap is a popular Slate alternative and migrating from Slate to Tiptap is straightforward. This guide will help you transition from Slate's JSON structure to Tiptap's extension system.
[](https://tiptap.dev/docs/guides/migrate-from-slate#content-migration)
Content migration
-----------------------------------------------------------------------------------------
### [](https://tiptap.dev/docs/guides/migrate-from-slate#html-content-compatibility)
HTML content compatibility
Slate uses a custom JSON structure that needs to be converted for Tiptap. You'll need to serialize your Slate content to HTML first:
// Convert Slate JSON to HTML using Slate's serialization
import { serialize } from 'slate-html-serializer'
const rules = [\
{\
serialize(obj, children) {\
if (obj.object === 'block') {\
switch (obj.type) {\
case 'paragraph':\
return
{children}
\
case 'heading-one':\
return
{children}
\
case 'heading-two':\
return
{children}
\
// Add more block types as needed\
}\
}\
if (obj.object === 'mark') {\
switch (obj.type) {\
case 'bold':\
return {children}\
case 'italic':\
return {children}\
// Add more mark types as needed\
}\
}\
},\
},\
]
const htmlContent = serialize(slateValue, { rules })
// Use HTML content in Tiptap
const editor = new Editor({
content: htmlContent,
extensions: [StarterKit],
})
For simpler Slate structures, you might be able to map directly to Tiptap's JSON format:
// Example Slate to Tiptap JSON conversion
function slateToTiptap(slateNodes) {
return {
type: 'doc',
content: slateNodes.map((node) => {
if (node.type === 'paragraph') {
return {
type: 'paragraph',
content: node.children.map((child) => ({
type: 'text',
text: child.text,
marks: child.bold ? [{ type: 'bold' }] : [],
})),
}
}
// Add more node type mappings
}),
}
}
const tiptapContent = slateToTiptap(slateValue.document.nodes)
const editor = new Editor({
content: tiptapContent,
extensions: [StarterKit],
})
While HTML works perfectly, we recommend converting it to Tiptap's JSON format for better performance and readability. For batch conversion of existing content, use the [HTML utility](https://tiptap.dev/docs/editor/api/utilities/html)
to convert HTML to JSON programmatically.
[](https://tiptap.dev/docs/guides/migrate-from-slate#editor-setup)
Editor setup
-------------------------------------------------------------------------------
### [](https://tiptap.dev/docs/guides/migrate-from-slate#installation)
Installation
First, install Tiptap and its dependencies:
npm install @tiptap/core @tiptap/starter-kit
Tiptap supports all modern frontend UI frameworks like React and Vue. Follow the framework-specific installation instructions in our [installation guides](https://tiptap.dev/docs/editor/getting-started/install)
.
### [](https://tiptap.dev/docs/guides/migrate-from-slate#basic-editor-setup)
Basic editor setup
Replace your Slate editor with Tiptap:
// Slate (before)
import { createEditor } from 'slate'
import { Slate, Editable, withReact } from 'slate-react'
const [editor] = useState(() => withReact(createEditor()))
const [value, setValue] = useState(initialValue)
return (
)
// Tiptap (after)
import { Editor } from '@tiptap/core'
import StarterKit from '@tiptap/starter-kit'
const editor = new Editor({
element: document.querySelector('#editor'),
extensions: [StarterKit],
content: '
Hello World!
',
})
[](https://tiptap.dev/docs/guides/migrate-from-slate#extensions)
Extensions
---------------------------------------------------------------------------
### [](https://tiptap.dev/docs/guides/migrate-from-slate#understanding-tiptaps-extension-system)
Understanding Tiptap's extension system
Tiptap uses a modular extension system that resembles Slate's plugin system. Each feature is an independent extension with clear APIs and configuration options.
The [StarterKit](https://tiptap.dev/docs/editor/extensions/functionality/starterkit)
is a bundle of all the basic extensions, and you can add or remove other extensions as needed.
Explore all available extensions in our [extensions guide](https://tiptap.dev/docs/editor/extensions/overview)
, or [create your own](https://tiptap.dev/docs/editor/extensions/custom-extensions)
to support custom functionality and HTML elements.
### [](https://tiptap.dev/docs/guides/migrate-from-slate#common-slate-plugin-equivalents)
Common Slate plugin equivalents
| Slate Concept | Tiptap Extension | Notes |
| --- | --- | --- |
| Bold mark | `Bold` | Included in StarterKit |
| Italic mark | `Italic` | Included in StarterKit |
| Underline mark | `Underline` | Included in StarterKit |
| Link mark | `Link` | Included in StarterKit |
| Image block | `Image` | Available separately |
| List blocks | `BulletList`, `OrderedList`, `ListItem` | Included in StarterKit |
| Heading blocks | `Heading` | Included in StarterKit |
| Blockquote block | `Blockquote` | Included in StarterKit |
| Code block | `CodeBlock` | Included in StarterKit |
| Table blocks | `Table` | Available separately |
### [](https://tiptap.dev/docs/guides/migrate-from-slate#extension-configuration)
Extension configuration
import { Editor } from '@tiptap/core'
import StarterKit from '@tiptap/starter-kit'
import Image from '@tiptap/extension-image'
import Table from '@tiptap/extension-table'
import TableRow from '@tiptap/extension-table-row'
import TableHeader from '@tiptap/extension-table-header'
import TableCell from '@tiptap/extension-table-cell'
const editor = new Editor({
extensions: [\
StarterKit,\
Image.configure({\
inline: true,\
allowBase64: true,\
}),\
Table.configure({\
resizable: true,\
}),\
TableRow,\
TableHeader,\
TableCell,\
],
})
### [](https://tiptap.dev/docs/guides/migrate-from-slate#custom-extensions)
Custom extensions
For Slate custom plugins, create custom Tiptap extensions. See our [custom extensions guide](https://tiptap.dev/docs/editor/extensions/custom-extensions)
for detailed instructions.
[](https://tiptap.dev/docs/guides/migrate-from-slate#ui-implementation)
UI implementation
-----------------------------------------------------------------------------------------
### [](https://tiptap.dev/docs/guides/migrate-from-slate#toolbar-implementation)
Toolbar implementation
Slate's custom toolbar components translate to Tiptap UI components:
// Slate toolbar (before)
const Toolbar = () => {
const editor = useSlate()
return (
)
}
// Tiptap equivalent (React example)
function Toolbar({ editor }) {
if (!editor) return null
return (
)
}
### [](https://tiptap.dev/docs/guides/migrate-from-slate#pre-built-ui-components)
Pre-built UI components
For faster development, use Tiptap's pre-built UI components:
* Explore our [UI components](https://tiptap.dev/docs/ui-components/getting-started/overview)
for ready-to-use components
* Check out practical examples in our [default text editor example](https://tiptap.dev/docs/examples/basics/default-text-editor)
### [](https://tiptap.dev/docs/guides/migrate-from-slate#hovering-toolbar)
Hovering toolbar
Replicate Slate's hovering toolbar using Tiptap's BubbleMenu:
import { BubbleMenu } from '@tiptap/react'
function MyEditor() {
const editor = useEditor({
extensions: [StarterKit],
})
return (
<>
>
)
}
### [](https://tiptap.dev/docs/guides/migrate-from-slate#node-views-custom-elements)
Node views (custom elements)
Slate's custom elements can be replaced with Tiptap's Node Views. Learn more about Node Views in our [official guide](https://tiptap.dev/docs/editor/extensions/custom-extensions/node-views)
// Slate custom element (before)
const ImageElement = ({ attributes, children, element }) => {
return (
{children}
)
}
// Tiptap Node View (after)
import { Node } from '@tiptap/core'
import { ReactNodeViewRenderer } from '@tiptap/react'
const ImageComponent = ({ node }) => {
return
}
const CustomImage = Node.create({
name: 'customImage',
addNodeView() {
return ReactNodeViewRenderer(ImageComponent)
},
})
[](https://tiptap.dev/docs/guides/migrate-from-slate#migration-checklist)
Migration checklist
---------------------------------------------------------------------------------------------
Install Tiptap packages
Convert Slate JSON content to HTML or Tiptap JSON
Replace Slate editor initialization with Tiptap setup
Map Slate plugins to Tiptap extensions
Migrate custom elements to [Node Views](https://tiptap.dev/docs/editor/extensions/custom-extensions/node-views)
Migrate toolbar components to Tiptap UI
Test content compatibility
Convert existing content to JSON format (recommended)
Implement custom extensions for any missing functionality
Update event handlers and API calls
Test hovering toolbar functionality
Verify custom element rendering
[](https://tiptap.dev/docs/guides/migrate-from-slate#next-steps)
Next steps
---------------------------------------------------------------------------
* Explore the [extension overview](https://tiptap.dev/docs/editor/extensions/overview)
to discover all available extensions
* Learn about [custom extensions](https://tiptap.dev/docs/editor/extensions/custom-extensions)
for advanced customization
* Check out our [examples](https://tiptap.dev/docs/examples)
for implementation inspiration
* Review the [performance guide](https://tiptap.dev/docs/guides/performance)
for optimization tips
[PreviouslyMigrate from Quill](https://tiptap.dev/docs/guides/migrate-from-quill)
[Next upMigrate from Lexical](https://tiptap.dev/docs/guides/migrate-from-lexical)
---
# React Composable API | Tiptap Editor Docs
Tiptap provides a declarative `` component that simplifies editor setup and automatically provides context to all child components. This composable API is an alternative to the hook-based `useEditor` + `` approach, offering a more React-idiomatic way to work with Tiptap.
[](https://tiptap.dev/docs/guides/react-composable-api#when-to-use-the-composable-api)
When to use the Composable API
---------------------------------------------------------------------------------------------------------------------
The composable API is ideal when you:
* Want a more declarative, component-based approach
* Need to access the editor from multiple child components
* Prefer automatic context management over manual prop passing
* Want built-in loading states and SSR-friendly patterns
For simpler use cases or when you need more direct control, the [hook-based useEditor approach](https://tiptap.dev/docs/editor/getting-started/install/react)
may be more appropriate.
[](https://tiptap.dev/docs/guides/react-composable-api#installation)
Installation
---------------------------------------------------------------------------------
Before using the composable API, make sure you have Tiptap installed in your React project. Follow the [React installation guide](https://tiptap.dev/docs/editor/getting-started/install/react)
to set up the required dependencies.
[](https://tiptap.dev/docs/guides/react-composable-api#using-the-tiptap-component)
Using the Tiptap component
-------------------------------------------------------------------------------------------------------------
The `` component is the root provider that makes the editor instance available to all child components via React context.
### [](https://tiptap.dev/docs/guides/react-composable-api#basic-setup)
Basic setup
Create a new component and import the `Tiptap` component along with `useEditor`:
// src/Editor.tsx
import { Tiptap, useEditor } from '@tiptap/react'
import StarterKit from '@tiptap/starter-kit'
function Editor() {
const editor = useEditor({
extensions: [StarterKit],
content: '
Hello World!
',
})
return (
Loading editor...
)
}
export default Editor
### [](https://tiptap.dev/docs/guides/react-composable-api#available-subcomponents)
Available subcomponents
The `` component provides several subcomponents that handle common editor UI patterns:
| Component | Description |
| --- | --- |
| `Tiptap.Content` | Renders the editor content area. Replaces ``. |
| `Tiptap.Loading` | Renders its children only while the editor is initializing. Useful for loading states. |
| `Tiptap.BubbleMenu` | A context-aware bubble menu that appears on text selection. |
| `Tiptap.FloatingMenu` | A context-aware floating menu that appears on empty lines. |
[](https://tiptap.dev/docs/guides/react-composable-api#accessing-the-editor-in-child-components)
Accessing the editor in child components
-----------------------------------------------------------------------------------------------------------------------------------------
One of the main benefits of the composable API is that child components can access the editor without prop drilling.
### [](https://tiptap.dev/docs/guides/react-composable-api#using-the-usetiptap-hook)
Using the useTiptap hook
The `useTiptap` hook returns the editor instance and an `isReady` flag that indicates whether the editor has finished initializing.
import { useTiptap } from '@tiptap/react'
function MenuBar() {
const { editor, isReady } = useTiptap()
if (!isReady || !editor) {
return null
}
return (
)
}
Then include the menu bar anywhere inside your `` component:
### [](https://tiptap.dev/docs/guides/react-composable-api#using-usetiptapstate-for-reactive-state)
Using useTiptapState for reactive state
For performance-sensitive components, use `useTiptapState` to subscribe to specific parts of the editor state. This prevents unnecessary re-renders when unrelated state changes.
import { useTiptap, useTiptapState } from '@tiptap/react'
function WordCount() {
const { isReady } = useTiptap()
const wordCount = useTiptapState((state) => {
const text = state.editor.state.doc.textContent
return text.split(/\s+/).filter(Boolean).length
})
if (!isReady) {
return null
}
return {wordCount} words
}
The selector function receives an `EditorStateSnapshot` and should return only the data your component needs. The component will only re-render when the selected value changes.
### Important
Only use `useTiptapState` when the editor is ready. Check `isReady` from `useTiptap()` before rendering components that use this hook.
[](https://tiptap.dev/docs/guides/react-composable-api#server-side-rendering-ssr)
Server-side rendering (SSR)
-------------------------------------------------------------------------------------------------------------
The composable API works seamlessly with server-side rendering. Use the `immediatelyRender` option to prevent the editor from rendering on the server, and leverage `Tiptap.Loading` to display a placeholder:
'use client'
import { Tiptap, useEditor } from '@tiptap/react'
import StarterKit from '@tiptap/starter-kit'
export function MyEditor() {
const editor = useEditor({
extensions: [StarterKit],
content: '
Hello World!
',
immediatelyRender: false,
})
return (
Loading editor...
)
}
The `Tiptap.Loading` component is particularly useful with SSR as it displays a placeholder until the editor initializes on the client.
[](https://tiptap.dev/docs/guides/react-composable-api#performance-considerations)
Performance considerations
-------------------------------------------------------------------------------------------------------------
The composable API is designed with performance in mind:
* **Automatic context optimization**: The editor context is memoized to prevent unnecessary re-renders
* **Selective subscriptions**: Use `useTiptapState` to subscribe only to the state you need
* **Built-in loading states**: Prevent rendering child components until the editor is ready
For more performance tips, see the [React Performance Guide](https://tiptap.dev/docs/guides/performance)
.
[](https://tiptap.dev/docs/guides/react-composable-api#backwards-compatibility)
Backwards compatibility
-------------------------------------------------------------------------------------------------------
The `` component automatically provides the `EditorContext`, which means you can use the `useCurrentEditor` hook inside it for backwards compatibility with existing code:
import { useCurrentEditor } from '@tiptap/react'
function EditorJSONPreview() {
const { editor } = useCurrentEditor()
if (!editor) {
return null
}
return
{JSON.stringify(editor.getJSON(), null, 2)}
}
However, for new code, we recommend using `useTiptap()` which provides additional context like the `isReady` flag.
[](https://tiptap.dev/docs/guides/react-composable-api#api-reference)
API Reference
-----------------------------------------------------------------------------------
### [](https://tiptap.dev/docs/guides/react-composable-api#tiptap-component)
Tiptap component
The root provider component that makes the editor instance available via React context.
**Props:**
| Prop | Type | Description |
| --- | --- | --- |
| `instance` | `Editor \| null` | The editor instance from `useEditor()` |
| `children` | `ReactNode` | Child components |
**Example:**
### [](https://tiptap.dev/docs/guides/react-composable-api#usetiptap-hook)
useTiptap hook
Returns the Tiptap context value.
**Returns:**
| Property | Type | Description |
| --- | --- | --- |
| `editor` | `Editor \| null` | The editor instance |
| `isReady` | `boolean` | `true` when the editor has finished initializing |
**Example:**
const { editor, isReady } = useTiptap()
if (!isReady || !editor) {
return null
}
### [](https://tiptap.dev/docs/guides/react-composable-api#usetiptapstate-hook)
useTiptapState hook
Subscribes to a slice of the editor state using a selector function.
**Signature:**
const value = useTiptapState(selector, equalityFn?)
**Parameters:**
| Parameter | Type | Description |
| --- | --- | --- |
| `selector` | `(state: EditorStateSnapshot) => T` | Function to select state |
| `equalityFn` | `(a: T, b: T) => boolean` | Optional equality function to control re-renders. Defaults to `deepEqual` from `fast-equals` (deep value comparison). |
**Example:**
const isBold = useTiptapState((state) => state.editor.isActive('bold'))
[](https://tiptap.dev/docs/guides/react-composable-api#comparison-composable-api-vs-hook-based-approach)
Comparison: Composable API vs Hook-based Approach
----------------------------------------------------------------------------------------------------------------------------------------------------------
| Feature | Composable API | Hook-based Approach |
| --- | --- | --- |
| Setup complexity | Low - declarative components | Medium - manual prop passing |
| Context management | Automatic | Manual via EditorContext.Provider |
| Child component access | Easy via `useTiptap()` | Requires prop drilling or context |
| Loading states | Built-in `Tiptap.Loading` | Manual implementation |
| SSR support | Built-in with `Tiptap.Loading` | Requires manual null checks |
| Performance | Optimized with `useTiptapState` | Optimized with `useEditorState` |
| Best for | Complex UIs with many child components | Simple UIs or direct control needed |
[](https://tiptap.dev/docs/guides/react-composable-api#next-steps)
Next steps
-----------------------------------------------------------------------------
* [Optimize your React integration](https://tiptap.dev/docs/guides/performance)
* [Configure your editor](https://tiptap.dev/docs/editor/getting-started/configure)
* [Add styles to your editor](https://tiptap.dev/docs/editor/getting-started/style-editor)
* [Learn more about Tiptap concepts](https://tiptap.dev/docs/editor/core-concepts/introduction)
[PreviouslyPerformance](https://tiptap.dev/docs/guides/performance)
[Next upInvalid schema handling](https://tiptap.dev/docs/guides/invalid-schema)
---
# What's new | Tiptap Resources
Tiptap V3 introduces significant updates to the core editor. This guide highlights important changes clearly, making your upgrade straightforward. Use it to quickly find everything essential to update your integration.
[](https://tiptap.dev/docs/resources/whats-new#upgrade-guide)
Upgrade guide
---------------------------------------------------------------------------
If you’re upgrading from Tiptap 2.x to 3.x, refer to our [upgrade guide](https://tiptap.dev/docs/guides/upgrade-tiptap-v2)
. It covers breaking changes, important updates, and new features clearly—ensuring your migration process is efficient.
### [](https://tiptap.dev/docs/resources/whats-new#breaking-changes)
Breaking Changes
| **Change** | **Description** |
| --- | --- |
| **Removed UMD builds** | UMD builds have been removed. We recommend using ESM builds instead. UMD builds are not compatible with the new `@tiptap/extension-*` packages. |
| **New `shouldRerenderOnTransaction` option with `false` default** | The new `shouldRerenderOnTransaction` option is disabled by default to minimize rerendering with React components. This can cause your editor component to not rerender as expected. [Read more](https://tiptap.dev/docs/examples/advanced/react-performance) . |
| **Migration from `tippy.js` to `floating-ui`** | We replaced the outdated `tippy.js` library with the new `floating-ui` library. This change improves the performance and reliability of our menus and tooltips. |
| **Changes to our Text Style API** | The `@tiptap/extension-text-style` package has seen API updates providing a new `TextStyleKit` extension that encapsulates all possible text styling functionalities in one extension. Default options were also updated. |
| **Commands were changed** | The behavior or names of commands were changed. `clearContent` and `setContent` now emit updates correctly while `setContent` also received a change to its parameters. `insertContent` was changed to prevent unwanted text splitting. |
| **NodeView `getPos` now can return `undefined`** | `nodeView.getPos()` can now return `undefined` making a check for `undefined` necessary to handle this case properly. |
| **`editor.getCharacterCount()` was removed** | The `editor.getCharacterCount()` method was removed. It was marked as deprecated in the previous version and is now removed. You can use `editor.storage.characterCount.characters()` instead while using the `CharacterCount` extension. |
| **`considerAnyAsEmpty` option removed from placeholder extension** | The `considerAnyAsEmpty` option was removed from the placeholder extension as it was deprecated already and wasn't respected in the previous version. |
| **Stricter Typing** | 3.0.0 brings stricter typing to the editor. This change improves the overall type safety of the editor and makes it easier to work with but might require some adjustments in your code. We recommend using TypeScript to benefit from the improved type safety. |
| **Moved Utility Extensions** | We moved all our utility extensions like `History`, `Placeholder`, `CharacterCount`, `DropCursor`, `GapCursor`, `TrailingNode`, `Focus`, and `Selection` to the new `@tiptap/extensions` package. |
| **Renamed Collaboration Cursor** | The `CollaborationCursor` extension was renamed to `CollaborationCaret` as we felt cursors was too general and could clash with potential other cursor types in the future. |
| **Renamed History Extension** | The `History` extension was renamed to `UndoRedo` to better reflect its purpose and avoid confusion with the collaborative History feature. This also includes the `history` option on the `StarterKit`. |
[](https://tiptap.dev/docs/resources/whats-new#new-features)
New features
-------------------------------------------------------------------------
* **New `@tiptap/extensions` extension** - The new `@tiptap/extensions` package includes and combines multiple utility extensions. [Read more](https://tiptap.dev/docs/guides/upgrade-tiptap-v2#extensions-package)
.
* **New [MarkView](https://prosemirror.net/docs/ref/#view.MarkView)
support** - MarkViews are now supported by Tiptap. This can be useful to render custom HTML for marks. [Read more](https://tiptap.dev/docs/editor/extensions/custom-extensions/mark-views)
.
* **Improved Server-Side Rendering** - The editor can now run on SSR environments without rendering the editor content. [Read more](https://tiptap.dev/docs/guides/upgrade-tiptap-v2#server-side-rendering)
.
* **Deletes can now be tracked as an event** - Editor deletions can now be tracked as an event. [Read more](https://tiptap.dev/docs/editor/api/events#list-of-available-events)
.
* **Nodes and marks have new attribute validation support** - Validating attributes on nodes or marks is now supported.
* **StarterKit updates** - The StarterKit was made more powerful now including more extensions by default. [Read more](https://tiptap.dev/docs/guides/upgrade-tiptap-v2#starterkit-updates)
.
* **TableKit** - The new `TableKit` extension allows you to register all important table extensions in one package. [Read more](https://tiptap.dev/docs/editor/extensions/functionality/table-kit)
.
* **ListKit** - The new `ListKit` extension allows you to register all important list extensions in one package. [Read more](https://tiptap.dev/docs/editor/extensions/functionality/list-kit)
.
* **TextStyleKit** - The new `TextStyleKit` extension registers all important text-style extensions in one package. [Read more](https://tiptap.dev/docs/editor/extensions/functionality/text-style-kit)
.
* **New `rewriteUnknownContent` helper** - This helper function can be imported from the `@tiptap/core`.
* **New `unmount` as an alternative to destroying the editor** - The editor instance now supports an `unmount` method which allows for mounting and unmounting the editor to the DOM. This encourages re-use of editor instances by preserving all the same options between instances. This is different from the `destroy` method, which will unmount, emit the `destroy` event, and remove all event listeners.
* **New `toggleTextStyle` command for text styles** - The `toggleTextStyle` command allows you to toggle text styles on and off. This is useful for toggling text styles like bold or italic.
* **Static Renderer** - The Static Renderer helps render JSON content as HTML, Markdown, or React components without an editor instance. [Read more](https://tiptap.dev/docs/editor/api/utilities/static-renderer)
.
* **JSX Renderer** - With 3.0.0 you will have the ability to use **framework agnostic** JSX for the `renderHTML` function of your extensions. [Read more](https://tiptap.dev/docs/editor/api/utilities/jsx)
.
### [](https://tiptap.dev/docs/resources/whats-new#improvements)
Improvements
* **Improved transaction handling** - Transactions are now handled more efficiently and reliably. This change improves the overall performance of the editor.
* **`focusEvent` plugin key is exported** - You can now import the `focusEvent` plugin key if needed from the core package.
* **Improvements on mobile devices** - The 3.0.0 release brings improvements for mobile devices primarily focusing on touch events.
* **Minor IME bugfixes** - The 3.0.0 release includes a few minor bugfixes for IME input handling.
* **TextStyle can now consume style attributes** - The `TextStyle` extension can now consume styles from the style attribute and will skip parsing of other styles if there was an override defined in the style attribute. See [this Github Comment](https://github.com/ueberdosis/tiptap/discussions/5912#discussioncomment-11716337)
.
* **TableView on `Table` extension is now importable** - The `TableView` class on the `Table` extension is now importable to allow for custom table views that still are resizable.
### [](https://tiptap.dev/docs/resources/whats-new#additional-changes)
Additional changes
* **HTML parsing with `happy-dom-without-node`** - We now use `happy-dom-without-node` to parse HTML as a lightweight alternative to `zeed-dom`.
* **Various smaller bugfixes** - The 3.0.0 release includes various smaller bugfixes and improvements to the overall performance of the editor.
[](https://tiptap.dev/docs/resources/whats-new#whats-next-in-tiptap-3x)
What's next in Tiptap 3.X?
--------------------------------------------------------------------------------------------------
We're already exploring future features for releases beyond Tiptap 3.0.
* **Content migrations**: Migrations allow you to rewrite your document JSON to align with your current schema, facilitating document updates during schema changes with fully customizable solutions tailored to your needs.
* **Markdown support**: Enhance your editing capabilities by enabling the editor to both accept and output content in markdown, meeting the demands of modern applications and leveraging the strengths of LLMs in markdown generation.
* **Decorations API**: The new Decorations API allows you to influence document presentation without altering its content, providing an intuitive way to add visual enhancements beyond complex ProseMirror internals.
[PreviouslyOverview](https://tiptap.dev/docs)
[Next upEditor](https://tiptap.dev/docs/editor/getting-started/overview)
---
# Upgrade v1 to v2 | Tiptap Collaboration Docs
First of all, Tiptap v1 isn’t supported anymore and won’t receive any further updates.
If you’re still using Tiptap v1, you can find the documentation [here](https://v1.tiptap.dev/)
, but we strongly recommend that you upgrade to version 2.
Yes, it’s tedious work to upgrade your favorite text editor to a new API, but we made sure you’ve got enough reasons to upgrade to the newest version.
* Autocompletion in your IDE (thanks to TypeScript)
* Amazing documentation with 100+ pages and 100+ interactive examples
* Active development, new features in the making, new releases every week
* Tons of new extensions
* Well-tested code base
The new API will look pretty familiar to you, but there are a ton of changes though. To make the upgrade a little bit easier, here is everything you need to know:
[](https://tiptap.dev/docs/guides/upgrade-tiptap-v1#uninstall-tiptap-v1)
Uninstall Tiptap v1
--------------------------------------------------------------------------------------------
The whole package structure has changed, we even moved to another npm namespace, so you’ll need to remove the old version entirely before upgrading to Tiptap 2.
Otherwise you’ll run into an exception, for example “looks like multiple versions of prosemirror-model were loaded”.
npm uninstall tiptap tiptap-commands tiptap-extensions tiptap-utils
[](https://tiptap.dev/docs/guides/upgrade-tiptap-v1#install-tiptap-v2)
Install Tiptap v2
----------------------------------------------------------------------------------------
Once you have uninstalled the old version of Tiptap, install the new Vue 2 package, the ProseMirror library and the starter kit:
npm install @tiptap/vue-2 @tiptap/pm @tiptap/starter-kit
[](https://tiptap.dev/docs/guides/upgrade-tiptap-v1#keep-tiptap-v2-up-to-date)
Keep Tiptap v2 up to date
--------------------------------------------------------------------------------------------------------
We are constantly releasing updates to Tiptap.
Unfortunately, for npm there is no integrated tool to easily update your dependencies, but you can use the `npm-check` package:
npm install -g npm-check
npm-check -u
[](https://tiptap.dev/docs/guides/upgrade-tiptap-v1#explicitly-register-the-document-text-and-paragraph-extensions)
Explicitly register the Document, Text and Paragraph extensions
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Tiptap 1 tried to hide a few required extensions from you with the default setting `useBuiltInExtensions: true`. That setting has been removed and you’re required to import all extensions. Be sure to explicitly import at least the [`Document`](https://tiptap.dev/docs/editor/extensions/nodes/document)
, [`Paragraph`](https://tiptap.dev/docs/editor/extensions/nodes/paragraph)
and [`Text`](https://tiptap.dev/docs/editor/extensions/nodes/text)
extensions.
import Document from '@tiptap/extension-document'
import Paragraph from '@tiptap/extension-paragraph'
import Text from '@tiptap/extension-text'
new Editor({
extensions: [\
Document,\
Paragraph,\
Text,\
// all your other extensions\
],
})
And we removed some settings: `dropCursor`, `enableDropCursor`, and `enableGapCursor`. Those are separate extensions now: [`Dropcursor`](https://tiptap.dev/docs/editor/extensions/functionality/dropcursor)
and [`Gapcursor`](https://tiptap.dev/docs/editor/extensions/functionality/gapcursor)
. You probably want to load them, but if you don’t, just ignore this.
[](https://tiptap.dev/docs/guides/upgrade-tiptap-v1#new-names-for-most-extensions)
New names for most extensions
----------------------------------------------------------------------------------------------------------------
We switched to lowerCamelCase, so there’s a lot type names that changed. If you stored your content as JSON you need to loop through it and rename them. Sorry for that one.
| Old type | New type |
| --- | --- |
| ~`bullet_list`~ | `bulletList` |
| ~`code_block`~ | `codeBlock` |
| ~`hard_break`~ | `hardBreak` |
| ~`horizontal_rule`~ | `horizontalRule` |
| ~`list_item`~ | `listItem` |
| ~`ordered_list`~ | `orderedList` |
| ~`table_cell`~ | `tableCell` |
| ~`table_header`~ | `tableHeader` |
| ~`table_row`~ | `tableRow` |
| ~`todo_list`~ | `taskList` (new name!) |
| ~`todo_item`~ | `taskItem` (new name!) |
[](https://tiptap.dev/docs/guides/upgrade-tiptap-v1#removed-methods)
Removed methods
------------------------------------------------------------------------------------
We removed the `.state()` method. No worries though, it’s still available through `editor.state`.
[](https://tiptap.dev/docs/guides/upgrade-tiptap-v1#new-extension-api)
New extension API
----------------------------------------------------------------------------------------
In case you’ve built some custom extensions for your project, you’re required to rewrite them to fit the new API. No worries, you can keep a lot of your work though. The `schema`, `commands`, `keys`, `inputRules` and `pasteRules` all work like they did before. It’s just different how you register them.
import { Node } from '@tiptap/core'
const CustomExtension = Node.create({
name: 'custom_extension',
addOptions() {
…
},
addAttributes() {
…
},
parseHTML() {
…
},
renderHTML({ node, HTMLAttributes }) {
…
},
addCommands() {
…
},
addKeyboardShortcuts() {
…
},
addInputRules() {
…
},
// and more …
})
Read more about [all the nifty details building custom extensions](https://tiptap.dev/docs/editor/extensions/custom-extensions)
in our guide.
[](https://tiptap.dev/docs/guides/upgrade-tiptap-v1#renamed-settings-and-methods)
Renamed settings and methods
--------------------------------------------------------------------------------------------------------------
[We renamed a lot of settings and methods](https://tiptap.dev/docs/editor/api/editor)
. Hopefully you can migrate to the new API with search & replace. Here is a list of what changed:
| Old name | New name |
| --- | --- |
| ~`autoFocus`~ | `autofocus` |
[](https://tiptap.dev/docs/guides/upgrade-tiptap-v1#renamed-commands)
Renamed commands
--------------------------------------------------------------------------------------
All new extensions come with specific commands to set, unset and toggle styles. So instead of `.bold()`, it’s now `.toggleBold()`. Also, we switched to lowerCamelCase, below are a few examples. Oh, and we renamed `todo_list`, to `taskList`, sorry for that one.
| Old command | New command |
| --- | --- |
| `.redo()` | `.redo()` (nothing changed) |
| `.undo()` | `.undo()` (nothing changed) |
| ~`.todo_list()`~ | `.toggleTaskList()` (new name!) |
| ~`.blockquote()`~ | `.toggleBlockquote()` |
| ~`.bold()`~ | `.toggleBold()` |
| ~`.bullet_list()`~ | `.toggleBulletList()` |
| ~`.code()`~ | `.toggleCode()` |
| ~`.code_block()`~ | `.toggleCodeBlock()` |
| ~`.hard_break()`~ | `.setHardBreak()` |
| ~`.heading()`~ | `.toggleHeading()` |
| ~`.horizontal_rule()`~ | `.setHorizontalRule()` |
| ~`.italic()`~ | `.toggleItalic()` |
| ~`.link()`~ | `.toggleLink()` |
| ~`.ordered_list()`~ | `.toggleOrderedList()` |
| ~`.paragraph()`~ | `.setParagraph()` |
| ~`.strike()`~ | `.toggleStrike()` |
| ~`.underline()`~ | `.toggleUnderline()` |
| … | … |
[](https://tiptap.dev/docs/guides/upgrade-tiptap-v1#menubar-bubblemenu-and-floatingmenu)
MenuBar, BubbleMenu and FloatingMenu
-----------------------------------------------------------------------------------------------------------------------------
Read the dedicated [guide on creating menus](https://tiptap.dev/docs/editor/getting-started/style-editor/custom-menus)
to migrate your menus.
[](https://tiptap.dev/docs/guides/upgrade-tiptap-v1#commands-can-be-chained-now)
Commands can be chained now
------------------------------------------------------------------------------------------------------------
Most commands can be combined to one call now. That’s shorter than separate function calls in most cases. Here is an example to make the selected text bold:
editor.chain().focus().toggleBold().run()
The `.chain()` is required to start a new chain and the `.run()` is needed to actually execute all the commands in between. Read more about [the new Tiptap commands](https://tiptap.dev/docs/editor/api/commands)
in our API documentation.
[](https://tiptap.dev/docs/guides/upgrade-tiptap-v1#focus-isnt-called-on-every-command-anymore)
.focus() isn’t called on every command anymore
----------------------------------------------------------------------------------------------------------------------------------------------
We tried to hide the `.focus()` command from you with Tiptap 1 and executed that on every command. That led to issues in specific use cases, where you want to run a command, but don’t want to focus the editor.
With Tiptap v2 you have to explicitly call the `focus()` and you probably want to do that in a lot of places. Here is an example:
editor.chain().focus().toggleBold().run()
[](https://tiptap.dev/docs/guides/upgrade-tiptap-v1#event-callbacks-have-fewer-parameters)
Event callbacks have fewer parameters
--------------------------------------------------------------------------------------------------------------------------------
The new event callbacks have fewer parameters. The same things should be available through `this.` now. [Read more about events here.](https://tiptap.dev/docs/editor/api/events)
[](https://tiptap.dev/docs/guides/upgrade-tiptap-v1#collaborative-editing)
Collaborative editing
------------------------------------------------------------------------------------------------
The reference implementation for collaborative editing uses Y.js now. That’s a whole different thing. You still can use the Tiptap 1 extension, but it’s up to you to adapt it to the new extension API. If you’ve done this, don’t forget to share it with us so we can link to it from here!
Read more about [the new collaborative editing experience](https://tiptap.dev/docs/collaboration/getting-started/install)
in our guide.
[](https://tiptap.dev/docs/guides/upgrade-tiptap-v1#marks-dont-support-node-view-anymore)
Marks don’t support node view anymore
-------------------------------------------------------------------------------------------------------------------------------
For marks, node views are [not well supported in ProseMirror](https://discuss.prosemirror.net/t/there-is-a-bug-in-marks-nodeview/2722/2)
. There is also [a related issue](https://github.com/ueberdosis/tiptap/issues/613)
for Tiptap 1. That’s why we removed it in Tiptap 2.
[PreviouslyOffline Collaboration](https://tiptap.dev/docs/guides/offline-support)
[Next upUpgrade Tiptap V2](https://tiptap.dev/docs/guides/upgrade-tiptap-v2)
---
# Upgrade v2 to v3 | Tiptap Collaboration Docs
Tiptap v3 is a major update with breaking changes. This guide will help you upgrade your Tiptap v2 project to version 3.
[](https://tiptap.dev/docs/guides/upgrade-tiptap-v2#breaking-changes)
Breaking Changes
--------------------------------------------------------------------------------------
### [](https://tiptap.dev/docs/guides/upgrade-tiptap-v2#package-changes)
Package Changes
* UMD builds have been removed (now using tsup)
Some packages may have been removed or renamed, so you may need to update your imports and dependencies. Here are some common changes:
Table packages
- import Table from '@tiptap/extension-table'
- import TableRow from '@tiptap/extension-table-row'
- import TableCell from '@tiptap/extension-table-cell'
- import TableHeader from '@tiptap/extension-table-header'
+ import { Table, TableRow, TableCell, TableHeader } from '@tiptap/extension-table'
List packages
- import BulletList from '@tiptap/extension-bullet-list'
- import OrderedList from '@tiptap/extension-ordered-list'
- import ListItem from '@tiptap/extension-list-item'
- import ListKeymap from '@tiptap/extension-list-keymap'
- import TaskList from '@tiptap/extension-task-list'
- import TaskItem from '@tiptap/extension-task-item'
+ import { BulletList, OrderedList, ListItem, ListKeymap, TaskList, TaskItem } from '@tiptap/extension-list'
Extensions
- import Focus from '@tiptap/extension-focus'
- import Placeholder from '@tiptap/extension-placeholder'
- import History from '@tiptap/extension-history'
- import Dropcursor from '@tiptap/extension-dropcursor'
- import Gapcursor from '@tiptap/extension-gapcursor'
- import CharacterCount from '@tiptap/extension-character-count'
+ import { Focus, Placeholder, UndoRedo, Dropcursor, Gapcursor, CharacterCount } from '@tiptap/extensions'
CollaborationCursor
- import CollaborationCursor from '@tiptap/extension-collaboration-cursor'
+ import CollaborationCaret from '@tiptap/extension-collaboration-caret'
Collaboration History
- import CollaborationHistory from '@tiptap-pro/extension-collaboration-history'
+ import Snapshot from '@tiptap-pro/extension-snapshot'
React Menus
- import { BubbleMenu, FloatingMenu } from '@tiptap/react'
+ import { BubbleMenu, FloatingMenu } from '@tiptap/react/menus'
Vue 3 Menus
- import { BubbleMenu, FloatingMenu } from '@tiptap/vue-3'
+ import { BubbleMenu, FloatingMenu } from '@tiptap/vue-3/menus'
Vue 2 Menus
- import { BubbleMenu, FloatingMenu } from '@tiptap/vue-2'
+ import { BubbleMenu, FloatingMenu } from '@tiptap/vue-2/menus'
### [](https://tiptap.dev/docs/guides/upgrade-tiptap-v2#menu-systems)
Menu Systems
Tippy.js has been replaced with Floating UI for all floating elements. This affects:
* @tiptap/extension-bubble-menu
* @tiptap/extension-drag-handle
* @tiptap/extension-drag-handle-react
* @tiptap/extension-drag-handle-vue-2
* @tiptap/extension-drag-handle-vue-3
* @tiptap/extension-floating-menu
* @tiptap/extension-mention
* @tiptap/suggestion
* @tiptap-pro/extension-emoji
Example migration:
// Before
import { BubbleMenu } from '@tiptap/react'
const menu = {/* menu content */}
// After
import { BubbleMenu } from '@tiptap/react/menus'
import { offset } from '@floating-ui/dom'
const menu = (
{/* menu content */}
)
* New required dependency: `@floating-ui/dom`. Install it with:
npm install @floating-ui/dom@^1.6.0
Make sure to uninstall `tippy.js` if you were using it, as it is no longer needed.
npm uninstall tippy.js
### [](https://tiptap.dev/docs/guides/upgrade-tiptap-v2#api-changes-text-style-updates)
API Changes Text Style Updates
* `mergeNestedSpanStyles` now defaults to `true`
* New text style extensions added:
* `font-size`
* `background-color`
* `line-height`
* `TextStyleKit` API available for configuring multiple extensions
You can now use the new `TextStyleKit` for easier configuration:
import { TextStyleKit } from '@tiptap/extension-text-style'
new Editor({
extensions: [\
TextStyleKit.configure({\
backgroundColor: {\
types: ['textStyle'],\
},\
color: {\
types: ['textStyle'],\
},\
fontFamily: {\
types: ['textStyle'],\
},\
fontSize: {\
types: ['textStyle'],\
},\
lineHeight: {\
types: ['textStyle'],\
},\
}),\
],
})
### [](https://tiptap.dev/docs/guides/upgrade-tiptap-v2#introducing-shouldrerenderontransaction)
Introducing `shouldRerenderOnTransaction`
The `@tiptap/react` Editor configuration now includes `shouldRerenderOnTransaction`, controlling component rerendering.
Previously (pre-3.0.0), the Editor rerendered on every transaction-induced state update, facilitating continuous editor object tracking but potentially impacting rendering performance.
`shouldRerenderOnTransaction` is disabled by default. Enable it by setting `shouldRerenderOnTransaction: true` in the editor configuration.
Post-upgrade, UI elements relying on the Editor's state might become unresponsive. Solutions include disabling the option (potential performance implications) or implementing manual state tracking, as demonstrated:
function MyEditor() {
const [selection, setSelection] = useState({ from: 0, to: 0 });
const editor = useEditor({
// Editor configuration
onTransaction({ transaction }) {
setSelection({
from: transaction.selection.from,
to: transaction.selection.to,
});
},
});
}
You can also use `useEditorState` to extract either the whole state or select specific values to extract.
const { currentSelection } = useEditorState({
editor,
selector: (snapshot) => {
return { currentSelection: snapshot.editor.state.selection }
},
})
This allows explicit state extraction and management for dependent UI logic.
### [](https://tiptap.dev/docs/guides/upgrade-tiptap-v2#command-changes)
Command Changes
* `clearContent` and `setContent` now emit updates by default
* `setContent` signature changed to (content, options)
* `insertContent` behavior modified to prevent splitting text nodes at the beginning
### [](https://tiptap.dev/docs/guides/upgrade-tiptap-v2#nodeview-changes)
NodeView Changes
The `getPos` function in NodeViewRendererProps can now return `undefined`. Update your code to handle this case:
// Before
const pos = nodeViewProps.getPos()
// After
const pos = nodeViewProps.getPos()
if (pos !== undefined) {
// use pos
}
### [](https://tiptap.dev/docs/guides/upgrade-tiptap-v2#removed-features)
Removed Features
* `editor.getCharacterCount()` method has been removed
* `considerAnyAsEmpty` option removed from placeholder extension
[](https://tiptap.dev/docs/guides/upgrade-tiptap-v2#new-features)
New Features
------------------------------------------------------------------------------
### [](https://tiptap.dev/docs/guides/upgrade-tiptap-v2#extensions-package)
Extensions Package
The new `@tiptap/extensions` package combines multiple utility extensions:
* [CharacterCount](https://tiptap.dev/docs/editor/extensions/functionality/character-count)
* [Dropcursor](https://tiptap.dev/docs/editor/extensions/functionality/dropcursor)
* [Gapcursor](https://tiptap.dev/docs/editor/extensions/functionality/gapcursor)
* [History](https://tiptap.dev/docs/editor/extensions/functionality/undo-redo)
(now named `UndoRedo`)
* [Placeholder](https://tiptap.dev/docs/editor/extensions/functionality/placeholder)
* [TrailingNode](https://tiptap.dev/docs/editor/extensions/functionality/trailing-node)
* [Focus](https://tiptap.dev/docs/editor/extensions/functionality/focus)
* [Selection](https://tiptap.dev/docs/editor/extensions/functionality/selection)
This will automatically update the imports in your project.
Example migration:
- import CharacterCount from '@tiptap/extension-character-count'
+ import { CharacterCount } from '@tiptap/extensions'
### [](https://tiptap.dev/docs/guides/upgrade-tiptap-v2#server-side-rendering)
Server-Side Rendering
Improved SSR support with the ability to run the editor on the server-side without rendering:
const editor = new Editor({
element: null, // opt-in to SSR
content: {
type: 'doc',
content: [\
/* ... */\
],
},
extensions: [\
/* ... */\
],
})
### [](https://tiptap.dev/docs/guides/upgrade-tiptap-v2#mark-views)
Mark Views
Adds support for [mark views](https://prosemirror.net/docs/ref/#view.MarkView)
, which allow you to render custom views for marks within the editor. This is useful for rendering custom UI for marks, like a color picker for a text color mark or a link editor for a link mark.
Including support for React and Vue 3 frameworks
// Plain JS
import { Mark } from '@tiptap/core'
Mark.create({
// Other options...
addMarkView() {
return ({ mark, HTMLAttributes }) => {
const dom = document.createElement('b')
const contentDOM = document.createElement('span')
dom.appendChild(contentDOM)
return {
dom,
contentDOM,
}
}
},
})
// React
import { Mark } from '@tiptap/core'
import { ReactMarkViewRenderer } from '@tiptap/react'
Mark.create({
// ... other options
addMarkView() {
return ReactMarkViewRenderer(YourComponent)
},
})
// Vue 3
import { Mark } from '@tiptap/core'
import { VueMarkViewRenderer } from '@tiptap/vue-3'
Mark.create({
addMarkView() {
return VueMarkViewRenderer(Component)
},
})
### [](https://tiptap.dev/docs/guides/upgrade-tiptap-v2#other-improvements)
Other Improvements
* New delete event tracking
* HTML parsing now uses happy-dom-without-node
* New node/mark attribute validation support
* Performance improvements for transaction handling
[](https://tiptap.dev/docs/guides/upgrade-tiptap-v2#starterkit-updates)
StarterKit Updates
------------------------------------------------------------------------------------------
StarterKit now includes these extensions by default:
* [Link](https://tiptap.dev/docs/editor/extensions/marks/link)
* [ListKeymap](https://tiptap.dev/docs/editor/extensions/functionality/listkeymap)
* [Underline](https://tiptap.dev/docs/editor/extensions/marks/underline)
If you've installed these separately, you can remove them:
- import Link from '@tiptap/extension-link'
- import ListKeymap from '@tiptap/extension-list-keymap'
- import Underline from '@tiptap/extension-underline'
Also make sure to update disabled extension names in your StarterKit configuration if you were using them:
const editor = new Editor({
extensions: [\
StarterKit.configure({\
- history: false, // disable history\
+ undoRedo: false // disable undo/redo (previously history)\
})\
]
})
[](https://tiptap.dev/docs/guides/upgrade-tiptap-v2#migration-steps)
Migration Steps
------------------------------------------------------------------------------------
1. Update Dependencies
* Remove UMD-dependent code
* Install `@floating-ui/dom` (for bubble & floating menus)
* Update Tiptap packages to v3
2. Update Menu Implementations
* Replace `tippyOptions` with Floating UI options
* Update menu imports to use `/menus` suffix
3. Review NodeView Usage
* Add checks for `undefined` `getPos`
4. Update Extensions
* Migrate to `@tiptap/extensions` where applicable
* Remove redundant `StarterKit` extensions
* Update text style configurations
5. Review Command Usage
* Update `setContent` calls to use new signature
* Check `clearContent` behavior with default updates
[PreviouslyUpgrade Tiptap V1](https://tiptap.dev/docs/guides/upgrade-tiptap-v1)
[Next upCollaboration API](https://tiptap.dev/docs/guides/collaboration-api)
---
# Auth Guide | Tiptap Collaboration Docs
After setting up a collaborative editor in the installation guide, it's crucial to address authentication for longer-term use. The temporary JWT provided in your [Tiptap account](https://cloud.tiptap.dev/v2/configuration/document-server)
is only suitable for brief testing sessions.
### Need help with JWT?
If you need assistance with setting up server-side JWT authentication, you can find guidance at the [bottom of the page](https://tiptap.dev/docs/collaboration/getting-started/authenticate#integrate-jwt-server-side)
.
[](https://tiptap.dev/docs/collaboration/getting-started/authenticate#set-up-authorization)
Set up authorization
----------------------------------------------------------------------------------------------------------------
Setting up the right access controls is important for keeping your documents secure and workflows smooth in Tiptap Collaboration.
This part of the guide walks you through how to use JSON Web Tokens (JWTs) to fine-tune who gets to see and edit what. Whether you need to give someone full access, restrict them to certain documents, or block access entirely, we've got you covered with minimalistic examples.
### Caution
If you exclude the `allowedDocumentNames` property from your JWT setup, users can access all documents in your system!
### [](https://tiptap.dev/docs/collaboration/getting-started/authenticate#allow-full-access-to-every-document)
Allow full access to every document
Omitting the `allowedDocumentNames` property from the JWT payload grants the user access to all documents. This is useful for users who need unrestricted access.
import jsonwebtoken from 'jsonwebtoken'
const data = { sub: 'your_local_user_identifier' }
const jwt = jsonwebtoken.sign(data, 'your_secret')
### [](https://tiptap.dev/docs/collaboration/getting-started/authenticate#limit-access-to-specific-documents)
Limit access to specific documents
To restrict a user's access to specific documents, include those document names in the `allowedDocumentNames` array within the JWT payload. This ensures the user can only access the listed documents.
import jsonwebtoken from 'jsonwebtoken'
const data = {
sub: 'your_local_user_identifier',
allowedDocumentNames: ['user-specific-document-1', 'user-specific-document-2'],
}
const jwt = jsonwebtoken.sign(data, 'your_secret')
### [](https://tiptap.dev/docs/collaboration/getting-started/authenticate#block-access-to-all-documents)
Block access to all documents
To prohibit a user from accessing any documents, provide an empty array for `allowedDocumentNames` in the JWT payload. This effectively blocks access to all documents, except if granted using `readonlyDocumentNames`.
import jsonwebtoken from 'jsonwebtoken'
const data = {
sub: 'your_local_user_identifier',
allowedDocumentNames: [],
}
const jwt = jsonwebtoken.sign(data, 'your_secret')
[](https://tiptap.dev/docs/collaboration/getting-started/authenticate#set-read-only-access)
Set Read-Only access
----------------------------------------------------------------------------------------------------------------
The `readonlyDocumentNames` property in your JWT setup plays a crucial role when you need to allow users to view documents without the ability to edit them. This feature is particularly useful in scenarios where you want to share information with team members for review or reference purposes but need to maintain the integrity of the original document.
By specifying document names in the `readonlyDocumentNames` array, you grant users read-only access to those documents. Users can open and read the documents, but any attempts to modify the content will be restricted. This ensures that sensitive or critical information remains unchanged while still being accessible for necessary personnel.
In this example, we grant read-only access to two documents, `annual-report-2024` and `policy-document-v3`. Users with this JWT can view these documents but cannot make any edits.
import jsonwebtoken from 'jsonwebtoken'
const data = {
sub: 'your_local_user_identifier',
allowedDocumentNames: ['annual-report-2024', 'policy-document-v3'], // or [] ; if you omit "allowedDocumentNames", the user has read-write to all documents, except the ones mentioned in readonlyDocumentNames (as mentioned above!),
readonlyDocumentNames: ['annual-report-2024', 'policy-document-v3'],
}
const jwt = jsonwebtoken.sign(data, 'your_secret')
Incorporating the `readonlyDocumentNames` property into your JWT strategy improves document security by ensuring that only authorized edits are made, preserving the integrity of your critical documents.
[](https://tiptap.dev/docs/collaboration/getting-started/authenticate#allow-commenting-while-on-read-only-access)
Allow commenting while on read-only access
------------------------------------------------------------------------------------------------------------------------------------------------------------
If you want to forbid editing the document but still allow comments, you can add `commentDocumentNames` to the JWT.
import jsonwebtoken from 'jsonwebtoken'
const data = {
sub: 'your_local_user_identifier',
allowedDocumentNames: [], // no write access to any doc
readonlyDocumentNames: ['annual-report-2024', 'policy-document-v3'], // read access
commentDocumentNames: ['annual-report-2024', 'policy-document-v3'], // plus comments access
}
const jwt = jsonwebtoken.sign(data, 'your_secret')
[](https://tiptap.dev/docs/collaboration/getting-started/authenticate#authorize-with-wildcards)
Authorize with Wildcards
------------------------------------------------------------------------------------------------------------------------
Wildcards in JWTs offer a dynamic way to manage document access, allowing for broader permissions within specific criteria without listing each document individually. This method is particularly useful in scenarios where documents are grouped by certain attributes, such as projects or teams.
### [](https://tiptap.dev/docs/collaboration/getting-started/authenticate#manage-project-specific-documents)
Manage project-specific documents
For teams working on multiple projects, it's essential to ensure that members have access only to the documents relevant to their current projects. By using project identifiers with wildcards, you can streamline access management.
import jsonwebtoken from 'jsonwebtoken'
const data = {
sub: 'your_local_user_identifier',
allowedDocumentNames: ['project-alpha/*', 'project-beta/*'],
}
const jwt = jsonwebtoken.sign(data, 'your_secret')
In this example, users will have access to all documents under 'project-alpha' and 'project-beta', making it easier to manage permissions as new documents are added to these projects.
[](https://tiptap.dev/docs/collaboration/getting-started/authenticate#short-lived-jwts)
Short-lived JWTs
--------------------------------------------------------------------------------------------------------
You may want to limit the lifetime of your JWTs by setting an expiration time. JWTs are validated every few seconds, so an expired token will be rejected shortly after expiration and won't be able to reconnect.
Needless to say, with expiring tokens it's essential to handle the expiration case - both mid-session, but also on initial connect or reconnect. Your users may leave tabs open for weeks, and you don't want them to lose data because of an expired token (see also [unsynced Changes](https://tiptap.dev/docs/collaboration/provider/integration#unsynced-changes)
).
If the JWT is not valid when creating the connection (or reconnecting), the `onAuthenticationFailed` callback is triggered (reason `permission-denied`) If the JWT expired during an established connection, the `onClose` callback is triggered with a reason `JWT verification failed`.
In both cases, you need to re-create the provider and supply a new JWT.
Tiptap Collab provides an API to revoke JWTs in case you need to, so we don't recommend making your tokens too short-lived. ([see Revoke JWT API](https://www.postman.com/tiptap-platform/workspace/tiptap-workspace/request/33042171-63ed5bbe-f54d-4a85-b2d0-5c1bc215d2fc)
)
[](https://tiptap.dev/docs/collaboration/getting-started/authenticate#integrate-jwt-server-side)
Integrate JWT server side
--------------------------------------------------------------------------------------------------------------------------
JWT, or JSON Web Token, is a compact, URL-safe means of representing claims to be transferred between two parties. The information in a JWT is digitally signed using a cryptographic algorithm to ensure that the claims cannot be altered after the token is issued. This digital signature makes the JWT a reliable vehicle for secure information exchange in web applications, providing a method to authenticate and exchange information.
### [](https://tiptap.dev/docs/collaboration/getting-started/authenticate#create-a-static-jwt-for-testing)
Create a static JWT for testing
For testing purposes, you might not want to set up a complete backend system to generate JWTs. In such cases, using online tools like [http://jwtbuilder.jamiekurtz.com/](http://jwtbuilder.jamiekurtz.com/)
can be a quick workaround. These tools allow you to create a JWT by inputting the necessary payload and signing it with a secret key.
When using these tools, ensure that the "Key" field is replaced with the secret key from your [Collaboration settings](https://collab.tiptap.dev/apps/settings)
page. You don’t need to change any other information.
Remember, this approach is only recommended for testing due to security risks associated with exposing your secret key.
### [](https://tiptap.dev/docs/collaboration/getting-started/authenticate#generate-jwts-server-side)
Generate JWTs server side
For production-level applications, generating JWTs on the server side is a necessity to maintain security. Exposing your secret key in client-side code would compromise the security of your application. Here’s an example using NodeJS for creating JWTs server-side:
npm install jsonwebtoken
import jsonwebtoken from 'jsonwebtoken'
const payload = {
// The payload contains claims like the user ID, which can be used to identify the user and their permissions.
sub: 'your_local_user_identifier',
}
// The 'sign' method creates the JWT, with the payload and your secret key as inputs.
const jwt = jsonwebtoken.sign(payload, 'your_secret_key_here')
// The resulting JWT is used for authentication in API requests, ensuring secure access.
// Important: Never expose your secret key in client-side code!
This JWT should be incorporated into API requests within the `token` field of your authentication provider, safeguarding user sessions and data access.
To fully integrate JWT into your application, consider setting up a dedicated server or API endpoint, such as `GET /getCollabToken`. This endpoint would dynamically generate JWTs based on a secret stored securely on the server and user-specific information, like document access permissions.
This setup not only increases security but also provides a scalable solution for managing user sessions and permissions in your collaborative application.
Ensure the secret key is stored as an environment variable on the server, or define it directly in the server code. Avoid sending it from the client side.
A full server / API example is available [here](https://github.com/ueberdosis/tiptap-collab-replit/tree/main/src)
.
[PreviouslyInstall](https://tiptap.dev/docs/collaboration/getting-started/install)
[Next upIntegration](https://tiptap.dev/docs/collaboration/provider/integration)
---
# REST API | Tiptap Collaboration Docs
The Collaboration Management API provides a suite of RESTful endpoints for managing documents. This API can be used for document creation, listing, retrieval, updates, deletion, and duplication.
You can experiment with the REST API by visiting our [Postman Collection](https://www.postman.com/tiptap-platform/workspace/tiptap-workspace/collection/33042171-cc186a66-df41-4df8-9c6e-e91b20deffe5)
.
[](https://tiptap.dev/docs/collaboration/documents/rest-api#rate-limits)
Rate limits
------------------------------------------------------------------------------------
To maintain system integrity and protect from misconfigured clients, our infrastructure—including the management API and websocket connections through the `TiptapCollabProvider`—is subject to rate limits.
### [](https://tiptap.dev/docs/collaboration/documents/rest-api#default-rate-limits-per-source-ip)
Default rate limits (per source IP):
* **Requests:** 100
* **Time window:** 5 seconds
* **Burst capacity:** Up to 200 requests
If you encounter these limits under normal operation, please [email us](mailto:humans@tiptap.dev)
.
[](https://tiptap.dev/docs/collaboration/documents/rest-api#access-the-api)
Access the API
------------------------------------------------------------------------------------------
The REST API is exposed directly from your Document server at your custom URL:
https://YOUR_APP_ID.collab.tiptap.cloud/
### [](https://tiptap.dev/docs/collaboration/documents/rest-api#authentication)
Authentication
Authenticate your API requests by including your API secret in the `Authorization` header. You can find your API secret in the [settings](https://cloud.tiptap.dev/v2/configuration/document-server)
of your Tiptap Cloud dashboard.
### [](https://tiptap.dev/docs/collaboration/documents/rest-api#document-identifiers)
Document identifiers
If your document identifier contains a slash (`/`), encode it as `%2F`, e.g., using `encodeURIComponent`.
[](https://tiptap.dev/docs/collaboration/documents/rest-api#api-endpoints-overview)
API endpoints overview
----------------------------------------------------------------------------------------------------------
Access the Collaboration Management API to manage your documents efficiently. For a comprehensive view of all endpoints across Tiptap products, explore our [Postman Collection](https://www.postman.com/tiptap-platform/workspace/tiptap-workspace/collection/33042171-cc186a66-df41-4df8-9c6e-e91b20deffe5)
, which includes detailed examples and configurations.
| Operation | Method | Endpoint | Description |
| --- | --- | --- | --- |
| Create Document | POST | `/api/documents/:identifier` | Create a document using a `yjs` or `json` update message. |
| Batch Import Documents | PUT | `/api/admin/batch-import` | Import multiple documents in bulk. |
| Get Document | GET | `/api/documents/:identifier` | Get a document in `json` or `yjs` format. |
| List Documents | GET | `/api/documents` | Retrieve a list of all documents with pagination options. |
| Duplicate Document | POST + GET | `/api/documents/:identifier` (GET then POST) | Duplicate a document by retrieving it and then creating it with a new identifier. |
| Encrypt Document | POST | `/api/documents/:identifier/encrypt` | Encrypt a document using Base64. |
| List Versions | GET | `/api/documents/:identifier/versions` | Get all versions of a document. |
| Get Version | GET | `/api/documents/:identifier/versions/:versionId` | Get a specific version. |
| Create Version | POST | `/api/documents/:identifier/versions` | Create a new version with optional name and metadata. |
| Update Version | PATCH | `/api/documents/:identifier/versions/:versionId` | Update a version's name or metadata. |
| Revert to Version | POST | `/api/documents/:identifier/versions/:versionId/revertTo` | Revert a document to an older version. |
| Update Document | PATCH | `/api/documents/:identifier` | Apply a Yjs update message to an existing document. |
| Delete Document | DELETE | `/api/documents/:identifier` | Delete a document from the server. |
Take a look at the [metrics and statistics endpoints](https://tiptap.dev/docs/collaboration/operations/metrics)
as well!
[](https://tiptap.dev/docs/collaboration/documents/rest-api#create-a-document)
Create a document
------------------------------------------------------------------------------------------------
POST /api/documents/:identifier
This call lets you create a document using [binary Yjs](https://tiptap.dev/docs/collaboration/getting-started/overview#about-yjs)
or JSON format (default: `yjs`). It can be used to seed documents before a user connects to the Tiptap Collaboration server.
The endpoint returns HTTP status `204` if the document is created successfully, or `409` if the document already exists. To overwrite an existing document, you must [delete it](https://tiptap.dev/docs/collaboration/documents/rest-api#delete-a-document)
first.
* **Yjs format**: To create a document using a Yjs binary update message, first encode the Yjs document using `Y.encodeStateAsUpdate`.
curl --location 'https://YOUR_APP_ID.collab.tiptap.cloud/api/documents/DOCUMENT_NAME' \
--header 'Authorization: YOUR_SECRET_FROM_SETTINGS_AREA' \
--data '@yjsUpdate.binary'
* **JSON format**: To create a document using JSON, pass the query parameter `format=json` and include the document's content in the Tiptap JSON format.
curl --location 'https://YOUR_APP_ID.collab.tiptap.cloud/api/documents/DOCUMENT_NAME?format=json' \
--header 'Authorization: YOUR_SECRET_FROM_SETTINGS_AREA' \
--header 'Content-Type: application/json' \
--data '{
"type": "doc",
"content": [\
{\
"type": "paragraph",\
"content": [\
{\
"type": "text",\
"text": "This is your content."\
}\
]\
}\
]
}'
[](https://tiptap.dev/docs/collaboration/documents/rest-api#batch-import-documents)
Batch import documents
----------------------------------------------------------------------------------------------------------
PUT /api/admin/batch-import
This call lets you import multiple documents in bulk using a predefined JSON structure. Each document must include its metadata (such as created\_at, name, and version) and its content in the Tiptap JSON format.
The endpoint returns HTTP status `204` if the documents are imported successfully, or `400` if the request contains invalid data.
curl --location --request PUT 'https://YOUR_APP_ID.collab.tiptap.cloud/api/admin/batch-import' \
--header 'Content-Type: application/json' \
--data '[\
[\
{\
"created_at": "2024-05-01T10:00:00Z",\
"version": 0,\
"name": "document-1",\
"tiptap_json": {"type": "doc", "content": [{"type": "paragraph", "content": [{"type": "text", "text": "Text of document-1: v0"}]}]}\
},\
{\
"created_at": "2024-05-01T11:00:00Z",\
"version": 1,\
"name": "document-1",\
"tiptap_json": {"type": "doc", "content": [{"type": "paragraph", "content": [{"type": "text", "text": "Text of document-1: v1"}]}]}\
}\
],\
[\
{\
"created_at": "2024-06-01T10:00:00Z",\
"version": 0,\
"name": "document-2",\
"tiptap_json": {"type": "doc", "content": [{"type": "paragraph", "content": [{"type": "text", "text": "Text of document-2: v0"}]}]}\
},\
{\
"created_at": "2024-06-01T11:00:00Z",\
"version": 1,\
"name": "document-2",\
"tiptap_json": {"type": "doc", "content": [{"type": "paragraph", "content": [{"type": "text", "text": "Text of document-2: v1"}]}]}\
}\
]\
]'
[](https://tiptap.dev/docs/collaboration/documents/rest-api#get-a-document)
Get a document
------------------------------------------------------------------------------------------
GET /api/documents/:identifier?format=:format&fragment=:fragment
This call lets you export the specified document with all fragments in JSON or Yjs format. If the document is currently open on your server, we will return the in-memory version; otherwise, we read from the database.
* `format` supports either `yjs`, `base64`, `text`, or `json` (default: `json`). If you choose the `yjs` format, you'll get the binary Yjs update message created with `Y.encodeStateAsUpdate`.
* `fragment` can be an array (e.g., `fragment=a&fragment=b`) or a single fragment you want to export. By default, we only export the `default` fragment. This parameter is only applicable when using the `json` or `text`format; with `yjs`, you'll always get the entire Yjs document.
curl --location 'https://YOUR_APP_ID.collab.tiptap.cloud/api/documents/DOCUMENT_NAME' \
--header 'Authorization: YOUR_SECRET_FROM_SETTINGS_AREA'
When using `axios`, you need to specify `responseType: arraybuffer` in the request options.
import * as Y from 'yjs'
const ydoc = new Y.Doc()
const axiosResult = await axios.get(
'https://YOUR_APP_ID.collab.tiptap.cloud/api/documents/DOCUMENT_NAME?format=yjs',
{
headers: {
Authorization: 'YOUR_SECRET_FROM_SETTINGS_AREA',
},
responseType: 'arraybuffer',
},
)
Y.applyUpdate(ydoc, axiosResult.data)
When using `node-fetch`, you need to use `.arrayBuffer()` and create a Buffer from it:
import * as Y from 'yjs'
const ydoc = new Y.Doc()
const fetchResult = await fetch(
'https://YOUR_APP_ID.collab.tiptap.cloud/api/documents/DOCUMENT_NAME?format=yjs',
{
headers: {
Authorization: 'YOUR_SECRET_FROM_SETTINGS_AREA',
},
},
)
Y.applyUpdate(ydoc, Buffer.from(await docUpdateAsBinaryResponse.arrayBuffer()))
[](https://tiptap.dev/docs/collaboration/documents/rest-api#list-documents)
List documents
------------------------------------------------------------------------------------------
GET /api/documents?take=100&skip=0
This call returns a paginated list of all documents in storage. By default, we return the first 100 documents. Pass `take` and `skip` parameters to adjust pagination.
curl --location 'https://YOUR_APP_ID.collab.tiptap.cloud/api/documents' \
--header 'Authorization: YOUR_SECRET_FROM_SETTINGS_AREA'
[](https://tiptap.dev/docs/collaboration/documents/rest-api#duplicate-a-document)
Duplicate a document
------------------------------------------------------------------------------------------------------
This call lets you copy or duplicate a document. First, retrieve the document using the `GET` endpoint and then create a new one with the `POST` call. Here's an example in typescript:
const docUpdateAsBinaryResponse = await axios.get(
'https://YOUR_APP_ID.collab.tiptap.cloud/api/documents/DOCUMENT_NAME?format=yjs',
{
headers: {
Authorization: 'YOUR_SECRET_FROM_SETTINGS_AREA',
},
responseType: 'arraybuffer',
},
)
await axios.post(
'https://YOUR_APP_ID.collab.tiptap.cloud/api/documents/DOCUMENT_NAME-duplicated',
docUpdateAsBinaryResponse.data,
{
headers: {
Authorization: 'YOUR_SECRET_FROM_SETTINGS_AREA',
},
},
)
Note that the new document will not have the versions of the source document. If you want to preserve versions, you can use the import/export endpoint (see the postman collection)
[](https://tiptap.dev/docs/collaboration/documents/rest-api#encrypt-a-document)
Encrypt a document
--------------------------------------------------------------------------------------------------
POST /api/documents/:identifier/encrypt
This call lets you encrypt a document with the specified identifier using Base64 encryption.
The endpoint returns HTTP status `204` if the document is successfully encrypted, or `404` if the document does not exist.
curl --location 'https://YOUR_APP_ID.collab.tiptap.cloud/api/documents/DOCUMENT_NAME/encrypt' \
--header 'Content-Type: application/json' \
--header 'Authorization: YOUR_SECRET_FROM_SETTINGS_AREA' \
--data '{
"type": "doc",
"content": [\
{\
"type": "paragraph",\
"attrs": {\
"indent": 0,\
"textAlign": "left"\
},\
"content": [\
{\
"text": "the entire document is replaced by this (except if you changed the mode parameter to '\''append'\'')",\
"type": "text"\
}\
]\
}\
]
}'
[](https://tiptap.dev/docs/collaboration/documents/rest-api#version-management)
Version management
--------------------------------------------------------------------------------------------------
Versions capture the state of a document at a point in time. Each version has a `version` number, `date`, and optionally a `name` and `meta` (arbitrary metadata object).
For a full interactive reference of all version endpoints, see the [Postman Collection](https://www.postman.com/tiptap-platform/workspace/tiptap-workspace/folder/33042171-a6e8aa7e-c077-46ee-a2a3-241f8efd5d25?action=share&creator=33042171&active-environment=33042171-378cfb31-9c16-40f0-95b8-b9f0d3c7d2ab)
.
### [](https://tiptap.dev/docs/collaboration/documents/rest-api#revert-to-version)
Revert to version
POST /api/documents/:identifier/versions/:versionId/revertTo
This call lets you revert a document to a specific previous version by applying an update that corresponds to a prior state of the document.
The endpoint returns HTTP status `204` if the document is successfully reverted, or `404` if the document or version is not found.
curl --location --request POST 'https://YOUR_APP_ID.collab.tiptap.cloud/api/documents/DOCUMENT_NAME/versions/VERSION_ID/revertTo' \
--header 'Content-Type: application/json' \
--header 'Authorization: YOUR_SECRET_FROM_SETTINGS_AREA'
### [](https://tiptap.dev/docs/collaboration/documents/rest-api#update-a-version)
Update a version
PATCH /api/documents/:identifier/versions/:versionId
This call lets you update a version's name or metadata. You can use this to rename versions or attach additional context after creation.
For available request body parameters and examples, see the [Postman Collection](https://www.postman.com/tiptap-platform/workspace/tiptap-workspace/folder/33042171-a6e8aa7e-c077-46ee-a2a3-241f8efd5d25?action=share&creator=33042171&active-environment=33042171-378cfb31-9c16-40f0-95b8-b9f0d3c7d2ab)
.
[](https://tiptap.dev/docs/collaboration/documents/rest-api#update-a-document)
Update a document
------------------------------------------------------------------------------------------------
PATCH /api/documents/:identifier
This call accepts a Yjs update message and applies it to the existing document on the server.
The endpoint returns the HTTP status `204` if the document was updated successfully, `404` if the document does not exist, or `422` if the payload is invalid or the update cannot be applied.
curl --location --request PATCH 'https://YOUR_APP_ID.collab.tiptap.cloud/api/documents/DOCUMENT_NAME' \
--header 'Authorization: YOUR_SECRET_FROM_SETTINGS_AREA' \
--data '@yjsUpdate.binary'
The API endpoint also supports JSON document updates, document history for tracking changes without replacing the entire document, and node-specific updates.
For more detailed information on manipulating documents using JSON instead of Yjs, refer to our [Content injection](https://tiptap.dev/docs/collaboration/documents/content-injection)
page.
[](https://tiptap.dev/docs/collaboration/documents/rest-api#delete-a-document)
Delete a document
------------------------------------------------------------------------------------------------
DELETE /api/documents/:identifier
This call deletes a document from the server after closing any open connection to the document.
It returns either HTTP status `204` if the document was deleted successfully, or `404` if the document was not found.
curl --location --request DELETE 'https://YOUR_APP_ID.collab.tiptap.cloud/api/documents/DOCUMENT_NAME' \
--header 'Authorization: YOUR_SECRET_FROM_SETTINGS_AREA'
### Document persists after deletion
If the endpoint returns `204` but the document still exists, make sure that no user is re-creating the document from the provider. We close all connections before deleting a document, but your error handling might recreate the provider, thus creating the document again.
[PreviouslyOverview](https://tiptap.dev/docs/collaboration/documents)
[Next upSnapshots](https://tiptap.dev/docs/collaboration/documents/snapshot)
---
# Collaboration | Tiptap Collaboration Docs
Collaboration adds real-time collaborative editing to your editor. Presence indicators show who’s active, awareness highlights each user’s cursor and selection, and built-in [version history](https://tiptap.dev/docs/collaboration/documents/snapshot)
and [Comments](https://tiptap.dev/docs/comments/getting-started/overview)
track every change.
It runs on our open source [Hocuspocus](https://github.com/ueberdosis/hocuspocus)
backend, syncs content with the Yjs CRDT, and scales from a single demo to thousands of concurrent connections.
[](https://tiptap.dev/docs/collaboration/getting-started/overview#maintain-documents)
Maintain documents
--------------------------------------------------------------------------------------------------------
Every change is stored as a Yjs update. Use the [REST API](https://tiptap.dev/docs/collaboration/documents/rest-api)
to fetch JSON or push programmatic edits. Add [webhooks](https://tiptap.dev/docs/collaboration/core-concepts/webhooks)
for instant notifications and to retrieve all your documents.
Create your own backups of all documents and associated information using our [document management API](https://tiptap.dev/docs/collaboration/documents/rest-api)
.
[](https://tiptap.dev/docs/collaboration/getting-started/overview#enable-advanced-features)
Enable advanced features
--------------------------------------------------------------------------------------------------------------------
* [Version history](https://tiptap.dev/docs/collaboration/documents/snapshot)
– install the Collaboration Snapshot extension and let users restore any previous state.
* [Snapshot compare](https://tiptap.dev/docs/collaboration/documents/snapshot-compare)
– highlight differences between versions with Snapshot Compare.
Enterprise on-premises solution
-------------------------------
Integrate Collaboration and all other Tiptap features into your infrastructure.
* On-premises:
Deploy our docker images in your own stack
* High availability cluster:
Scale confidently to millions of users
* Dedicated support:
Custom development and integration support in Chat
[Let's talk](https://tiptap.dev/contact-sales)
[](https://tiptap.dev/docs/collaboration/getting-started/overview#migrate-from-hocuspocus-or-collaboration-cloud)
Migrate from Hocuspocus or Collaboration Cloud
----------------------------------------------------------------------------------------------------------------------------------------------------------------
Migrating your application from Hocuspocus to either an on-premises solution or the Tiptap Collaboration Cloud involves a simple switch from the `HocuspocusProvider` to the `TiptapCollabProvider`, or the other way around.
This doesn't require any other updates to your setup, and the way you interact with the API won't change as well. The `TiptapCollabProvider` acts as a go-between, managing how your application connects to the server and handles login details.
This migration approach is also applicable when migrating from the Tiptap Collaboration Cloud to an on-premises configuration.
Review the [Batch Import endpoint](https://tiptap.dev/docs/collaboration/documents/rest-api#batch-import-documents)
to migrate your documents.
[](https://tiptap.dev/docs/collaboration/getting-started/overview#schema-management)
Schema management
------------------------------------------------------------------------------------------------------
Tiptap enforces strict schema adherence, discarding any elements not defined in the active schema. This can cause issues when clients using different schema versions concurrently edit a document.
For instance, imagine adding a task list feature in an update. Users on the previous schema won't see these task lists, and any added by a user on the new schema will disappear from their view due to schema discrepancies. This occurs because Tiptap synchronizes changes across clients, removing unrecognized elements based on the older schema.
To mitigate these issues, consider implementing [Invalid Schema Handling](https://tiptap.dev/docs/editor/core-concepts/schema#invalid-schema-handling)
as outlined in the Tiptap Editor docs.
[Next upInstall](https://tiptap.dev/docs/collaboration/getting-started/install)
---
# Inject content API | Tiptap Collaboration Docs
To inject content into documents server-side, use the PATCH endpoint described in this document. This feature supports version history, tracking changes as well as content added through this endpoint.
The update document endpoint also allows JSON updates to modify documents on your Collaboration server, both On-Premises and Cloud:
* Add `json`, `binary`, or `base64` content to any document server-side.
* Inject content into specific nodes using the [UniqueID extension](https://tiptap.dev/docs/editor/extensions/functionality/uniqueid)
.
* Users can still collaborate in real-time as content is injected.
* Track user and injected content changes, fully compatible with [Snapshots](https://tiptap.dev/docs/collaboration/documents/snapshot)
.
### [](https://tiptap.dev/docs/collaboration/documents/content-injection#use-cases)
Use cases
The content injection REST API enables a couple of handy but sophisticated use cases:
* Live translation of document content.
* Programmatically tagging or manipulating document content server-side.
* Integrating server-side components, like executing SQL queries and displaying results.
* Version history integration and conflict-free merging of concurrent edits.
[](https://tiptap.dev/docs/collaboration/documents/content-injection#update-a-document)
Update a document
---------------------------------------------------------------------------------------------------------
To update an existing document on the Collaboration server, you can use the `PATCH` method with the following API endpoint:
PATCH /api/documents/:identifier?format=:format
This endpoint accepts a Yjs update message and applies it to the specified document. The `format` query parameter specifies the format of the update and can be one of the following:
* `json`: Updates the document using JSON format (with some caveats, [see below](https://tiptap.dev/docs/collaboration/documents/content-injection#update-via-json)
).
* `binary`: Directly using Yjs's `Y.encodeStateAsUpdate` method.
* `base64`: The binary state encoded as a Base64 string.
Upon successful update, the server will return HTTP status `204`. If the document does not exist, it will return `404`, and if the payload is invalid or the update cannot be applied, it will return `422`.
**Example:** `curl` command to update a document
curl --location --request PATCH 'https://YOUR_APP_ID.collab.tiptap.cloud/api/documents/DOCUMENT_NAME' \\
--header 'Authorization: YOUR_SECRET_FROM_SETTINGS_AREA' \\
--data '@yjsUpdate.binary'
[](https://tiptap.dev/docs/collaboration/documents/content-injection#update-via-json)
Update via JSON
-----------------------------------------------------------------------------------------------------
When updating via JSON, the server computes the difference between the current document state and the provided JSON, then internally calculates the required Yjs update to reach the target state.
To ensure precise updates, especially for node-specific changes, it is recommended to use the `nodeAttributeName` and `nodeAttributeValue` parameters. These can be generated by Tiptap's [UniqueID Extension](https://tiptap.dev/docs/editor/extensions/functionality/uniqueid)
or a custom implementation. Note that this only works for top level nodes.
* `nodeAttributeName`: Configured as `attributeName` in the [UniqueID extension](https://tiptap.dev/docs/editor/extensions/functionality/uniqueid)
.
* `nodeAttributeValue`: The unique value generated for the node being updated. You can pass multiple values with `?nodeAttributeValue=a&=nodeAttributeValue=b`.
You can use `?mode=append` to append nodes to the document's JSON representation without altering existing nodes.
Omitting these parameters may result in overwriting any updates made between fetching the document and issuing the update call. The `get document` call returns a header `x-${fragmentName}-checksum` which can be used to detect conflicts by passing it to the update call as `?checksum=${checksum}`. If the document has been updated since the last fetch, the update will fail with a `409 Checksum mismatch.` status.
**Example:** Updating a document using JSON
// Define the document name, secret, and application ID
const docName = '' // URI-encoded if necessary
const secret = ''
const appId = '';
// Construct the base URL
const url = `https://${appId}.collab.tiptap.cloud`
// Fetch the current document's JSON representation
const docJson = await axios.get(`${url}/api/documents/${docName}?format=json`, {
headers: {
Authorization: secret
},
})
// Extract the document's JSON content
const tiptapJson = docJson.data
const nodes = tiptapJson.content
// Find and log specific nodes using their unique identifiers
const query = nodes.find(n => n.attrs?.identifier === 'fe5c0789-85d9-4877-a2c3-bccf5d874866').content[0].text
const resultTable = nodes.find(n => n.attrs?.identifier === '246368b6-0746-4ca1-a16f-8d964aff4041')
console.log(`Query: ${query}`)
console.log(JSON.stringify(resultTable.content))
// Append new content to the result table node
resultTable.content.push({
// New table row content here
{
"type": "tableRow",
"content": [\
{\
"type": "tableCell",\
"attrs": {\
"colspan": 1,\
"rowspan": 1\
},\
"content": [\
{\
"type": "paragraph",\
"attrs": {\
"textAlign": "left"\
},\
"content": [\
{\
"type": "text",\
"text": "Jan"\
}\
]\
}\
]\
},\
{\
"type": "tableCell",\
"attrs": {\
"colspan": 1,\
"rowspan": 1\
},\
"content": [\
{\
"type": "paragraph",\
"attrs": {\
"textAlign": "left"\
},\
"content": [\
{\
"type": "text",\
"text": "Thurau"\
}\
]\
}\
]\
},\
{\
"type": "tableCell",\
"attrs": {\
"colspan": 1,\
"rowspan": 1\
},\
"content": [\
{\
"type": "paragraph",\
"attrs": {\
"textAlign": "left"\
},\
"content": [\
{\
"type": "text",\
"text": "jan@janthurau.de"\
}\
]\
}\
]\
}\
]
}
})
// Send the updated JSON back to the server to apply the changes
await axios.patch(`${url}/api/documents/${docName}?format=json`, tiptapJson, {
headers: {
Authorization: secret
}
})
### [](https://tiptap.dev/docs/collaboration/documents/content-injection#update-only-node-attrs)
Update only node attrs
If you want to only update attributes of a node, you can use the `?mode=attrs` query parameter. This will only update the attributes of the node and not its content. In this mode, the `nodeAttributeName` and `nodeAttributeValue` parameters work for any (not just top level) nodes.
Note that we're deleting all attrs on that node and then setting only the ones specified in the payload of the request. Not specifying a node filter (nodeAttributeName, nodeAttributeValue) will result in all nodes being updated.
curl --location --request PATCH '/api/documents/:identifier?format=json&nodeAttributeName=id&nodeAttributeValue=12&mode=attrs' \
--data '{
"indent": 12,
"textAlign": "right"
}'
[](https://tiptap.dev/docs/collaboration/documents/content-injection#create-a-document)
Create a document
---------------------------------------------------------------------------------------------------------
To seed a new document on the Tiptap Collab server, use the `POST` method with the following endpoint:
POST /api/documents/:identifier?format=:format
The server will return HTTP status `204` for successful creation, `409` if the document already exists (you must delete it first to overwrite), and `422` if the action failed.
The `format` parameter accepts the same values as the update endpoint (`binary`, `base64`, or `json`).
[PreviouslyCompare Snapshots](https://tiptap.dev/docs/collaboration/documents/snapshot-compare)
[Next upConfigure runtime](https://tiptap.dev/docs/collaboration/operations/configure)
---
# Configure runtime | Tiptap Collaboration Docs
Configure runtime settings in your document server to manage your environment directly via the REST API.
These settings let you modify secrets, webhook URLs, and more, particularly when adapting to changes in your project requirements or security protocols, without restarting your application.
[](https://tiptap.dev/docs/collaboration/operations/configure#settings-overview)
Settings overview
--------------------------------------------------------------------------------------------------
Several settings can be adjusted dynamically:
| Key | Description |
| --- | --- |
| `secret` | [JWT secret](https://tiptap.dev/docs/collaboration/getting-started/authenticate) , auto-generated on the first launch |
| `api_secret` | API secret to use in the Authorization header, auto-generated on the first launch |
| `allowed_origins` | Validates `Origin` headers against the provided values (comma separated), e.g., `https://test.tiptap.dev,https://*.tiptap.dev`; If not set, validation is disabled |
| `authentication_disabled` | Set to `1` to disable authentication, `0` to enable (default: `0`) |
| `webhook_url` | URL for receiving webhook callbacks |
| `webhook_loader_url` | Optional webhook URL for initially loading documents. See [webhooks](https://tiptap.dev/docs/collaboration/core-concepts/webhooks#loader-webhook) for more information. |
| `webhook_version` | Version of the webhook |
| `webhook_awareness` | Enable awareness webhooks for user activity, tracking `user.connected` and `user.disconnected` events (`1` for enabled, `0` for disabled) |
| `webhook_log_errors_only` | Log only webhook errors; successful webhook logs are disabled |
| `default_auto_versioning` | Set to `1` to enable auto versioning, `0` to disable (default: `0`) |
| `default_auto_versioning_interval` | Interval for auto versioning in seconds (default: `30` seconds) |
| `auto_versioning_all_fragments` | Set to `1` to make versioning listen for changes on any fragment/field of the doc (default is `0`, which will just watch the `default` fragment). This both affects auto versioning and manual versioning ; manual versioning will require `force=true` when this setting is `0`. Changing this requires the server to re-load the document to come into effect. |
| `jwt_explicit_access` | Set to `1` to prevent document access when no "allowedDocumentNames" is set in the JWT. |
| `skip_s3_health_check` | Set to `1` to turn off the s3 health check (on-prem only). This is useful if your s3 storage does not support `getCalledIdentity`. |
| `name` | Instance name for identification |
| `webhook_include_ydoc_state` | Set this to `1` to include the full yjs document in all document.saved webhooks |
| `thread_authenticator` | Set this to `1` to enable the thread authenticator. Threads/comments will automatically be assigned with the user id from the JWT, and it will not be possible to edit or delete foreign threads. However, foreign threads can usually be resolved (except if disabled using the setting below) |
| `commentonly_disallow_resolve_foreign_threads` | Set this to `1` to disallow resolving foreign threads when on comment-only connections. |
| `webhook_include_fields` | Set this to `1` to include custom yjs fields in the webhook (see [webhooks](https://tiptap.dev/docs/collaboration/core-concepts/webhooks#custom-fields) ). |
[](https://tiptap.dev/docs/collaboration/operations/configure#managing-settings-via-api)
Managing settings via API
------------------------------------------------------------------------------------------------------------------
The collaboration platform offers a straightforward API for managing these settings. Replace `:key` with the setting key you wish to update.
### [](https://tiptap.dev/docs/collaboration/operations/configure#create-or-overwrite-settings)
Create or overwrite settings
Use this call to add or update settings:
curl --location --request PUT 'https://YOUR_APP_ID.collab.tiptap.cloud/api/admin/settings/:key' \
--header 'Authorization: YOUR_SECRET_FROM_SETTINGS_AREA' --header 'Content-Type: text/plain' \
-d 'your value'
### [](https://tiptap.dev/docs/collaboration/operations/configure#list-current-settings)
List current settings
Use this call to retrieve a list of all current settings:
curl --location 'https://YOUR_APP_ID.collab.tiptap.cloud/api/admin/settings' \
--header 'Authorization: YOUR_SECRET_FROM_SETTINGS_AREA'
### [](https://tiptap.dev/docs/collaboration/operations/configure#retrieve-a-specific-setting)
Retrieve a specific setting
Use this call to retrieve the value of a particular setting:
curl --location 'https://YOUR_APP_ID.collab.tiptap.cloud/api/admin/settings/:key' \
--header 'Authorization: YOUR_SECRET_FROM_SETTINGS_AREA'
### [](https://tiptap.dev/docs/collaboration/operations/configure#delete-a-setting)
Delete a setting
Use this call to delete a setting:
curl --location --request DELETE 'https://YOUR_APP_ID.collab.tiptap.cloud/api/admin/settings/:key' \
--header 'Authorization: YOUR_SECRET_FROM_SETTINGS_AREA'
[](https://tiptap.dev/docs/collaboration/operations/configure#server-performance-metrics)
Server performance metrics
--------------------------------------------------------------------------------------------------------------------
Use the `/api/statistics` endpoint to gather server performance data, including total document count, peak concurrent connections, total connections over the last 30 days, and lifetime connection counts. Review the [metrics](https://tiptap.dev/docs/collaboration/operations/metrics)
page for additional information.
curl --location 'https://YOUR_APP_ID.collab.tiptap.cloud/api/statistics' \
--header 'Authorization: YOUR_SECRET_FROM_SETTINGS_AREA'
[PreviouslyInject content](https://tiptap.dev/docs/collaboration/documents/content-injection)
[Next upMetrics](https://tiptap.dev/docs/collaboration/operations/metrics)
---
# Configure | Tiptap Comments Docs
Comments are embedded within documents in the Collaboration Cloud. To enable comments, integrate the TiptapCollabProvider and configure your setup to support comment functionality.
[](https://tiptap.dev/docs/comments/core-concepts/configure#provider)
Provider
------------------------------------------------------------------------------
The `TiptapCollabProvider` instance
Default: `null`
const tiptapCollabProvider = new TiptapCollabProvider({
// your provider options
})
Comments.configure({
provider: tiptapCollabProvider,
})
[](https://tiptap.dev/docs/comments/core-concepts/configure#classes)
Classes
----------------------------------------------------------------------------
The classes used for the threads.
Default:
{
thread: 'tiptap-thread',
threadInline: 'tiptap-thread--inline',
threadBlock: 'tiptap-thread--block',
threadHovered: 'tiptap-thread--hovered',
threadSelected: 'tiptap-thread--selected',
threadResolved: 'tiptap-thread--resolved',
threadUnresolved: 'tiptap-thread--unresolved',
}
Comments.configure({
classes: {
thread: 'my-thread',
threadInline: 'my-thread-inline',
threadBlock: 'my-thread-block',
threadHovered: 'my-thread-hovered',
threadSelected: 'my-thread-selected',
threadResolved: 'my-thread-resolved',
threadUnresolved: 'my-thread-unresolved',
},
})
[](https://tiptap.dev/docs/comments/core-concepts/configure#onclickthread)
onClickThread
----------------------------------------------------------------------------------------
A callback that is called when a thread is clicked. If the thread is clicked, the thread ID is passed to the callback. If no thread is clicked, `null` is passed.
Default: `undefined`
Comments.configure({
// ID can be a string or null
onClickThread: (id) => console.log('Thread clicked', id),
})
[](https://tiptap.dev/docs/comments/core-concepts/configure#deleteunreferencedthreads)
deleteUnreferencedThreads
----------------------------------------------------------------------------------------------------------------
A boolean option that controls whether to delete threads not referenced in the document. By default, threads are marked as deleted when they are not referenced in a document anymore. This is useful for cleaning up threads that are no longer needed.
However, if you want to keep the threads even if they are not referenced in the document, you can set this option to `false`.
**Default:**: `true`
Comments.configure({
// keep threads even if they are not referenced in the document
deleteUnreferencedThreads: false,
})
[](https://tiptap.dev/docs/comments/core-concepts/configure#uselegacywrapping)
useLegacyWrapping
------------------------------------------------------------------------------------------------
### Warning
The new wrapping mechanism uses a different schema for threads on block nodes, which is not compatible with the previous wrapping behavior. If this is set to `false` without mapping existing thread nodes to the new schema, the threads content will be stripped from the document.
A boolean option that controls whether to use the legacy wrapping mechanism for multi-line comments. We suggest for new implementations to set this to `false`, and existing integrations can stay on the previous behavior. This is only required for backwards compatibility with existing comments, and it will be removed in the future.
The new wrapping mechanism is more flexible, allowing to wrap content more precise and supports mixed wrapping of inline and block nodes.
**Default:** `true`
Comments.configure({
// enable new flexible block wrapping
useLegacyWrapping: false,
})
[PreviouslyStyle threads](https://tiptap.dev/docs/comments/core-concepts/style-threads)
[Next upThread authentication](https://tiptap.dev/docs/comments/core-concepts/thread-authentication)
---
# Metrics | Tiptap Collaboration Docs
The Tiptap Collaboration API offers several endpoints to access real-time statistics and health information for both the server and individual documents. A simplified version of the metrics is also available in the cloud dashboard.
These endpoints help to troubleshoot issues, monitor server performance, or build analytics dashboards for insights into user interactions and system status. Integrating statistics into your monitoring systems allows you to proactively manage your collaboration environment's health.
### Review the postman collection
Experiment with the REST API by visiting our [Postman Collection](https://www.postman.com/tiptap-platform/workspace/tiptap-workspace/request/33042171-1adccc70-772c-464a-8602-1303659f9af6)
.
[](https://tiptap.dev/docs/collaboration/operations/metrics#access-the-api)
Access the API
------------------------------------------------------------------------------------------
The REST API is exposed directly from your Document server at your custom URL:
https://YOUR_APP_ID.collab.tiptap.cloud/
### [](https://tiptap.dev/docs/collaboration/operations/metrics#authentication)
Authentication
Authenticate your API requests by including your API secret in the `Authorization` header. You can find your API secret in the [settings](https://cloud.tiptap.dev/v2/configuration/document-server)
of your Tiptap Cloud dashboard.
### [](https://tiptap.dev/docs/collaboration/operations/metrics#document-identifiers)
Document identifiers
If your document identifier contains a slash (`/`), encode it as `%2F`, e.g., using `encodeURIComponent`.
[](https://tiptap.dev/docs/collaboration/operations/metrics#server-statistics-endpoint)
Server statistics endpoint
------------------------------------------------------------------------------------------------------------------
This endpoint provides basic statistics about the Tiptap Collaboration server, offering insights into overall activity and usage metrics.
GET /api/statistics
### Caution
The total number of connections in the last 30 days and the lifetime connection count are presented as strings due to their internal representation as BigInt values.
**Example:** Server statistics
{
"totalDocuments": 4,
"totalConnections30d": "3",
"maxConcurrentConnections30d": 3,
"lifetimeConnections": "144",
"currentConnectionsCount": 3,
"currentLoadedDocumentsCount": 1,
"openDocuments": ["testdocument"],
"connectionsPerDocument": {
"testdocument": 3
},
"version": "3.33.0"
}
[](https://tiptap.dev/docs/collaboration/operations/metrics#document-statistics-endpoint)
Document statistics endpoint
----------------------------------------------------------------------------------------------------------------------
Retrieve statistics for a specific document by its identifier. Use this endpoint to monitor real-time user engagement with a document.
GET /api/documents/:identifier/statistics
**Example:** Statistics of a document named :identifier
{
"currentConnections": 2,
"connectedIps": ["127.0.0.1", "10.100.1.23"]
}
[](https://tiptap.dev/docs/collaboration/operations/metrics#server-health-endpoint)
Server health endpoint
----------------------------------------------------------------------------------------------------------
Use this call to check liveness, readiness, and cconnectivity to essential components like the database and Redis.
GET /health
**Example:** Issue with Redis
HTTP 500:
DB:ok
REDIS:fail
**Example:** No Redis detected
HTTP 200:
DB:ok
REDIS:inactive
[PreviouslyConfigure runtime](https://tiptap.dev/docs/collaboration/operations/configure)
---
# Style threads | Tiptap Comments Docs
To style threads in your Tiptap editor, we use decoration classes that are wrapped around the threads. Since threads can include block nodes, we have two types of decorations: one for inline threads, which are wrapped around the text, and one for block threads, which are wrapped around the block node.
By default, the following css classes are used for the threads:
.tiptap-thread {} // the thread class for any type of thread
.tiptap-thread--inline {} // the thread class for inline threads
.tiptap-thread--block {} // the thread class for block threads
.tiptap-thread--hovered {} // the thread class for hovered threads
.tiptap-thread--selected {} // the thread class for selected threads
.tiptap-thread--resolved {} // the thread class for resolved threads
.tiptap-thread--unresolved {} // the thread class for unresolved threads
Those classes can also be overwritten by passing through the classes to the `CommentsKit` extension.
const editor = new Editor({
...
extensions: [\
...,\
CommentsKit.configure({\
classes: {\
thread: 'my-thread',\
threadInline: 'my-thread-inline',\
threadBlock: 'my-thread-block',\
threadHovered: 'my-thread-hovered',\
threadSelected: 'my-thread-selected',\
threadResolved: 'my-thread-resolved',\
threadUnresolved: 'my-thread-unresolved',\
},\
}),\
]
})
[](https://tiptap.dev/docs/comments/core-concepts/style-threads#handling-hover-events)
Handling hover events
------------------------------------------------------------------------------------------------------------
Let's say you have a sidebar with a list of threads, and you want to highlight the thread currently hovered in your sidebar inside the editor. You can dispatch a transaction to the editor with the meta `threadMouseOver` or `threadMouseOut` to indicate which thread is currently hovered.
const onHoverThread = (threadId) => {
const { tr } = editor.state
tr.setMeta('threadMouseOver', threadId)
editor.view.dispatch(tr)
}
const onUnhoverThread = (threadId) => {
const { tr } = editor.state
tr.setMeta('threadMouseOut', threadId)
editor.view.dispatch(tr)
}
;
[PreviouslyManage threads](https://tiptap.dev/docs/comments/core-concepts/manage-threads)
[Next upConfigure](https://tiptap.dev/docs/comments/core-concepts/configure)
---
# Snapshot extension | Tiptap Editor Docs
Document history records every change to your content so you can roll back mistakes, audit edits, or branch a new draft from any point.
This page walks you through installation, configuration, and common tasks for the **History** extension.
### Public Demo
The editor content is shared across all demo visitors.
[](https://tiptap.dev/docs/collaboration/documents/snapshot#access-the-pro-registry)
Access the Pro registry
------------------------------------------------------------------------------------------------------------
The Version History extension is published in Tiptap’s private npm registry. Integrate the extension by following the [private registry guide](https://tiptap.dev/docs/guides/pro-extensions)
. If you already authenticated your Tiptap account you can go straight to [#Install](https://tiptap.dev/docs/collaboration/documents/snapshot#install)
.
[](https://tiptap.dev/docs/collaboration/documents/snapshot#install)
Install
----------------------------------------------------------------------------
npm install @tiptap-pro/extension-snapshot @hocuspocus/transformer
**Note**: The `@hocuspocus/transformer` package is required for transforming Y.js binary into Tiptap JSON. It also requires Y.js installed for collaboration. If you don't have it installed, run `npm install yjs` in your project. This should happen automatically if you use NPM (as it automatically resolves peer dependencies).
[](https://tiptap.dev/docs/collaboration/documents/snapshot#settings)
Settings
------------------------------------------------------------------------------
| Setting | Type | Default |
| --- | --- | --- |
| provider | `TiptapCollabProvider` | `null` |
| onUpdate | `function` | `() => {}` |
[](https://tiptap.dev/docs/collaboration/documents/snapshot#autoversioning)
Autoversioning
------------------------------------------------------------------------------------------
The autoversioning feature automatically creates new versions of your document at regular intervals. This ensures that you have a comprehensive change history without manual intervention.
You can enable this feature using the `enableAutoVersioning` command (default: disabled).
When you enable autoversioning, Tiptap creates new versions at regular intervals (30 seconds by default, only if the document has changed). This can create many versions, so you may want to increase the interval. To customize the interval, you can do the following:
// Set the interval (in seconds) between autoversions
provider.setAutoVersioningInterval(900)
provider.enableAutoVersioning()
provider.disableAutoVersioning()
[](https://tiptap.dev/docs/collaboration/documents/snapshot#revert-to-a-version)
Revert to a version
----------------------------------------------------------------------------------------------------
When you revert to a previous version:
1. If there are unsaved changes, Tiptap automatically creates a version to preserve those changes.
2. Tiptap creates a new version at the top of the history with the content from the version you select.
3. All users can continue working from this new version.
Note that reverting only affects the `default` fragment in the ydoc. When you revert the Tiptap content, the comments don't change (unless you specify a different `field` in the TiptapCollabProvider).
You can integrate the [compare snapshots](https://tiptap.dev/docs/collaboration/documents/snapshot-compare)
extension to highlight differences between versions, ensuring you choose the right version to restore.
[](https://tiptap.dev/docs/collaboration/documents/snapshot#storage)
Storage
----------------------------------------------------------------------------
| Key | Type | Description |
| --- | --- | --- |
| currentVersion | `number` | The current version. |
| lastSaved | `Date` | The last saved timestamp |
| latestVersion | `number` | The latest version. |
| provider | `TiptapCollabProvider` | The Collaboration provider instance |
| status | `string` | The status of the provider - can be `connecting`, `connected` or `disconnected` |
| synced | `boolean` | Is the version history synced with the server |
| versioningEnabled | `boolean` | Is versioning enabled |
| versions | `array` | The array of versions that are stored in the history. Each version has `version` (number), `date` (number), `name` (optional string), and `meta` (optional object). |
[](https://tiptap.dev/docs/collaboration/documents/snapshot#commands)
Commands
------------------------------------------------------------------------------
| Command | Description |
| --- | --- |
| saveVersion | Creates a new version with an optional name, force flag, and metadata |
| fetchVersions | Fetches the list of versions from the server and updates the storage |
| enableVersioning | Enables autoversioning for this document |
| disableVersioning | Disables autoversioning for this document |
| revertToVersion | Revert to a specific version, can create a new revert version with optional title |
[](https://tiptap.dev/docs/collaboration/documents/snapshot#examples)
Examples
------------------------------------------------------------------------------
### [](https://tiptap.dev/docs/collaboration/documents/snapshot#basic-setup)
Basic setup
const provider = new TiptapCollabProvider({
// ...
})
const editor = new Editor({
// ...
extensions: [\
// ...\
Snapshot.configure({\
provider,\
}),\
],
})
### [](https://tiptap.dev/docs/collaboration/documents/snapshot#store-version-updates)
Store version updates
In this example we retrieve the data of a version update and save it into a variable
let currentVersion = 0
let latestVersion = 0
let autoversioningEnabled = false
let versions = []
const provider = new TiptapCollabProvider({
// ...
})
const editor = new Editor({
// ...
extensions: [\
// ...\
Snapshot.configure({\
provider,\
onUpdate(payload) {\
currentVersion = payload.currentVersion\
latestVersion = payload.version\
versions = payload.versions\
autoversioningEnabled = payload.versioningEnabled\
},\
}),\
],
})
### [](https://tiptap.dev/docs/collaboration/documents/snapshot#access-version-data-directly-from-storage)
Access version data directly from storage
const provider = new TiptapCollabProvider({
// ...
})
const editor = new Editor({
// ...
extensions: [\
// ...\
Snapshot.configure({\
provider,\
}),\
],
})
const latestVersion = editor.storage.snapshot.latestVersion
const currentVersion = editor.storage.snapshot.currentVersion
const versions = editor.storage.snapshot.versions
const autoversioningEnabled = editor.storage.snapshot.versioningEnabled
### [](https://tiptap.dev/docs/collaboration/documents/snapshot#create-a-new-version-manually)
Create a new version manually
editor.commands.saveVersion('My new custom version')
### [](https://tiptap.dev/docs/collaboration/documents/snapshot#create-a-version-with-metadata)
Create a version with metadata
You can attach arbitrary metadata to a version. This is useful for storing additional context like the author, tags, or any custom data.
editor.commands.saveVersion('Release v1.0', false, {
author: 'Jane Doe',
tags: ['release', 'stable'],
})
The `saveVersion` command accepts three parameters:
| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| name | `string` | `undefined` | An optional name for the version |
| force | `boolean` | `false` | Create the version even if no changes were detected |
| meta | `Record` | `undefined` | Arbitrary metadata to attach to the version |
### [](https://tiptap.dev/docs/collaboration/documents/snapshot#fetch-versions-from-the-server)
Fetch versions from the server
You can manually refresh the list of versions from the server using the `fetchVersions` command. This updates the extension storage with the latest version data.
editor.commands.fetchVersions()
### [](https://tiptap.dev/docs/collaboration/documents/snapshot#enable-autoversioning-on-document)
Enable autoversioning on document
editor.commands.enableVersioning()
### [](https://tiptap.dev/docs/collaboration/documents/snapshot#disable-autoversioning-on-document)
Disable autoversioning on document
editor.commands.disableVersioning()
### [](https://tiptap.dev/docs/collaboration/documents/snapshot#revert-with-version-id)
Revert with version ID
editor.commands.revertToVersion(4)
### [](https://tiptap.dev/docs/collaboration/documents/snapshot#revert-with-version-id-with-custom-name)
Revert with version ID with custom name
In this example, the editor command helps you go back to version 4. When you use this command, it takes you back to how things were in version 4, and it also saves this old version as a new version called 'Revert to version'. This way, you can continue working from version 4, but it's now saved as the latest version.
editor.commands.revertToVersion(4, 'Revert to version')
### [](https://tiptap.dev/docs/collaboration/documents/snapshot#revert-name-and-back-up)
Revert, name, and back up
In this example, when you revert to version 4 of your document, the editor automatically creates two new versions. The first new version captures and saves your document’s state just before reverting, serving as a backup. The second new version restores the document to version 4, allowing you to continue from here as your new starting point.
editor.commands.revertToVersion(4, 'Revert to version', 'Unversioned changes before revert')
### [](https://tiptap.dev/docs/collaboration/documents/snapshot#using-provider-level-version-methods)
Using provider-level version methods
The `TiptapCollabProvider` exposes methods for working with versions directly, without the Snapshot extension.
#### [](https://tiptap.dev/docs/collaboration/documents/snapshot#list-versions)
List versions
Fetch the full list of versions from the server:
const versions = await provider.listVersions()
// versions: Array<{ version: number, date: number, name?: string, meta?: Record }>
#### [](https://tiptap.dev/docs/collaboration/documents/snapshot#get-cached-versions)
Get cached versions
Get the locally cached list of versions (no server request):
const versions = provider.getVersions()
#### [](https://tiptap.dev/docs/collaboration/documents/snapshot#watch-for-version-changes)
Watch for version changes
Subscribe to version list updates using callbacks:
const onVersionsChange = (versions) => {
console.log('Versions updated:', versions)
}
// Start watching
provider.watchVersions(onVersionsChange)
// Stop watching
provider.unwatchVersions(onVersionsChange)
#### [](https://tiptap.dev/docs/collaboration/documents/snapshot#create-a-version-with-metadata)
Create a version with metadata
provider.createVersion('My version', false, {
author: 'Jane Doe',
tags: ['draft'],
})
### [](https://tiptap.dev/docs/collaboration/documents/snapshot#implementing-version-previews-for-your-editor)
Implementing version previews for your editor
The examples above directly modify the document and do not provide local-only previews of the version. Therefore, you must create your own frontend solution for this requirement. You can leverage the stateless messaging system of the `TiptapCloudProvider` to request a specific version from the server.
Start by attaching a listener to the provider:
// Import the getPreviewContentFromVersionPayload helper function (refer to details below)
import { watchPreviewContent } from '@tiptap-pro/extension-snapshot'
// Configure the provider
const provider = new TiptapCollabProvider({ ... })
// Use the watchPreviewContent util function to watch for content changes on the provider
const unbindWatchContent = watchPreviewContent(provider, content => {
// set your editors content
editor.commands.setContent(content)
})
If you want to unbind the watcher, you can call the returned `unbindWatchContent` function like this:
const unbindWatchContent = watchPreviewContent(provider, (content) => {
// set your editors content
editor.commands.setContent(content)
})
// unwatch
unbindWatchContent()
Following this setup, you can trigger `version.preview` requests like so:
// Define a function that sends a version.preview request to the provider
const requestVersion = (version) => {
provider.sendStateless(
JSON.stringify({
action: 'version.preview',
// Include your version number here
version,
}),
)
}
// Trigger the request
requestVersion(1)
// You can then link this function to button clicks or other UI elements to trigger the request.
To go beyond previews and compare different versions visually, the [compare snapshots](https://tiptap.dev/docs/collaboration/documents/snapshot-compare)
extension provides an easy way to see the changes between any two versions within the editor.
[](https://tiptap.dev/docs/collaboration/documents/snapshot#utility-functions)
Utility functions
------------------------------------------------------------------------------------------------
### [](https://tiptap.dev/docs/collaboration/documents/snapshot#getpreviewcontentfromversionpayload)
getPreviewContentFromVersionPayload
This function turns the payload from the Collaboration provider into Tiptap JSON content.
| Argument | Description |
| --- | --- |
| payload | The Hocuspocus payload for the version preview event |
| field | The field you want to parse. Default: `default` |
const myContent = getPreviewContentFromVersionPayload(payload, 'default')
### [](https://tiptap.dev/docs/collaboration/documents/snapshot#watchpreviewcontent)
watchPreviewContent
This function sets up a watcher on your provider that watches the necessary events to react to version content changes. It also returns a new function that you can use to unwatch those events.
| Argument | Description |
| --- | --- |
| provider | The Collaboration provider |
| callback | The callback function that is called, the argument is the Tiptap JSON content |
| field | The watched field - defaults to `default` |
const unwatchContent = watchPreviewContent(provider, editor.commands.setContent, 'default')
// unwatch the version preview content
unwatchContent()
[](https://tiptap.dev/docs/collaboration/documents/snapshot#possible-provider-payloads)
Possible provider payloads
------------------------------------------------------------------------------------------------------------------
Here is a list of payloads that can be sent or received from the provider:
### [](https://tiptap.dev/docs/collaboration/documents/snapshot#outgoing)
Outgoing
#### [](https://tiptap.dev/docs/collaboration/documents/snapshot#documentrevert)
`document.revert`
Request a document revert to a given version with optional title settings.
provider.revertToVersion(1, {
currentVersionName: 'Before reverting to version 1',
newVersionName: 'Revert to version 1',
})
#### [](https://tiptap.dev/docs/collaboration/documents/snapshot#versioncreate)
`version.create`
Creates a new version with an optional name and metadata.
provider.sendStateless(
JSON.stringify({
action: 'version.create',
name: 'My custom version',
force: false,
meta: { author: 'Jane Doe' },
}),
)
#### [](https://tiptap.dev/docs/collaboration/documents/snapshot#versionslist)
`versions.list`
Requests the full list of versions from the server.
provider.sendStateless(
JSON.stringify({ action: 'versions.list' }),
)
### [](https://tiptap.dev/docs/collaboration/documents/snapshot#incoming)
Incoming
#### [](https://tiptap.dev/docs/collaboration/documents/snapshot#versioncreated)
`version.created`
This stateless message includes information about newly created versions, including any metadata that was attached.
provider.on('stateless', (data) => {
const payload = JSON.parse(data.payload)
if (payload.event === 'version.created') {
const latestVersion = payload.version
const name = payload.name // optional version name
const date = payload.date // creation timestamp
const meta = payload.meta // optional metadata object
}
})
#### [](https://tiptap.dev/docs/collaboration/documents/snapshot#versionslist)
`versions.list`
The `listVersions` method returns the full list of versions. Each version includes `version`, `date`, and optional `name` and `meta` fields.
const versions = await provider.listVersions()
// versions is an array of { version: number, date: number, name?: string, meta?: object }
#### [](https://tiptap.dev/docs/collaboration/documents/snapshot#documentreverted)
`document.reverted`
This stateless message includes information about a document revert.
provider.on('stateless', (data) => {
const payload = JSON.parse(data.payload)
if (payload.event === 'document.reverted') {
const currentVersion = payload.version
}
})
[PreviouslyREST API](https://tiptap.dev/docs/collaboration/documents/rest-api)
[Next upCompare Snapshots](https://tiptap.dev/docs/collaboration/documents/snapshot-compare)
---
# Provider | Tiptap Collaboration Docs
Together with the Collaboration backend, providers serve as the backbone for real-time collaborative editing. They establish and manage the communication channels between users, ensuring that updates and changes to documents are synchronized across all participants.
Providers handle the complexities of real-time data exchange, including conflict resolution, network reliability, and user presence awareness.
The `TiptapCollabProvider` adds advanced features tailored for collaborative environments, such as WebSocket message authentication, debug modes, and flexible connection strategies.
[](https://tiptap.dev/docs/collaboration/provider/integration#set-up-the-provider)
Set up the provider
------------------------------------------------------------------------------------------------------
First, install the provider package in your project using:
### Set up private registry
Note that you need to follow the instructions [here](https://cloud.tiptap.dev/pro-extensions)
to set up access to the private registry.
npm install @tiptap-pro/provider
For a basic setup, connect to the Collaboration backend by specifying the document's name, your document server ID (for cloud setups), or the base URL (for on-premises), along with your JWT.
Depending on your framework, register a callback to the Collaboration backend, such as `useEffect()` in React or `onMounted()` in Vue.js.
const doc = new Y.Doc()
useEffect(() => {
const provider = new TiptapCollabProvider({
name: note.id, // Document identifier
appId: 'YOUR_APP_ID', // replace with YOUR_APP_ID from Cloud dashboard
token: 'YOUR_JWT', // Authentication token
document: doc,
user: userId,
})
}, [])
### Note for On-Premises Customers
If you are hosting your collaboration environment on-premises, replace the `appId` parameter with `baseUrl` in your provider configuration to connect to your server.
[](https://tiptap.dev/docs/collaboration/provider/integration#configure-the-collaboration-provider)
Configure the collaboration provider
----------------------------------------------------------------------------------------------------------------------------------------
The Tiptap Collaboration provider offers several settings for custom configurations. Review the tables below for all parameters, practical use cases, and key concepts like "[awareness](https://tiptap.dev/docs/collaboration/core-concepts/awareness)
".
| Setting | Default Value | Description |
| --- | --- | --- |
| `appId` | `''` (empty) | Document server ID for Collaboration Cloud setups |
| `baseUrl` | `''` (empty) | URL for connecting to on-premises servers. Used as an alternative to `appId` for on-prem setups |
| `shardKey` | `''` (empty) | Only use this if you use Tiptap Collab HA. Usually this would then be set to the document name, or a team id |
| `name` | `''` (empty) | The document's name |
| `token` | `''` (empty) | Authentication token for secure connections. Supports strings, functions, and Promises |
| `document` | `new Y.Doc()` | The Yjs document instance. Defaults to a new document if none is provided |
| `user` | `null` | User ID or name for attributing changes to the document. |
| `forceSyncInterval` | `false` | Forces server sync at regular intervals, in milliseconds |
### [](https://tiptap.dev/docs/collaboration/provider/integration#optimize-reconnection-timings)
Optimize reconnection timings
The provider’s reconnection settings are preset for optimal performance in production settings. If you need to adjust these settings for specific scenarios, you can do so with our delay configurations.
Adjust initial delays, apply exponential backoff, or set maximum wait times to fine-tune your application's reconnection behavior, balancing responsiveness with server efficiency.
Note that these settings can only be configured when creating the TiptapCollabProviderWebsocket instance separately. You'll then need to pass it to the TiptapCollabProvider (as `websocketProvider`).
| Setting | Default Value | Description |
| --- | --- | --- |
| `delay` | `1000` | Base delay between reconnection attempts, in milliseconds |
| `factor` | `2` | Multiplier applied to the delay, increasing it exponentially after each attempt |
| `initialDelay` | `0` | Time in milliseconds before the first reconnection attempt |
| `maxAttempts` | `0` | Maximum number of reconnection attempts. `0` means unlimited attempts |
| `jitter` | `true` | Adds variability to the delay by randomly selecting a value between `minDelay` and the calculated delay for each attempt |
| `minDelay` | `1000` | Minimum delay when jitter is enabled. Has no effect if jitter is disabled |
| `maxDelay` | `30000` | Maximum delay allowed between reconnection attempts. Set to `0` to allow the delay to increase indefinitely using exponential backoff (`factor`). |
| `timeout` | `0` | Time limit, in milliseconds, for each reconnection attempt before stopping |
| `messageReconnectTimeout` | `30000` | Time in milliseconds to wait for a server message before terminating the connection. If no message is received, the connection is closed automatically |
[](https://tiptap.dev/docs/collaboration/provider/integration#unsynced-changes)
Unsynced changes
------------------------------------------------------------------------------------------------
The provider maintains an integer that keeps track of the number of unsynced changes. Whenever the server receives a change, it acknowledges it, and the provider decrements the counter.
You should monitor this counter and inform the user when their changes are not yet synced: either before they leave the page or after a certain timeout or number of unsynced changes.
A "change" may be a single character, a single node, or a whole document, depending on your custom use-case, so in general the counter should be at `0` or slightly above, but only for a short period of time (essentially the latency of the connection).
You can get the current value of the counter by accessing `TiptapCollabProvider.unsyncedChanges`, or by subscribing to the `unsyncedChanges` event.
TiptapCollabProvider.on('unsyncedChanges', n => console.log(`${n.number} unsynced changes`))
[](https://tiptap.dev/docs/collaboration/provider/integration#rate-limits)
Rate limits
--------------------------------------------------------------------------------------
To maintain system integrity and protect from misconfigured clients, our infrastructure—including the management API and websocket connections through the `TiptapCollabProvider`—is subject to rate limits.
### [](https://tiptap.dev/docs/collaboration/provider/integration#default-rate-limits-per-source-ip)
Default rate limits (per source IP):
* **Requests:** 100
* **Time window:** 5 seconds
* **Burst capacity:** Up to 200 requests
If you encounter these limits under normal operation, please [email us](mailto:humans@tiptap.dev)
.
[PreviouslyAuthenticate](https://tiptap.dev/docs/collaboration/getting-started/authenticate)
[Next upEvents](https://tiptap.dev/docs/collaboration/provider/events)
---
# Provider events | Tiptap Collaboration Docs
Events in Collaboration providers let you respond to various states and changes, such as successful connections or authentication updates. You can attach event listeners during provider initialization or add them later based on your application's needs.
[](https://tiptap.dev/docs/collaboration/provider/events#use-provider-events)
Use provider events
-------------------------------------------------------------------------------------------------
| Event | Description |
| --- | --- |
| `open` | Triggered when the WebSocket connection opens. |
| `connect` | Triggered when the provider connects to the server. |
| `authenticated` | Indicates successful client authentication. |
| `authenticationFailed` | Triggered when client authentication fails. |
| `status` | Tracks changes in connection status. |
| `close` | Triggered when the WebSocket connection closes. |
| `disconnect` | Triggered when the provider disconnects. |
| `destroy` | Signifies the impending destruction of the provider. |
| `message` | Triggered by incoming messages. |
| `outgoingMessage` | Triggered before a message is sent. |
| `synced` | Indicates the initial successful sync of the Yjs document. |
| `stateless` | Triggered when the stateless message is received. |
| `awarenessUpdate` | Triggered when user awareness information updates. |
| `awarenessChange` | Triggered when the awareness state changes. |
[](https://tiptap.dev/docs/collaboration/provider/events#configure-event-listeners)
Configure event listeners
-------------------------------------------------------------------------------------------------------------
To track events immediately, pass event listeners directly to the provider's constructor. This guarantees that listeners are active from the start.
const provider = new TiptapCollabProvider({
appId: '', // Use for cloud setups, replace with baseUrl in case of on-prem
name: 'example-document', // Document identifier
token: '', // Your authentication JWT
document: ydoc,
onOpen() {
console.log('WebSocket connection opened.')
},
onConnect() {
console.log('Connected to the server.')
},
// See below for more event listeners...
})
### [](https://tiptap.dev/docs/collaboration/provider/events#bind-events-dynamically)
Bind events dynamically
To add or remove listeners after initialization, the provider supports dynamic binding and unbinding of event handlers.
**Example:** Binding event listeners during provider initialization
const provider = new TiptapCollabProvider({
// …
})
provider.on('synced', () => {
console.log('Document synced.')
})
**Example:** Binding/unbinding event listeners after provider initialization
const onMessage = () => {
console.log('New message received.')
}
// Binding
provider.on('message', onMessage)
// Unbinding
provider.off('message', onMessage)
[](https://tiptap.dev/docs/collaboration/provider/events#provider-event-examples)
Provider event examples
---------------------------------------------------------------------------------------------------------
### [](https://tiptap.dev/docs/collaboration/provider/events#display-connection-status)
Display connection status
Use `onConnect` and `onDisconnect` to provide users with real-time connection status feedback, enhancing the user experience.
provider.on('connect', () => {
showStatus('Connected')
})
provider.on('disconnect', () => {
showStatus('Disconnected')
})
### [](https://tiptap.dev/docs/collaboration/provider/events#sync-document-status)
Sync document status
Use `synced` to alert users when the document is fully synced initially, ensuring they start working with the latest version.
provider.on('synced', () => {
alert('Document initialized')
})
### [](https://tiptap.dev/docs/collaboration/provider/events#handle-authentication-issues)
Handle authentication issues
Use `authenticationFailed` to catch authentication errors and prompt users to reauthenticate, ensuring secure access.
provider.on('authenticationFailed', ({ reason }) => {
console.error('Authentication failed:', reason)
requestUserReauthentication()
})
[PreviouslyIntegration](https://tiptap.dev/docs/collaboration/provider/integration)
[Next upAwareness](https://tiptap.dev/docs/collaboration/core-concepts/awareness)
---
# Snapshot Compare extension | Tiptap Editor Docs
Snapshot Compare lets you line-up two document versions and highlights everything that changed. Use it to track edits, review contributions, and restore earlier states.
The Snapshot Compare extension adds extra functionality to the [Snapshots](https://tiptap.dev/docs/collaboration/documents/snapshot)
by allowing you to visually compare changes made between two versions of a document so you can track what’s been added, removed, or modified. These comparisons are called _diffs_.
[](https://tiptap.dev/docs/collaboration/documents/snapshot-compare#access-the-private-registry)
Access the private registry
----------------------------------------------------------------------------------------------------------------------------
The Snapshot Compare extension is published in Tiptap’s private npm registry. Integrate the extension by following the [private registry guide](https://tiptap.dev/docs/guides/pro-extensions)
. If you already authenticated your Tiptap account you can go straight to [#Install](https://tiptap.dev/docs/collaboration/documents/snapshot-compare#install)
.
[](https://tiptap.dev/docs/collaboration/documents/snapshot-compare#install)
Install
------------------------------------------------------------------------------------
Install the extension from our private registry:
npm install @tiptap-pro/extension-snapshot-compare
[](https://tiptap.dev/docs/collaboration/documents/snapshot-compare#settings)
Settings
--------------------------------------------------------------------------------------
You can configure the `SnapshotCompare` extension with the following options:
| Setting | Type | Default | Description |
| --- | --- | --- | --- |
| provider | `TiptapCollabProvider` | `null` | The Collaboration provider instance |
| mapDiffToDecorations | `function` | `() => {}` | Control mapping a diff into a decoration to display their content |
Note that you need to provide the `user` identifier to the `TiptapCollabProvider`, as this information is used to optimize diffs.
const provider = new TiptapCollabProvider({
// ...
user: 'your user identifier' // REQUIRED! Note that we use the user identifier to optimize diffs, so it's important to provide it.
})
const editor = new Editor({
// ...
extensions: [\
// ...\
SnapshotCompare.configure({\
provider,\
}),\
],
})
#### [](https://tiptap.dev/docs/collaboration/documents/snapshot-compare#using-mapdifftodecorations-for-diff-decorations)
Using `mapDiffToDecorations` for diff decorations
The extension has a default mapping (`defaultMapDiffToDecorations`) to represent diffs as ProseMirror decorations. For more complex integrations and control, you can customize this mapping with the `mapDiffToDecorations` option.
**Example:** Applying custom predefined background colors to inline inserts
SnapshotCompare.configure({
mapDiffToDecorations: ({ diff, tr, editor, defaultMapDiffToDecorations }) => {
if (diff.type === 'inline-insert') {
// return ProseMirror decoration(s) or null
return Decoration.inline(
diff.from,
diff.to,
{
class: 'diff',
style: {
backgroundColor: diff.attribution.color.backgroundColor,
},
},
// pass the diff as the decoration's spec, this is required for `extractAttributeChanges`
{ diff },
)
}
// fallback to the default mapping
return defaultMapDiffToDecorations({
diff,
tr,
editor,
attributes: {
// add custom attributes to the decorations
'data-tiptap-user-id': myUserIdMapping[diff.attribution.userId],
},
})
},
})
[](https://tiptap.dev/docs/collaboration/documents/snapshot-compare#storage)
Storage
------------------------------------------------------------------------------------
The `SnapshotCompare` storage object contains the following properties:
| Key | Type | Description |
| --- | --- | --- |
| isPreviewing | `boolean` | Indicates whether the diff view is active |
| diffs | `Diff[]` | The diffs that are displayed in the diff view |
| previousContent | `JSONContent \| null` | The content before the diff view was applied |
Use the `isPreviewing` property to check if the diff view is currently active:
if (editor.storage.snapshotCompare.isPreviewing) {
// The diff view is currently active
}
Use the `diffs` property to access the diffs displayed in the diff view:
editor.storage.snapshotCompare.diffs
The property `previousContent` is used internally by the extension to restore the content when exiting the diff view. Typically, you do not need to interact with it directly.
[](https://tiptap.dev/docs/collaboration/documents/snapshot-compare#commands)
Commands
--------------------------------------------------------------------------------------
| Command | Description |
| --- | --- |
| `compareVersions` | Diffs two versions and renders the diff within the editor |
| `showDiff` | Given a change tracking transform, show the diff within the editor |
| `hideDiff` | Hide the diff and restore the previous content |
### [](https://tiptap.dev/docs/collaboration/documents/snapshot-compare#compareversions)
compareVersions
Use the `compareVersions` command to compute and display the differences between two document versions.
editor.chain().compareVersions({
fromVersion: 1,
})
#### [](https://tiptap.dev/docs/collaboration/documents/snapshot-compare#options)
Options
You can pass in additional options for more control over the diffing process:
| Key | Description |
| --- | --- |
| `fromVersion` | The version to compare from. The order between `fromVersion` and `toVersion` is flexible |
| `toVersion` | The version to compare to (default: latest version) |
| `hydrateUserData` | Add contextual data to each user's changes |
| `onCompare` | Handle the diffing result manually |
| `enableDebugging` | Enable verbose logging for troubleshooting |
#### [](https://tiptap.dev/docs/collaboration/documents/snapshot-compare#using-hydrateuserdata-to-add-metadata)
Using `hydrateUserData` to add metadata
Each diff has an `attribution` field, which allows you to add additional metadata with the `hydrateUserData` callback function.
### Note
Do note that the `userId` is populated by the `TiptapCollabProvider` and should be used to identify the user who made the change. Without the `user` field provided by the provider, the `userId` will be `null`. [See more information in the TiptapCollabProvider documentation](https://tiptap.dev/docs/collaboration/provider/integration)
.
**Example:** Color-coding diffs based on user
const colorMapping = new Map([\
['user-1', '#ff0000'],\
['user-2', '#00ff00'],\
['user-3', '#0000ff'],\
])
editor.chain().compareVersions({
fromVersion: 1,
toVersion: 3,
hydrateUserData: ({ userId }) => {
return {
color: {
backgroundColor: colorMapping.get(userId),
},
}
},
})
editor.storage.snapshotCompare.diffs[0].attribution.color.backgroundColor // '#ff0000'
#### [](https://tiptap.dev/docs/collaboration/documents/snapshot-compare#using-oncompare-to-customize-the-diffing-process)
Using `onCompare` to customize the diffing process
If you need more control over the diffing process, use the `onCompare` option to receive the result and handle it yourself.
**Example:** Filtering diffs by user
editor.chain().compareVersions({
fromVersion: 1,
toVersion: 3,
onCompare: (ctx) => {
if (ctx.error) {
// handle errors that occurred in the diffing process
console.error(ctx.error)
return
}
// filter the diffs to display only the changes made by a specific user
const diffsToDisplay = ctx.diffSet.filter((diff) => diff.attribution.userId === 'user-1')
editor.commands.showDiff(ctx.tr, { diffs: diffsToDisplay })
},
})
### [](https://tiptap.dev/docs/collaboration/documents/snapshot-compare#showdiff)
showDiff
Use the `showDiff` command to display the diff within the editor using a change tracking transform (`tr`). This represents all of the changes made to the document since the last snapshot. You can use this transform to show the diff in the editor.
Typically, you use this command after customizing or filtering diffs with `compareVersions`, `onCompare`.
The `showDiff` command temporarily replaces the current editor content with the diff view, showing the differences between versions. It also stashes the content currently displayed in editor so that you can restore it later.
// This will display the changes that change tracking transform recorded in the editor
editor.commands.showDiff(tr)
### [](https://tiptap.dev/docs/collaboration/documents/snapshot-compare#options)
Options
You can pass additional options to control how the diffs are displayed:
| Key | Description |
| --- | --- |
| diffs | An array of diffs to visualize |
**Example:** Displaying specific diffs
// This will display only the diffs made by the user with the ID 'user-1'
const diffsToDisplay = tr.toDiff().filter((diff) => diff.attribution.userId === 'user-1')
editor.commands.showDiff(tr, { diffs: diffsToDisplay })
### [](https://tiptap.dev/docs/collaboration/documents/snapshot-compare#hidediff)
hideDiff
Use the `hideDiff` command to hide the diff and restore the previous content.
// This will hide the diff view and restore the previous content
editor.commands.hideDiff()
[](https://tiptap.dev/docs/collaboration/documents/snapshot-compare#add-styles)
Add styles
------------------------------------------------------------------------------------------
The Snapshot Compare extension applies classes to the elements of the diff view to help you style inserted and deleted text. See a complete example of how to style the diff view in the [code demo](https://tiptap.dev/docs/collaboration/documents/snapshot-compare#page-title)
at the top of this page.
### [](https://tiptap.dev/docs/collaboration/documents/snapshot-compare#basic-diff-styling)
Basic diff styling
The extension applies the following attributes to diff elements:
* `data-diff-type="inline-insert"` - for inserted text
* `data-diff-type="inline-delete"` - for deleted text
* `data-diff-type="inline-update"` - for updated text
* `data-diff-type="block-insert"` - for inserted blocks
* `data-diff-type="block-delete"` - for deleted blocks
You can target the elements with these data attributes by using CSS selectors.
Here's an example of basic styling for inserted and deleted text:
/* When there is no user involved, fallback to red and green colors */
/* Style inserted text */
[data-diff-type='inline-insert'],
[data-diff-type='inline-update'],
[data-diff-type='block-insert'] {
background-color: green;
}
/* Style deleted text */
[data-diff-type='inline-delete'],
[data-diff-type='block-delete'] {
background-color: red;
text-decoration: line-through;
}
### [](https://tiptap.dev/docs/collaboration/documents/snapshot-compare#resetting-mark-styles-for-deleted-text)
Resetting mark styles for deleted text
When styling marks are applied (like **bold**, _italic_, or `code`), the deleted text (the text before the formatting was applied) can have that formatting applied to it. This issue occurs because the deleted text appears inside the HTML tag of the formatting. To prevent this, reset the mark styles inside the deleted content.
First, reset the outer mark styles inside the deleted content.
/* Reset strong/bold formatting for deleted content */
strong {
[data-diff-type='inline-delete'],
[data-diff-type='block-delete'] {
font-weight: normal;
}
}
/* Reset italic formatting for deleted content */
em {
[data-diff-type='inline-delete'],
[data-diff-type='block-delete'] {
font-style: normal;
}
}
/* Reset code formatting for deleted content */
code {
[data-diff-type='inline-delete'],
[data-diff-type='block-delete'] {
font-family: sans-serif;
}
}
Then, if there is a mark tag inside the deleted content, re-apply the mark styles to it. This will ensure that the formatting is correctly applied to the deleted content.
/* Ensure mark styles work properly inside the deleted content */
[data-diff-type='inline-delete'],
[data-diff-type='block-delete'] {
strong {
font-weight: bold;
}
em {
font-style: italic;
}
code {
font-family: monospace;
}
}
### [](https://tiptap.dev/docs/collaboration/documents/snapshot-compare#user-attribution-styling)
User attribution styling
When user attribution is available, you can style diffs based on the user who made the change:
/* Style diffs with user attribution */
[data-diff-user-id] {
position: relative;
}
/* Tooltip showing user name */
[data-diff-user-id]::before {
content: attr(data-diff-user-id);
position: absolute;
visibility: hidden;
}
[data-diff-user-id]:hover::before {
visibility: visible;
}
[](https://tiptap.dev/docs/collaboration/documents/snapshot-compare#working-with-nodeview-advanced)
Working with NodeView (advanced)
------------------------------------------------------------------------------------------------------------------------------------
When using [custom node views](https://tiptap.dev/docs/editor/extensions/custom-extensions/node-views)
, the default diff mapping may not work as expected. You can customize the mapping and render the diffs directly within the custom node view.
Use the `extractAttributeChanges` helper to extract attribute changes in nodes. This allows you to access the previous and current attributes of a node, making it possible to highlight attribute changes within your custom node views.
### Note
When mapping the diffs into decorations yourself, you need to pass the `diff` as the decoration's `spec`. This is required for `extractAttributeChanges` to work correctly.
**Example:** Customizing a heading node view to display changes
import { extractAttributeChanges } from '@tiptap-pro/extension-snapshot-compare'
const Heading = BaseHeading.extend({
addNodeView() {
return ReactNodeViewRenderer(({ node, decorations }) => {
const { before, after, isDiffing } = extractAttributeChanges(decorations)
return (
{isDiffing && before.level !== after.level && (
#{before.level}
{after.level}
)}
)
})
},
})
[](https://tiptap.dev/docs/collaboration/documents/snapshot-compare#technical-details)
Technical details
--------------------------------------------------------------------------------------------------------
### [](https://tiptap.dev/docs/collaboration/documents/snapshot-compare#diff)
Diff
A `Diff` is an object that represents a change made to the document. It contains the following properties:
| Property | Type | Description |
| --- | --- | --- |
| `type` | `'inline-insert'` \| `'inline-delete'` \| `'block-insert'` \| `'block-delete'` \| `'inline-update'` \| `'block-update'` | The type of change made |
| `from` | `number` | The start position of the change |
| `to` | `number` | The end position of the change |
| `content` | `Fragment` | The content that was added or removed |
| `attribution` | `Attribution` | Metadata about the change, such as the user who made the change |
| `attributes?` | `Record` | The attributes **after** the change; only available for `'inline-update'` and `'block-update'` diffs |
| `previousAttributes?` | `Record` | The attributes **before** the change; only available for `'inline-update'` and `'block-update'` diffs |
### [](https://tiptap.dev/docs/collaboration/documents/snapshot-compare#diffset)
DiffSet
A `DiffSet` is an array of `Diff` objects, each corresponding to a specific change, like insertion, deletion, or update. You can iterate over the array to inspect individual changes or apply custom logic based on the diff types.
const diffsToDisplay = diffSet.filter((diff) => diff.attribution.userId === 'user-1')
// Show the filtered diffs in the editor
editor.commands.showDiff(tr, { diffs: diffsToDisplay })
### [](https://tiptap.dev/docs/collaboration/documents/snapshot-compare#attribution)
Attribution
The `Attribution` object contains metadata about a change. It includes the following properties:
| Property | Type | Description |
| --- | --- | --- |
| `type` | `'added'` \| `'removed'` | Indicates the type of change made |
| `userId` | `string` \| `undefined` | The ID of the user who made the change |
| `id` | `Y.ID` \| `undefined` | The Y.js client ID of the user who made the change |
You can extend the `Attribution` interface to include additional properties:
declare module '@tiptap-pro/extension-snapshot-compare' {
interface Attribution {
userName: string
}
}
### [](https://tiptap.dev/docs/collaboration/documents/snapshot-compare#changetrackingtransform)
ChangeTrackingTransform
The `ChangeTrackingTransform` is a class that records changes made to the document (based on ProseMirror's `Transform`). It represents a transform whose steps describe all of the changes made to go from one version of the document to another. It has the following properties:
| Property | Type | Description |
| --- | --- | --- |
| `steps` | `ChangeTrackingStep[]` | An array of steps that represent the changes made to the document |
| `doc` | `Node` | The document **after** the changes have been applied |
| `before` | `Node` | The document **before** the changes have been applied |
### [](https://tiptap.dev/docs/collaboration/documents/snapshot-compare#changetrackingstep)
ChangeTrackingStep
The `ChangeTrackingStep` is a class that represents a single change made to the document, based on ProseMirror's `ReplaceStep` class. It has the following property:
| Property | Type | Description |
| --- | --- | --- |
| `attribution` | `Attribution` | Metadata about the change, such as the user who made it |
### [](https://tiptap.dev/docs/collaboration/documents/snapshot-compare#types)
Types
Here is the full TypeScript definition for the `SnapshotCompare` extension:
declare module '@tiptap/core' {
interface Commands {
snapshotCompare: {
/**
* Given a change tracking transform, show the diff within the editor
*/
showDiff: (tr: ChangeTrackingTransform, options?: { diffs?: DiffSet }) => ReturnType
/**
* Hide the diff and restore the previous content
*/
hideDiff: () => ReturnType
/**
* Diffs two versions and renders the diff into the editor
*/
compareVersions: <
T extends Pick & Record = Pick &
Record,
>(options: {
/**
* The version to start the diff from
*/
fromVersion: number
/**
* The version to end the diff at
* If not provided, the latest snapshot will be used
*/
toVersion?: number
/**
* Allows adding contextual data to each users changes
*/
hydrateUserData?: (context: {
/**
* The type of event
*/
type: 'added' | 'removed'
/**
* The userId that manipulated this content
*/
userId: string | undefined
/**
* The yjs identifier of the content
*/
id?: y.ID
}) => T
/**
* If provided, allows customizing the behavior of rendering the diffs to the editor.
* @note The default behavior would be to just display the diff immediately.
*/
onCompare?: (
context:
| {
error?: undefined
/**
* The editor instance
*/
editor: Editor
/**
* All of the changes as a transform with attribution metadata
*/
tr: ChangeTrackingTransform
/**
* The changes represented as an array of diffs
*/
diffSet: DiffSet
}
| {
error: Error
},
) => void
/**
* Verbosely log the diffing process to help track down where things went wrong
*/
enableDebugging?: boolean
}) => ReturnType
}
}
}
export type SnapshotCompareOptions = {
/**
* The Tiptap provider instance. This is required for the extension to compute the diffs, but not to display them.
* It is also possible to pass a TiptapCollabProvider instance.
*/
provider: TiptapCollabProvider | null
/**
* This allows you to control mapping of a diff into a decoration to display the content of that diff
*/
mapDiffToDecorations?: (options: {
/**
* The diff to map to a decoration
*/
diff: Diff
/**
* The editor instance
*/
editor: Editor
/**
* The change tracking transform
*/
tr: ChangeTrackingTransform
/**
* The default implementation of how to map a diff to a decoration
*/
defaultMapDiffToDecorations: typeof defaultMapDiffToDecorations
}) => ReturnType
}
export type SnapshotCompareStorageInactive = {
/**
* Whether the diff view is currently active
*/
isPreviewing: false
/**
* The content before the diff view was applied
*/
previousContent: null
/**
* The change tracking transform that was applied
* It is currently empty because the diff view is not active
*/
diffs: DiffSet
/**
* The change tracking transform that was applied
*/
tr: null
}
export type SnapshotCompareStorageActive = {
/**
* Whether the diff view is currently active
*/
isPreviewing: true
/**
* The content before the diff view was applied
*/
previousContent: JSONContent
/**
* The change tracking transform that was applied
*/
diffs: DiffSet
/**
* The change tracking transform that was applied
*/
tr: ChangeTrackingTransform
}
export type SnapshotCompareStorage = SnapshotCompareStorageInactive | SnapshotCompareStorageActive
[PreviouslySnapshots](https://tiptap.dev/docs/collaboration/documents/snapshot)
[Next upInject content](https://tiptap.dev/docs/collaboration/documents/content-injection)
---
# Install Collaboration | Tiptap Collaboration Docs
This guide will get you started with collaborative editing in the Tiptap Editor. If you're already using Tiptap Editor, feel free to skip ahead to [Prepare your editor](https://tiptap.dev/docs/collaboration/getting-started/install#prepare-your-editor)
section.
### [](https://tiptap.dev/docs/collaboration/getting-started/install#install-tiptap-editor)
Install Tiptap Editor
If Tiptap Editor isn't installed yet, run the following command in your CLI for React to install the basic editor and necessary extensions for this example:
npm install @tiptap/extension-document @tiptap/extension-paragraph @tiptap/extension-text @tiptap/react
Once installed, you can get your Tiptap Editor up and running with this basic setup. Just add the following code snippets to your project:
[](https://tiptap.dev/docs/collaboration/getting-started/install#prepare-your-editor)
Prepare your editor
---------------------------------------------------------------------------------------------------------
To introduce team collaboration features into your Tiptap Editor, integrate the Yjs library and Editor Collaboration extension into your frontend. This setup uses Y.Doc, a shared document model, rather than just handling plain text. Afterwards we will connect Y.Doc to the TiptapCollabProvider to synchronize user interactions.
### [](https://tiptap.dev/docs/collaboration/getting-started/install#integrate-yjs-and-the-collaboration-extension)
Integrate Yjs and the Collaboration Extension
Add the Editor Collaboration extension and Yjs library to your frontend:
npm install @tiptap/extension-collaboration @tiptap/y-tiptap yjs y-protocols
Then, update your index.jsx to include these new imports:
import './styles.scss'
import Document from '@tiptap/extension-document'
import Paragraph from '@tiptap/extension-paragraph'
import Text from '@tiptap/extension-text'
import { EditorContent, useEditor } from '@tiptap/react'
import React from 'react'
import Collaboration from '@tiptap/extension-collaboration'
import * as Y from 'yjs'
const doc = new Y.Doc() // Initialize Y.Doc for shared editing
export default () => {
const editor = useEditor({
extensions: [\
Document,\
Paragraph,\
Text,\
Collaboration.configure({\
document: doc, // Configure Y.Doc for collaboration\
}),\
],
content: `
This is a radically reduced version of Tiptap. It has support for a document, with paragraphs and text. That’s it. It’s probably too much for real minimalists though.
The paragraph extension is not really required, but you need at least one node. Sure, that node can be something different.
`,
})
return
}
Your editor is now almost prepared for collaborative editing!
[](https://tiptap.dev/docs/collaboration/getting-started/install#start-your-document-server)
Start your Document server
-----------------------------------------------------------------------------------------------------------------------
1. **Activate a plan:** Begin a [free trial or choose a subscription](https://cloud.tiptap.dev/v2/billing)
.
2. **Add an environment.** On your [Dashboard](https://cloud.tiptap.dev/)
, click the `Add environment` button, enter a name and pick the region that is closest to your users.
3. **Check the configuration.** As soon as you save the environment, your document server boots up. Visit the [configuration page](https://cloud.tiptap.dev/v2/configuration/document-server)
to copy your document server ID, API keys, and other connection details.
### [](https://tiptap.dev/docs/collaboration/getting-started/install#connect-to-your-document-server)
Connect to your Document server
For collaborative functionality, install the `@tiptap-pro/provider` package:
### Set up private registry
Note that you need to follow the instructions [here](https://cloud.tiptap.dev/pro-extensions)
to set up access to the private registry.
npm install @tiptap-pro/provider
Next, configure the provider in your index.jsx file with your server details:
* **name**: Serves as the document identifier for synchronization.
* **appID**: Found in your [Cloud account](https://cloud.tiptap.dev/v2/configuration/document-server)
after you started your document server. For on-premises setups replace `appID` with `baseUrl`.
* **token**: Use the JWT from your [Cloud interface](https://cloud.tiptap.dev/v2/configuration/document-server)
for testing, but generate your own JWT for production.
### Adding initial content
When integrating the Editor in a non-collaborative setting, using the method shown here to set content is perfectly acceptable. However, if you transition to a collaborative environment, you will need to modify how you add initial content as shown after the next headline.
Incorporate the following code to complete the setup:
import './styles.scss'
import Document from '@tiptap/extension-document'
import Paragraph from '@tiptap/extension-paragraph'
import Text from '@tiptap/extension-text'
import { EditorContent, useEditor } from '@tiptap/react'
import React from 'react'
import Collaboration from '@tiptap/extension-collaboration'
import * as Y from 'yjs'
// Importing the provider and useEffect
import { useEffect } from 'react'
import { TiptapCollabProvider } from '@tiptap-pro/provider'
const doc = new Y.Doc()
export default () => {
const editor = useEditor({
extensions: [\
Document,\
Paragraph,\
Text,\
Collaboration.configure({\
document: doc,\
}),\
],
content: `
This is a radically reduced version of Tiptap. It has support for a document, with paragraphs and text. That’s it. It’s probably too much for real minimalists though.
The paragraph extension is not really required, but you need at least one node. Sure, that node can be something different.
`,
})
// Connect to your Collaboration server
useEffect(() => {
const provider = new TiptapCollabProvider({
name: 'document.name', // Unique document identifier for syncing. This is your document name.
appId: '7j9y6m10', // Your Cloud Dashboard AppID or `baseURL` for on-premises
token: 'notoken', // Your JWT
document: doc,
})
}, [])
return
}
After following these steps, you should be able to open two different browsers and connect to the same document simultaneously through separate WebSocket connections.
For a clear test of the collaboration features, using two different browsers is recommended to guarantee unique websocket connections.
### [](https://tiptap.dev/docs/collaboration/getting-started/install#initialize-content-properly)
Initialize Content Properly
Upon implementing collaboration in your Tiptap Editor, you might notice that the initial content is repeatedly added each time the editor loads. To prevent this, use the `.setContent()` method to set the initial content only once.
import './styles.scss'
import Document from '@tiptap/extension-document'
import Paragraph from '@tiptap/extension-paragraph'
import Text from '@tiptap/extension-text'
import { EditorContent, useEditor } from '@tiptap/react'
import React from 'react'
import * as Y from 'yjs'
import Collaboration from '@tiptap/extension-collaboration'
import { useEffect } from 'react'
import { TiptapCollabProvider } from '@tiptap-pro/provider'
const doc = new Y.Doc()
export default () => {
const editor = useEditor({
extensions: [\
Document,\
Paragraph,\
Text,\
Collaboration.configure({\
document: doc,\
}),\
],
// Remove the automatic content addition on editor initialization.
})
useEffect(() => {
const provider = new TiptapCollabProvider({
name: 'document.name', // Unique document identifier for syncing. This is your document name.
appId: '7j9y6m10', // Your Cloud Dashboard AppID or `baseURL` for on-premises
token: 'notoken', // Your JWT
document: doc,
// The onSynced callback ensures initial content is set only once using editor.setContent(), preventing repetitive content loading on editor syncs.
onSynced() {
if (!doc.getMap('config').get('initialContentLoaded') && editor) {
doc.getMap('config').set('initialContentLoaded', true)
editor.commands.setContent(`
This is a radically reduced version of Tiptap. It has support for a document, with paragraphs and text. That’s it. It’s probably too much for real minimalists though.
The paragraph extension is not really required, but you need at least one node. Sure, that node can be something different.
`)
}
},
})
}, [])
return
}
This ensures the initial content is set only once. To test with new initial content, create a new document by changing the `name` parameter (e.g., from `document.name` to `document.name2`).
[](https://tiptap.dev/docs/collaboration/getting-started/install#disable-default-undoredo)
Disable Default Undo/Redo
--------------------------------------------------------------------------------------------------------------------
If you're integrating collaboration into an editor **other than the one provided in this demo**, you may need to disable the default Undo/Redo function of your Editor. This is necessary to avoid conflicts with the collaborative history management: You wouldn't want to revert someone else's changes.
This action is only required if your project includes the Tiptap [StarterKit](https://tiptap.dev/docs/editor/extensions/functionality/starterkit)
or [Undo/Redo](https://tiptap.dev/docs/editor/extensions/functionality/undo-redo)
extension.
const editor = useEditor({
extensions: [\
StarterKit.configure({\
undoRedo: false, // Disables default Undo/Redo extension to use Collaboration's history management\
}),\
],
})
Following this guide will set up a basic, yet functional collaborative Tiptap Editor, synchronized through either the Collaboration Cloud or an on-premises backend.
[](https://tiptap.dev/docs/collaboration/getting-started/install#authenticate-your-users)
Authenticate your users
-----------------------------------------------------------------------------------------------------------------
Learn how to secure your collaborative Tiptap editor with JSON Web Tokens (JWTs). The next guide provides step-by-step instructions on creating and managing JWTs for both testing and production, ensuring controlled access with detailed examples. Read more about [authentication](https://tiptap.dev/docs/collaboration/getting-started/authenticate)
.
[PreviouslyOverview](https://tiptap.dev/docs/collaboration/getting-started/overview)
[Next upAuthenticate](https://tiptap.dev/docs/collaboration/getting-started/authenticate)
---
# Manage threads | Tiptap Comments Docs
Use this guide to integrate comments directly into your editor. For a complete list of all Comments editor commands, see the [Editor Commands](https://tiptap.dev/docs/comments/integrate/editor-commands)
page.
You can also interact with comments from outside your editor via our [Comments REST API](https://tiptap.dev/docs/comments/integrate/rest-api)
.
[](https://tiptap.dev/docs/comments/core-concepts/manage-threads#learn-about-threads)
Learn about threads
---------------------------------------------------------------------------------------------------------
Tiptap's Comments feature organizes discussions into threads, enabling clear and context-relevant collaboration by distinguishing between threads and individual comments.
Threads serve as containers for discussions related to specific document sections, while comments represent individual contributions within those threads.
[](https://tiptap.dev/docs/comments/core-concepts/manage-threads#create-a-new-thread)
Create a new thread
---------------------------------------------------------------------------------------------------------
Let's assume you have a button to create a new thread. You can use the `setThread` command to create a new thread at the current selection.
const createThread = () => {
editor
.chain()
.setThread({
content: 'This is a new thread', // the content of the threads first inital comment
})
.run()
}
This will create a new thread at the current selection and add a comment with the given content. By default comments and threads don't have a user or any other metadata assigned. Let's say you want to add the author to the thread **and** the comment. You can do this by passing through the `data` and `commentData` property to the `setThread` command.
const createThread = () => {
const user = {
id: '123', // the user id of the author
name: 'John Doe', // the name of the author
avatarUrl: 'https://example.com/avatar.jpg', // the avatar of the author
}
editor
.chain()
.setThread({
content: 'This is a new thread', // the content of the threads first inital comment
data: {
user,
},
commentData: {
user,
},
})
.run()
}
Now the thread and comment will have a user assigned to it.
[](https://tiptap.dev/docs/comments/core-concepts/manage-threads#receive-and-watch-threads-and-comments)
Receive and watch threads and comments
-----------------------------------------------------------------------------------------------------------------------------------------------
Receiving and watching the threads on your current document can easily be done by using the `subscribeToThreads` function. This function will register a watcher, fetch the first initial list of threads and keep the list up to date.
To unsubscribe from the threads, you can call the callback function returned by the `subscribeToThreads` function.
// Subscribe to threads
const unsubscribe = subscribeToThreads({
provider: yourTiptapCollabProvider,
callback: (threads) => {
// do something with threads, store in a state or variable from here
},
// optional options
getThreadsOptions: {
// only threads with the specific type will be fetched/watched, possible values are 'archived' and 'unarchived',
// if not set, only unarchived threads will be handled
// archived and unarchived threads represent soft-deleted threads
types: ['archived', 'unarchived'],
},
})
[](https://tiptap.dev/docs/comments/core-concepts/manage-threads#receive-and-render-threads-manually)
Receive and render threads manually
-----------------------------------------------------------------------------------------------------------------------------------------
To receive the list of threads on your current document manually, you can simply call `provider.getThreads()`. This will return an array of threads on the document connected to your provider.
This is a static array which won't update on its own. If you want to keep the list of threads up to date, you can listen to changes via the `provider.watchThreads` and `provider.unwatchThreads` functions.
// let's save threads in a variable
let threads = []
// this function is called whenever the threads change
const getThreads = () => {
threads = provider.getThreads()
}
// initial call to get the threads
getThreads()
// watch for changes
provider.watchThreads(getThreads)
To unwatch the threads you can call `provider.unwatchThreads(getThreads)`.
provider.unwatchThreads(getThreads)
Let's say you want to write a react hook to get the threads and keep them up to date, you could write a hook like this.
const useThreads = (provider) => {
const [threads, setThreads] = useState([])
useEffect(() => {
if (!provider) {
return () => null
}
const getThreads = () => {
setThreads(provider.getThreads())
}
getThreads()
provider.watchThreads(getThreads)
return () => {
provider.unwatchThreads(getThreads)
}
}, [provider])
return threads
}
Now those threads will be reactive and can be used to render the threads in your UI.
[](https://tiptap.dev/docs/comments/core-concepts/manage-threads#update-a-thread)
Update a thread
-------------------------------------------------------------------------------------------------
To update a thread you can use the `updateThread` command. This command will update the thread with the given id and update the content of the thread.
editor.commands.updateThread({
id: '123',
{
data: {
seen: true,
}
}
})
This will update the thread with the ID `123` and set the `seen` property to `true`.
[](https://tiptap.dev/docs/comments/core-concepts/manage-threads#delete-a-thread)
Delete a thread
-------------------------------------------------------------------------------------------------
To delete a thread you can use the `removeThread` command. This command will delete the thread with the given ID.
### How to delete threads
By default, threads removed won't be deleted from the yjs document. To do this, you can pass through the `deleteThread` option to the `removeThread` command.
editor.commands.removeThread({
id: '123',
deleteThread: true,
})
[](https://tiptap.dev/docs/comments/core-concepts/manage-threads#create-update-and-delete-comments)
Create, update and delete comments
--------------------------------------------------------------------------------------------------------------------------------------
Comments can be added, edited, and removed within threads but cannot be marked as resolved, as they are considered parts of the thread discussions.
To create a comment on a thread you can use the `createComment` command. This command will create a new comment on the thread with the given ID.
editor.commands.createComment({
threadId: '123',
content: 'This is a new comment', // this could also be Tiptap JSON or any other type of content
data: {
user, // pass through any meta data you want - in this case the user
},
})
This will create a new comment on the thread with the ID `123` and set the content to `This is a new comment`. You can also pass through any metadata you want to the comment.
To update a comment you can use the `updateComment` command. This command will update the comment with the given ID and update the content of the comment.
editor.commands.updateComment({
threadId: '123', // the thread ID
id: '456', // the comment ID
content: 'Now this is the new content', // the new content of the comment
data: {
edited: true, // set the edited property to true
},
})
This will update the comment with the ID `456` on the thread with the ID `123` and set the content to `Now this is the new content`. You can also pass through any metadata you want to the comment.
Finally you can delete a comment by using the `deleteComment` command. This command will delete the comment with the given ID.
editor.commands.deleteComment({
threadId: '123',
id: '456',
})
[](https://tiptap.dev/docs/comments/core-concepts/manage-threads#resolve-and-unresolve-threads)
Resolve and unresolve threads
-----------------------------------------------------------------------------------------------------------------------------
To resolve a thread you can use the `resolveThread` command. This command will resolve the thread with the given ID.
editor.commands.resolveThread({
id: '123',
})
This will resolve the thread with the ID `123`. To unresolve a thread you can use the `unresolveThread` command. This command will unresolve the thread with the given ID.
If you want to resolve a thread and add information on which user resolved the thread, you can set the threads data to include the user who resolved the thread. Be sure to clear the data when unresolving the thread.
editor.commands.unresolveThread({
id: '123',
})
[](https://tiptap.dev/docs/comments/core-concepts/manage-threads#find-all-threads-in-the-document)
Find all threads in the document
-----------------------------------------------------------------------------------------------------------------------------------
To find all threads within the editor document, you can use the `findThreadsInDocument` utility function. This function returns an array of all thread nodes and marks, including their positions in the document.
const threads = findThreadsInDocument(editor, {
blockTypeName: 'blockThread',
markTypeName: 'inlineThread',
})
This will return an array of thread objects, each containing the thread identifier, the node, its position and optionally the mark associated with it.
[](https://tiptap.dev/docs/comments/core-concepts/manage-threads#select-a-thread)
Select a thread
-------------------------------------------------------------------------------------------------
To select a thread you can use the `selectThread` command. This command selects the thread with the specified ID.
editor.commands.selectThread({
id: '123',
})
This will move the cursor to the thread with the ID `123`.
To deselect a thread you can use the `unselectThread` command. This command deselects the thread with the specified ID.
editor.commands.unselectThread({
id: '123',
})
You can also select or unselect threads without ID. In that case, the editor will select or unselect the thread at the current selection.
[PreviouslyInstall](https://tiptap.dev/docs/comments/getting-started/install)
[Next upStyle threads](https://tiptap.dev/docs/comments/core-concepts/style-threads)
---
# Thread authentication | Tiptap Comments Docs
The thread authenticator enforces ownership rules for threads and comments in collaborative documents. When enabled, users can only edit and delete their own threads and comments, while thread creation automatically assigns the authenticated user's ID.
### Requires JWT authentication
Thread authentication relies on [JWT authentication](https://tiptap.dev/docs/collaboration/getting-started/authenticate)
to identify users. Make sure your JWTs include the `sub` claim with a unique user identifier.
[](https://tiptap.dev/docs/comments/core-concepts/thread-authentication#enable-thread-authentication)
Enable thread authentication
----------------------------------------------------------------------------------------------------------------------------------
Enable the thread authenticator by setting the `thread_authenticator` setting to `1` via the [Settings API](https://tiptap.dev/docs/collaboration/operations/configure)
:
curl --location --request PUT 'https://YOUR_APP_ID.collab.tiptap.cloud/api/admin/settings/thread_authenticator' \
--header 'Authorization: YOUR_SECRET_FROM_SETTINGS_AREA' \
--header 'Content-Type: text/plain' \
-d '1'
You can also set the `COLLAB_THREAD_AUTHENTICATOR` environment variable to `1` on self-hosted instances.
[](https://tiptap.dev/docs/comments/core-concepts/thread-authentication#how-it-works)
How it works
--------------------------------------------------------------------------------------------------
When enabled, the thread authenticator intercepts all document updates that modify threads or comments and validates them against the authenticated user's identity.
### [](https://tiptap.dev/docs/comments/core-concepts/thread-authentication#automatic-user-id-assignment)
Automatic user ID assignment
When a user creates a thread or comment, the authenticator automatically assigns their user ID (from the JWT `sub` claim) to the thread and comment. This happens transparently and does not require any client-side changes.
### [](https://tiptap.dev/docs/comments/core-concepts/thread-authentication#ownership-enforcement)
Ownership enforcement
The following rules are enforced for all thread and comment operations:
| Operation | Rule |
| --- | --- |
| **Create thread** | Allowed. The authenticated user's ID is assigned automatically. |
| **Edit thread** | Only the thread owner can edit. |
| **Delete thread** | Only the thread owner can delete. |
| **Resolve/unresolve thread** | Owners can always resolve. Non-owners depend on access level (see below). |
| **Create comment** | Allowed. The authenticated user's ID is assigned automatically. |
| **Edit comment** | Only the comment owner can edit. |
| **Delete comment** | Only the comment owner can delete. |
If a user attempts an unauthorized action, the server rejects the update and returns an unauthorized error.
### Threads without a user ID
Threads and comments that were created before enabling the thread authenticator may not have a user ID assigned. These can be edited or deleted by anyone. Once a user modifies such a thread, their user ID is assigned to it.
[](https://tiptap.dev/docs/comments/core-concepts/thread-authentication#resolving-foreign-threads)
Resolving foreign threads
----------------------------------------------------------------------------------------------------------------------------
By default, all users with write or comment access can resolve threads they don't own. This is useful for workflows where reviewers or editors need to mark discussions as resolved.
To restrict comment-only users from resolving other users' threads, set the `commentonly_disallow_resolve_foreign_threads` setting to `1`:
curl --location --request PUT 'https://YOUR_APP_ID.collab.tiptap.cloud/api/admin/settings/commentonly_disallow_resolve_foreign_threads' \
--header 'Authorization: YOUR_SECRET_FROM_SETTINGS_AREA' \
--header 'Content-Type: text/plain' \
-d '1'
The resulting permission matrix for resolving foreign threads:
| Access level | Default behavior | With `commentonly_disallow_resolve_foreign_threads` set to `1` |
| --- | --- | --- |
| Write access | Can resolve | Can resolve |
| Comment-only access | Can resolve | Cannot resolve |
Thread owners can always resolve their own threads regardless of this setting.
[](https://tiptap.dev/docs/comments/core-concepts/thread-authentication#configuration-reference)
Configuration reference
------------------------------------------------------------------------------------------------------------------------
| Setting | Environment variable | Default | Description |
| --- | --- | --- | --- |
| `thread_authenticator` | `COLLAB_THREAD_AUTHENTICATOR` | `0` (disabled) | Set to `1` to enable thread authentication. |
| `commentonly_disallow_resolve_foreign_threads` | `COLLAB_COMMENTONLY_DISALLOW_RESOLVE_FOREIGN_THREADS` | `0` (allowed) | Set to `1` to prevent comment-only users from resolving threads they don't own. |
Both settings can be configured via the [Settings API](https://tiptap.dev/docs/collaboration/operations/configure)
or environment variables.
[PreviouslyConfigure](https://tiptap.dev/docs/comments/core-concepts/configure)
[Next upEditor commands](https://tiptap.dev/docs/comments/integrate/editor-commands)
---
# Editor commands | Tiptap Comments Docs
The Comments Editor API focuses on the client-side interactions for managing comments within the editor, enabling direct manipulation and customization of comment threads.
For server-side operations, use the [Comments REST API](https://tiptap.dev/docs/comments/integrate/rest-api)
to manage thread and comments outside the editor.
[](https://tiptap.dev/docs/comments/integrate/editor-commands#all-editor-commands-for-comments)
All editor commands for comments
--------------------------------------------------------------------------------------------------------------------------------
| Command | Description |
| --- | --- |
| `setThread` | Creates a new thread with optional user and content data |
| `removeThread` | Deletes a specified thread, with an option to remove it from the Yjs document |
| `updateThread` | Updates specific thread properties like 'seen' status |
| `selectThread` | Focuses the editor on a specified thread |
| `unselectThread` | Removes focus from the selected thread |
| `resolveThread` | Marks a thread as resolved |
| `unresolveThread` | Reverts a thread from resolved status |
| `createComment` | Adds a new comment to a thread with details like content and user data |
| `updateComment` | Modifies an existing comment's content and metadata |
| `removeComment` | Deletes a specified comment from a thread |
[](https://tiptap.dev/docs/comments/integrate/editor-commands#interact-with-threads)
Interact with threads
----------------------------------------------------------------------------------------------------------
### [](https://tiptap.dev/docs/comments/integrate/editor-commands#setthread-content-data-commentdata)
setThread( content, data, commentData )
Creates a new thread at your current selection.
editor.commands.setThread({
content: 'This is a new thread',
data: { authorId: '123' },
commentData: { authorId: '123' },
})
### [](https://tiptap.dev/docs/comments/integrate/editor-commands#removethread-id-deletethread)
removeThread( id, deleteThread )
Deletes a thread with the given ID. If no ID is provided, the thread at the current selection will be deleted. If `deleteThread` is set to `true`, the thread will also be deleted from the Yjs document.
editor.commands.removeThread({
id: '101',
deleteThread: true,
})
### [](https://tiptap.dev/docs/comments/integrate/editor-commands#updatethread-id-data)
updateThread( id, data )
Updates the properties of a thread with the specified ID.
editor.commands.updateThread({
id: '101',
data: { seen: true },
})
### [](https://tiptap.dev/docs/comments/integrate/editor-commands#selectthread-options)
selectThread( options )
Selects a thread with the specified ID.
editor.commands.selectThread({
id: '101',
})
#### [](https://tiptap.dev/docs/comments/integrate/editor-commands#options)
Options
* `id` (string): The ID of the thread to select.
* `selectAround` (boolean, default: false): If true, selects the content around the thread.
* `focus` (boolean, default: true): If true, focuses the editor after selecting the thread.
* `scrollIntoView` (boolean, default: true): If true, scrolls the editor to bring the selected thread into view.
* `updateSelection` (boolean, default: true): If true, updates the editor's selection to the selected thread.
* `triggerClick` (boolean, default: false): If true, simulates a click
### [](https://tiptap.dev/docs/comments/integrate/editor-commands#unselectthread)
unselectThread()
Deselects the currently selected thread.
editor.commands.unselectThread()
### [](https://tiptap.dev/docs/comments/integrate/editor-commands#resolvethread-id)
resolveThread( id )
Marks the thread with the specified ID as resolved.
editor.commands.resolveThread({
id: '101',
})
### [](https://tiptap.dev/docs/comments/integrate/editor-commands#unresolvethread-id)
unresolveThread( id )
Reverts the thread with the specified ID to unresolved status.
editor.commands.unresolveThread({
id: '101',
})
[](https://tiptap.dev/docs/comments/integrate/editor-commands#handle-comments)
Handle comments
----------------------------------------------------------------------------------------------
### [](https://tiptap.dev/docs/comments/integrate/editor-commands#createcomment-threadid-content-data)
createComment( threadId, content, data )
Creates a new comment on the thread with the specified thread ID.
editor.commands.createComment({
threadId: '101',
content: 'This is a new comment',
data: { authorId: '123' },
})
### [](https://tiptap.dev/docs/comments/integrate/editor-commands#updatecomment-threadid-id-content-data)
updateComment( threadId, id, content, data )
Updates a comment with the specified ID on the thread identified by a given thread ID.
editor.commands.updateComment({
threadId: '101',
id: '456',
content: 'Now this is the new content',
data: { edited: true },
})
### [](https://tiptap.dev/docs/comments/integrate/editor-commands#removecomment-threadid-id)
removeComment( threadId, id )
Deletes a comment with the specified ID from the thread identified by a given thread ID.
editor.commands.removeComment({
threadId: '101',
id: '456',
})
[PreviouslyThread authentication](https://tiptap.dev/docs/comments/core-concepts/thread-authentication)
[Next upREST API](https://tiptap.dev/docs/comments/integrate/rest-api)
---
# Comments | Tiptap Comments Docs
The `Comments` extension lets users create threads and comments in the editor. Threads can be used to discuss specific parts of the document, while individual comments can be added to particular sections.
Comments can be accessed and manipulated through the [Comments REST API](https://tiptap.dev/docs/comments/integrate/rest-api)
or received via [webhooks](https://tiptap.dev/docs/comments/integrate/webhook)
, enabling the creation of notification systems and the ability to add comments from outside the Editor.
For simpler use cases, start with the [install](https://tiptap.dev/docs/comments/getting-started/install)
section.
[](https://tiptap.dev/docs/comments/getting-started/overview#comments-features)
Comments features
-------------------------------------------------------------------------------------------------
* Add inline, document, or sidebar comments
* Comments on text, nodes, custom nodes, or across a selection of nodes
* Rich text support within comments (e.g., bold, emojis)
* Resolve, edit, or delete comments
* Offline commenting support
* Handle overlapping comments
* Mention users directly within comments
* Webhooks to trigger custom notifications services when users are mentioned
* Programmatically manage comments using the Comments API
[Next upInstall](https://tiptap.dev/docs/comments/getting-started/install)
---
# Install comments | Tiptap Comments Docs
Install and configure the comments extension by following this guide. Take a look at the Comments example application at the bottom of this page for a whole integration.
[](https://tiptap.dev/docs/comments/getting-started/install#access-the-private-registry)
Access the private registry
--------------------------------------------------------------------------------------------------------------------
The Comments extension is published in Tiptap’s private npm registry. Integrate the extension by following the [private registry guide](https://tiptap.dev/docs/guides/pro-extensions)
.
npm install @tiptap-pro/extension-comments
[](https://tiptap.dev/docs/comments/getting-started/install#integrating-the-comments-extension)
Integrating the Comments extension
----------------------------------------------------------------------------------------------------------------------------------
After installing the `comments` extension via npm or any other package manager, you can use it in your editor by registering the extension in the `extensions` property of your editor instance.
The Comments extension consists of multiple components, including nodes and plugins. To include all the required features, use the `CommentsKit` extension.
import { CommentsKit } from '@tiptap-pro/extension-comments'
const editor = new Editor({
...
extensions: [\
...,\
CommentsKit,\
]
})
This will add all required extensions to your editor. Since Threads are a **cloud** or **on premises** feature you will need to also pass through a `TiptapCollabProvider` instance to your comments extension.
const collabProvider = new TiptapCollabProvider({
// your provider options
})
const editor = new Editor({
...
extensions: [\
...,\
CommentsKit.configure({\
provider: collabProvider,\
}),\
]
})
Your editor is now ready to support threads.
* * *
See a full example of how to use the Comments extension in the following example:
[PreviouslyOverview](https://tiptap.dev/docs/comments/getting-started/overview)
[Next upManage threads](https://tiptap.dev/docs/comments/core-concepts/manage-threads)
---
# Webhook | Tiptap Comments Docs
Set up and manage webhooks to improve your Comments integration. Common use cases for Comments webhooks include:
* Sending notifications when a thread is created, resolved, updated, or deleted.
* Notifying users when comments are added, updated, or deleted.
* In conjunction with the [mention extension](https://tiptap.dev/docs/editor/extensions/nodes/mention)
, sending emails or notifications to users when they are mentioned in comments.
[](https://tiptap.dev/docs/comments/integrate/webhook#enable-comment-events)
Enable comment events
--------------------------------------------------------------------------------------------------
For accounts created after March 1, 2024, Comments webhooks are enabled by default. Otherwise, you could still be using an older version of the webhook system and need to manually upgrade:
1. In case you’ve previously implemented Collaboration webhooks, check the `type` and `trigger` fields when processing incoming webhooks. ([Documentation](https://tiptap.dev/docs/collaboration/core-concepts/webhooks)
)
2. Navigate to your [Collaboration settings](https://cloud.tiptap.dev/v2/configuration/document-server)
.
3. In the Webhooks section, click **Upgrade**.
This upgrade is necessary to accommodate the introduction of multiple new events being routed to the same webhook endpoint, distinguished by a new `type` and `trigger` field.
[](https://tiptap.dev/docs/comments/integrate/webhook#configure-webhooks)
Configure webhooks
--------------------------------------------------------------------------------------------
To configure webhooks for Comments notifications:
1. Navigate to the [Collaboration settings](https://cloud.tiptap.dev/v2/configuration/document-server)
in your account.
2. In the Webhooks section, add your desired endpoint URL.
After adding your URL, the webhook is immediately live. You'll start receiving notifications for the specified events without any delay.
[](https://tiptap.dev/docs/comments/integrate/webhook#webhook-events)
Webhook events
------------------------------------------------------------------------------------
Comments webhooks trigger notifications for a variety of events related to threads and comments within the Comments extension. These events are triggered immediately as soon as their associated action occur within the comments.
* `comment.added`
* `comment.updated`
* `comment.deleted`
* `thread.created` (only triggered if the thread is created without a first comment, usually via our 'create thread' API. When using the official Tiptap extensions, you'll only see `comment.added`)
* `thread.resolved`
* `thread.updated`
* `thread.deleted`
[](https://tiptap.dev/docs/comments/integrate/webhook#example-payloads)
Example payloads
----------------------------------------------------------------------------------------
Below are example payloads for different types of webhook events:
### [](https://tiptap.dev/docs/comments/integrate/webhook#threadcomment-created)
Thread/comment created
{
"trigger": "comment.added",
"thread": {
"id": "128ba3a9-c684-4956-8c9f-fe5dc147c7e5",
"createdAt": "2024-03-02T22:17:51.304Z",
"comments": [\
{\
"id": "0259e4cb-43ad-4eb2-a7e9-a7a7d5207a76",\
"createdAt": "2024-03-02T22:17:51.307Z",\
"updatedAt": "2024-03-02T22:17:51.307Z",\
"data": {\
"userName": "Cyndi Lauper",\
"userAvatar": "https://api.dicebear.com/7.x/lorelei/svg?seed=Cyndi Lauper"\
},\
"content": "Threaderstellungskommentar"\
}\
],
"updatedAt": "2024-03-02T22:17:51.305Z"
},
"comment": {
"id": "0259e4cb-43ad-4eb2-a7e9-a7a7d5207a76",
"createdAt": "2024-03-02T22:17:51.307Z",
"updatedAt": "2024-03-02T22:17:51.307Z",
"data": {
"userName": "Cyndi Lauper",
"userAvatar": "https://api.dicebear.com/7.x/lorelei/svg?seed=Cyndi Lauper"
},
"content": "Threaderstellungskommentar"
},
"selection": [\
{\
"type": "text",\
"text": "in my opinion",\
"marks": [\
{\
"type": "inlineThread",\
"attrs": {\
"data-thread-id": "128ba3a9-c684-4956-8c9f-fe5dc147c7e5"\
}\
}\
]\
}\
],
"appName": "",
"user": "",
"name": "documentName",
"type": "THREAD"
}
### [](https://tiptap.dev/docs/comments/integrate/webhook#comment-updated)
Comment updated
{
"trigger": "comment.updated",
"thread": [\
{\
"id": "0259e4cb-43ad-4eb2-a7e9-a7a7d5207a76",\
"createdAt": "2024-03-02T22:17:51.307Z",\
"updatedAt": "2024-03-02T22:18:04.246Z",\
"data": {\
"userName": "Cyndi Lauper",\
"userAvatar": "https://api.dicebear.com/7.x/lorelei/svg?seed=Cyndi Lauper"\
},\
"content": "Threaderstellungskommentar (bearbeitet)"\
}\
],
"comment": {
"id": "0259e4cb-43ad-4eb2-a7e9-a7a7d5207a76",
"createdAt": "2024-03-02T22:17:51.307Z",
"updatedAt": "2024-03-02T22:18:04.246Z",
"data": {
"userName": "Cyndi Lauper",
"userAvatar": "https://api.dicebear.com/7.x/lorelei/svg?seed=Cyndi Lauper"
},
"content": "Threaderstellungskommentar (bearbeitet)"
},
"selection": [\
{\
"type": "text",\
"text": "in my opinion",\
"marks": [\
{\
"type": "inlineThread",\
"attrs": {\
"data-thread-id": "0259e4cb-43ad-4eb2-a7e9-a7a7d5207a76"\
}\
}\
]\
}\
],
"appName": "",
"user": "",
"name": "documentName",
"type": "THREAD"
}
### [](https://tiptap.dev/docs/comments/integrate/webhook#comment-deleted)
Comment deleted
{
"trigger": "comment.deleted",
"thread": {
"id": "128ba3a9-c684-4956-8c9f-fe5dc147c7e5",
"createdAt": "2024-03-02T22:17:51.304Z",
"comments": [\
{\
"id": "0259e4cb-43ad-4eb2-a7e9-a7a7d5207a76",\
"createdAt": "2024-03-02T22:17:51.307Z",\
"updatedAt": "2024-03-02T22:18:04.246Z",\
"data": {\
"userName": "Cyndi Lauper",\
"userAvatar": "https://api.dicebear.com/7.x/lorelei/svg?seed=Cyndi Lauper"\
},\
"content": "Threaderstellungskommentar (bearbeitet)"\
}\
],
"updatedAt": "2024-03-02T22:17:51.305Z"
},
"comment": {
"id": "1841e650-2202-42b6-a868-907fee42ccf7",
"createdAt": "2024-03-02T22:18:20.974Z",
"updatedAt": "2024-03-02T22:18:20.975Z",
"data": {
"userName": "Cyndi Lauper",
"userAvatar": "https://api.dicebear.com/7.x/lorelei/svg?seed=Cyndi Lauper"
},
"content": "Zweites Kommentar, selber Thread"
},
"selection": [], // selection is not sent for deleted comments
"appName": "",
"user": "",
"name": "documentName",
"type": "THREAD"
}
### [](https://tiptap.dev/docs/comments/integrate/webhook#thread-deleted)
Thread deleted
{
"trigger": "thread.deleted",
"thread": {
"id": "128ba3a9-c684-4956-8c9f-fe5dc147c7e5",
"createdAt": "2024-03-02T22:17:51.304Z",
"comments": [\
{\
"id": "0259e4cb-43ad-4eb2-a7e9-a7a7d5207a76",\
"createdAt": "2024-03-02T22:17:51.307Z",\
"updatedAt": "2024-03-02T22:18:04.246Z",\
"data": {\
"userName": "Cyndi Lauper",\
"userAvatar": "https://api.dicebear.com/7.x/lorelei/svg?seed=Cyndi Lauper"\
},\
"content": "Threaderstellungskommentar (bearbeitet)"\
}\
],
"updatedAt": "2024-03-02T22:18:52.050Z",
"resolvedAt": null
},
"selection": [], // selection is not sent for deleted threads
"appName": "",
"user": "",
"name": "documentName",
"type": "THREAD"
}
### [](https://tiptap.dev/docs/comments/integrate/webhook#thread-resolved)
Thread resolved
{
"trigger": "thread.resolved",
"thread": {
"id": "128ba3a9-c684-4956-8c9f-fe5dc147c7e5",
"createdAt": "2024-03-02T22:17:51.304Z",
"comments": [\
{\
"id": "0259e4cb-43ad-4eb2-a7e9-a7a7d5207a76",\
"createdAt": "2024-03-02T22:17:51.307Z",\
"updatedAt": "2024-03-02T22:18:04.246Z",\
"data": {\
"userName": "Cyndi Lauper",\
"userAvatar": "https://api.dicebear.com/7.x/lorelei/svg?seed=Cyndi Lauper"\
},\
"content": "Threaderstellungskommentar (bearbeitet)"\
}\
],
"updatedAt": "2024-03-02T22:18:48.531Z",
"resolvedAt": "2024-03-02T22:18:48.531Z"
},
"selection": [\
{\
"type": "text",\
"text": "in my opinion",\
"marks": [\
{\
"type": "inlineThread",\
"attrs": {\
"data-thread-id": "128ba3a9-c684-4956-8c9f-fe5dc147c7e5"\
}\
}\
]\
}\
],
"appName": "",
"user": "",
"name": "documentName",
"type": "THREAD"
}
### [](https://tiptap.dev/docs/comments/integrate/webhook#thread-updated-ie-unresolved)
Thread updated (i.e., Unresolved)
{
"trigger": "thread.updated",
"thread": {
"id": "128ba3a9-c684-4956-8c9f-fe5dc147c7e5",
"createdAt": "2024-03-02T22:17:51.304Z",
"comments": [\
{\
"id": "0259e4cb-43ad-4eb2-a7e9-a7a7d5207a76",\
"createdAt": "2024-03-02T22:17:51.307Z",\
"updatedAt": "2024-03-02T22:18:04.246Z",\
"data": {\
"userName": "Cyndi Lauper",\
"userAvatar": "https://api.dicebear.com/7.x/lorelei/svg?seed=Cyndi Lauper"\
},\
"content": "Threaderstellungskommentar (bearbeitet)"\
}\
],
"updatedAt": "2024-03-02T22:18:52.050Z",
"resolvedAt": null
},
"selection": [\
{\
"type": "text",\
"text": "in my opinion",\
"marks": [\
{\
"type": "inlineThread",\
"attrs": {\
"data-thread-id": "128ba3a9-c684-4956-8c9f-fe5dc147c7e5"\
}\
}\
]\
}\
],
"appName": "",
"user": "",
"name": "documentName",
"type": "THREAD"
}
[PreviouslyREST API](https://tiptap.dev/docs/comments/integrate/rest-api)
---
# REST API | Tiptap Comments Docs
The Comments REST API lets users manage comment threads and individual comments from outside the Tiptap Editor. It supports creating, updating, deleting, and retrieving threads and comments.
Use the [Comments Postman Collection](https://www.postman.com/tiptap-platform/workspace/tiptap-workspace/folder/33042171-01d1c110-e913-4d99-b47a-fc95aad877c9)
for hands-on experimentation.
[](https://tiptap.dev/docs/comments/integrate/rest-api#access-the-api)
Access the API
-------------------------------------------------------------------------------------
The REST API is exposed directly from your Document server, available at your custom URL:
https://YOUR_APP_ID.collab.tiptap.cloud/
Authentication is done using an API secret which you can find in the [settings](https://cloud.tiptap.dev/v2/configuration/document-server)
of your Document server. The secret must be sent as an `Authorization` header.
If your document identifier contains a slash (`/`), encode it as `%2F`, e.g. using `encodeURIComponent`.
[](https://tiptap.dev/docs/comments/integrate/rest-api#review-all-api-endpoints)
Review all API endpoints
---------------------------------------------------------------------------------------------------------
| Operation | Method | Endpoint | Description |
| --- | --- | --- | --- |
| Create thread | POST | /api/documents/:identifier/threads | Create a new thread within a document |
| Get threads | GET | /api/documents/:identifier/threads | List all threads and view their details |
| Get thread | GET | /api/documents/:identifier/threads/:threadIdentifier | Retrieve a specific thread |
| Update thread | PATCH | /api/documents/:identifier/threads/:threadIdentifier | Modify attributes of an existing thread |
| Update comment | PATCH | /api/documents/:identifier/threads/:threadIdentifier/comments/:commentIdentifier | Update the content or metadata of a comment |
| Delete thread | DELETE | /api/documents/:identifier/threads/:threadIdentifier | Remove a specific thread from a document |
| Delete comment | DELETE | /api/documents/:identifier/threads/:threadIdentifier/comments/:commentIdentifier | Remove a specific comment from a thread |
[](https://tiptap.dev/docs/comments/integrate/rest-api#thread-rest-api-endpoints)
Thread REST API endpoints
-----------------------------------------------------------------------------------------------------------
### [](https://tiptap.dev/docs/comments/integrate/rest-api#get-threads)
Get threads
GET /api/documents/:identifier/threads
Retrieve all comment threads associated with a specific document. Use this endpoint to list all threads and view their details.
curl --location 'https://YOUR_APP_ID.collab.tiptap.cloud/api/documents/{document_id}/threads' \
--header 'Authorization: {{Authorization}}'
### [](https://tiptap.dev/docs/comments/integrate/rest-api#get-thread)
Get thread
GET /api/documents/:identifier/threads/:threadIdentifier
Fetch details of a specific thread using its unique identifier within a document. This is useful for retrieving specific discussion threads.
curl --location 'https://YOUR_APP_ID.collab.tiptap.cloud/api/documents/{document_id}/threads/{thread_id}' \
--header 'Authorization: {{Authorization}}'
### [](https://tiptap.dev/docs/comments/integrate/rest-api#create-thread)
Create thread
POST /api/documents/:identifier/threads
Create a new thread within a document. You can specify the initial content and additional data like user metadata.
curl --location 'https://YOUR_APP_ID.collab.tiptap.cloud/api/documents/{document_id}/threads' \
--header 'Content-Type: application/json' \
--header 'Authorization: {{Authorization}}' \
--data '{
"content": "moin",
"data": { "key": "ttt"}
}'
### [](https://tiptap.dev/docs/comments/integrate/rest-api#update-thread)
Update thread
PATCH /api/documents/:identifier/threads/:threadIdentifier
Modify attributes of an existing thread, such as marking it as resolved or updating its metadata.
curl --location --request PATCH 'https://YOUR_APP_ID.collab.tiptap.cloud/api/documents/{document_id}/threads/{thread_id}' \
--header 'Content-Type: application/json' \
--header 'Authorization: {{Authorization}}' \
--data '{
"resolvedAt": null
}'
### [](https://tiptap.dev/docs/comments/integrate/rest-api#delete-thread)
Delete thread
DELETE /api/documents/:identifier/threads/:threadIdentifier
Remove a specific thread from a document, effectively deleting all nested comments.
curl --location --request DELETE 'https://YOUR_APP_ID.collab.tiptap.cloud/api/documents/{document_id}/threads/{thread_id}' \
--header 'Authorization: {{Authorization}}'
[](https://tiptap.dev/docs/comments/integrate/rest-api#comment-rest-api-endpoints)
Comment REST API endpoints
-------------------------------------------------------------------------------------------------------------
### [](https://tiptap.dev/docs/comments/integrate/rest-api#create-comment)
Create comment
POST /api/documents/:identifier/threads/:threadIdentifier/comments
Add a new comment to an existing thread. Specify the content and any associated data.
curl --location 'https://YOUR_APP_ID.collab.tiptap.cloud/api/documents/{document_id}/threads/{thread_id}/comments' \
--header 'Content-Type: application/json' \
--header 'Authorization: {{Authorization}}' \
--data '{
"content": "test",
"data": { "key": "ttt"}
}'
### [](https://tiptap.dev/docs/comments/integrate/rest-api#update-comment)
Update comment
PATCH /api/documents/:identifier/threads/:threadIdentifier/comments/:commentIdentifier
Update the content or metadata of an existing comment within a thread.
curl --location --request PATCH 'https://YOUR_APP_ID.collab.tiptap.cloud/api/documents/{document_id}/threads/{thread_id}/comments/{comment_id}' \
--header 'Content-Type: application/json' \
--header 'Authorization: {{Authorization}}' \
--data '{
"content": "UPDATED!"
}'
### [](https://tiptap.dev/docs/comments/integrate/rest-api#delete-comment)
Delete comment
DELETE /api/documents/:identifier/threads/:threadIdentifier/comments/:commentIdentifier
Remove a specific comment from a thread. Use this to manage individual comments.
curl --location --request DELETE 'https://YOUR_APP_ID.collab.tiptap.cloud/api/documents/{document_id}/threads/{thread_id}/comments/{comment_id}' \
--header 'Authorization: {{Authorization}}'
[](https://tiptap.dev/docs/comments/integrate/rest-api#review-postman-collection)
Review Postman Collection
-----------------------------------------------------------------------------------------------------------
Use the [Comments Postman Collection](https://www.postman.com/tiptap-platform/workspace/tiptap-workspace/folder/33042171-01d1c110-e913-4d99-b47a-fc95aad877c9)
for hands-on experimentation.
[PreviouslyEditor commands](https://tiptap.dev/docs/comments/integrate/editor-commands)
[Next upWebhook](https://tiptap.dev/docs/comments/integrate/webhook)
---
# Conversion | Tiptap Conversion
The Tiptap Conversion Service makes it easy to import and export content between Tiptap and document file formats. It offers:
* **Import**: Convert files (Word DOCX or Markdown) into Tiptap's JSON document format.
* **Export**: Turn Tiptap JSON into Word DOCX, PDF, ODT (OpenDocument Text), EPUB, DOC (legacy Word), or Markdown files.
* **Editor Extensions**: Use built-in Tiptap Pro extensions to import/export DOCX and Markdown directly within a Tiptap editor instance.
* **REST API**: Use HTTP endpoints to integrate our conversion services anywhere needed without a Tiptap editor being required. The REST API currently supports all import and export formats.
### Conversion service
We are continuously improving the conversion service. Support for page breaks, headers/footers, references like footnotes, and other complex features are in active development.
[Next upAuthenticate](https://tiptap.dev/docs/conversion/getting-started/install)
---
# Authenticate | Tiptap Conversion
Tiptap Conversion lets you import and export Tiptap JSON to and from `DOCX`, `PDF`, `ODT`, `EPUB`, `DOC`, and `Markdown`. You can integrate import/export directly in your Tiptap editor with dedicated extensions for DOCX and Markdown, or use the Conversion REST API on your server for all supported formats.
[](https://tiptap.dev/docs/conversion/getting-started/install#set-up-authorization)
Set up authorization
--------------------------------------------------------------------------------------------------------
Most conversion operations require authentication. Generate a JWT (JSON Web Token) using the `secret key` from your Tiptap Cloud account. Include this JWT in requests or extension configs.
### Export DOCX Extension
The `extension-export-docx` package does not require authentication! Feel free to [skip](https://tiptap.dev/docs/conversion/export/docx/editor-export)
these steps if you only need that extension.
1. **Activate a plan:** Begin a [free trial or choose a subscription](https://cloud.tiptap.dev/v2/billing)
.
2. **Get your App ID and secret key** on the [Convert settings](https://cloud.tiptap.dev/v2/cloud/convert)
page.
3. **Generate a JWT** for testing using any JWT tool (e.g., JWT Builder). For production, always create JWTs **server-side** to keep your secret safe.
4. **Use the JWT** in your requests or extension config. For API calls, pass it in the `Authorization` header and the App ID in `X-App-Id`.
[](https://tiptap.dev/docs/conversion/getting-started/install#explore-file-type-integrations)
Explore file-type integrations
----------------------------------------------------------------------------------------------------------------------------
Depending on which format you want to work with, each file type has its own guide covering the available REST API endpoints and, where applicable, editor extensions:
**Import:**
* [DOCX import](https://tiptap.dev/docs/conversion/import/docx/rest-api)
(editor extension + REST API)
* [Markdown import](https://tiptap.dev/docs/conversion/import/markdown/rest-api)
(REST API)
**Export:**
* [DOCX export](https://tiptap.dev/docs/conversion/export/docx/rest-api)
(editor extension + REST API)
* [PDF export](https://tiptap.dev/docs/conversion/export/pdf/rest-api)
(REST API)
* [ODT export](https://tiptap.dev/docs/conversion/export/odt/rest-api)
(REST API)
* [EPUB export](https://tiptap.dev/docs/conversion/export/epub/rest-api)
(REST API)
* [DOC export](https://tiptap.dev/docs/conversion/export/doc/rest-api)
(REST API)
* [Markdown export](https://tiptap.dev/docs/conversion/export/markdown/rest-api)
(REST API)
These guides explain exactly how to integrate the respective REST API endpoints into your application and configure any necessary options.
[PreviouslyOverview](https://tiptap.dev/docs/conversion/getting-started/overview)
[Next upDOCX](https://tiptap.dev/docs/conversion/import/docx/editor-import)
---
# Custom LLM | Tiptap Content AI
Our AI extensions are designed to work with your custom LLMs. You can integrate them with your backend to provide advanced AI features to your users: AI agents, RAG pipelines, custom models, and more.
[](https://tiptap.dev/docs/content-ai/custom-llms#extensions)
Extensions
------------------------------------------------------------------------
We provide detailed guides on how to integrate your backend and LLMs in each of our extensions.

CloudOn premises
### AI Generation
Generate content, make simple inline edits, or autocomplete a text.
[Documentation](https://tiptap.dev/docs/content-ai/capabilities/generation/custom-llms)

BetaCloudOn premises
### AI Suggestion
Generate proofreadeing suggestions with your custom AI models.
[Documentation](https://tiptap.dev/docs/content-ai/capabilities/suggestion/custom-llms)

Closed Beta
### AI Toolkit
Give your AI text-editing superpowers. Build AI agents and custom AI integrations.
[Documentation](https://tiptap.dev/docs/content-ai/capabilities/ai-toolkit/overview)
---
# AI | Tiptap Content AI
Learn how to build AI-powered editors through guides, API references, and integration examples.
[](https://tiptap.dev/docs/content-ai/getting-started/overview#get-started)
Get started
---------------------------------------------------------------------------------------
Tiptap offers two frontend extensions that integrate directly into your editor.

Paid add-on
### AI Toolkit
Provides tool definitions for AI agents to read, insert, and patch editor content. Includes document chunking, schema-aware generation, real-time streaming, and suggestion-based reviews.
[Documentation](https://tiptap.dev/docs/content-ai/capabilities/ai-toolkit/overview)
[Use cases](https://tiptap.dev/docs/content-ai/capabilities/use-cases)

Paid add-onRestricted release
### Server AI Toolkit
Build AI agents that can read and edit Tiptap documents on the server. Enable background document processing and AI-powered document workflows.
[Documentation](https://tiptap.dev/docs/content-ai/capabilities/server-ai-toolkit/overview)

Start
### AI Generation
Executes built-in or custom text commands (summarize, rephrase, translate) with streaming responses. Includes Tab-triggered autocompletion and response management.
[Documentation](https://tiptap.dev/docs/content-ai/capabilities/generation/overview)
[Use cases](https://tiptap.dev/docs/content-ai/capabilities/use-cases)
[](https://tiptap.dev/docs/content-ai/getting-started/overview#explore-use-cases)
Explore use cases
---------------------------------------------------------------------------------------------------
Explore technical implementations for AI-powered editing. Build AI agents with document-editing capabilities like chatbots, inline edits, and proofreading, or add simple AI commands like autocompletion, translation, and summarization.
[View use cases](https://tiptap.dev/docs/content-ai/capabilities/use-cases)
[](https://tiptap.dev/docs/content-ai/getting-started/overview#use-your-own-ai-infrastructure)
Use your own AI infrastructure
-----------------------------------------------------------------------------------------------------------------------------
All extensions support custom AI infrastructure where no data is sent to Tiptap:
* **AI Toolkit**: Framework-agnostic. Works with Vercel AI SDK, LangChain, OpenAI SDK, Anthropic SDK, or your own implementation. [Learn more](https://tiptap.dev/docs/content-ai/capabilities/ai-toolkit/overview)
* **AI Generation**: Connect your own LLM by overriding the resolver. Alternatively, use Tiptap AI server as middleware to OpenAI (currently supports OpenAI models only). [Learn more](https://tiptap.dev/docs/content-ai/capabilities/generation/custom-llms)
**Privacy and security:** When using the Tiptap AI backend with AI Generation, learn how we handle AI data processing and content privacy in our [privacy documentation](https://tiptap.dev/docs/content-ai/resources/privacy)
.
[](https://tiptap.dev/docs/content-ai/getting-started/overview#legacy-extensions)
Legacy extensions
---------------------------------------------------------------------------------------------------
[AI Suggestion](https://tiptap.dev/docs/content-ai/capabilities/suggestion/overview)
, [AI Changes](https://tiptap.dev/docs/content-ai/capabilities/changes/overview)
, and [AI Assistant](https://tiptap.dev/docs/content-ai/capabilities/agent/overview)
are being deprecated in 2026 and will be replaced by AI Toolkit. These extensions receive no new features and are maintained for existing users only.
**Migrating to AI Toolkit:** The AI Toolkit provides superior capabilities for AI agent workflows. [View migration guides](https://tiptap.dev/docs/content-ai/capabilities/ai-toolkit/advanced-guides/migration-guides)
to learn how to transition your application.
### Migration support
Get hands-on help migrating your content as part of Business or Enterprise onboarding in a dedicated Slack channel.
[Talk to an engineer](https://tiptap.dev/contact-sales?form=ai-toolkit)
Trusted by Axios, PostHog, Beehiiv, GitLab and more.
[Next upUse cases](https://tiptap.dev/docs/content-ai/capabilities/use-cases)
---
# Legacy DOCX | Tiptap Conversion
The original `extension-import` and `extension-export` packages for Tiptap provide reliable methods to import and export DOCX files.
These extensions, and associated endpoints remain available to anyone with a Tiptap account but are deprecated and no longer actively developed.
They have been replaced by the new [Tiptap Conversion](https://tiptap.dev/docs/conversion/getting-started/overview)
extensions and services.
The following guide focuses on configuring the legacy extensions.
[](https://tiptap.dev/docs/conversion/legacy/overview#import-legacy-extension)
Import legacy extension
------------------------------------------------------------------------------------------------------
### Legacy Import Extension — Deprecated
The **extension-import** package is deprecated and will be sunset at some point in 2026 with prior communication from the team. Please migrate to our [newer solutions](https://tiptap.dev/docs/conversion/getting-started/overview)
to ensure uninterrupted service.
// Start with importing the extension
import { Import } from '@tiptap-pro/extension-import'
const editor = new Editor({
// ...
extensions: [\
// ...\
Import.configure({\
// The Convert App-ID from the Convert settings page: https://cloud.tiptap.dev/convert-settings\
appId: 'your-app-id',\
\
// The JWT you generated in the previous step\
token: 'your-jwt',\
\
// The URL to upload images to, if not provided, images will be stripped from the document\
imageUploadCallbackUrl: 'https://your-image-upload-url.com',\
\
// Enables the experimental DOCX import which should better preserve content styling\
experimentalDocxImport: true,\
}),\
],
})
### [](https://tiptap.dev/docs/conversion/legacy/overview#import-your-first-document)
Import your first document
// The most simple way to import a file
// This will import the uploaded file, replace the editor content
// and focus the editor
editor.chain().focus().import({ file }).run()
#### [](https://tiptap.dev/docs/conversion/legacy/overview#customize-the-import-behavior)
Customize the import behavior
// You can also use the onImport callback to customize the import behavior
editor
.chain()
.import({
file,
onImport(context) {
const { setEditorContent, content, error } = context
// add error handling
if (error) {
showErrorToast({ message: error.message })
}
// You could also modify the content before inserting it
content.doc.content.push({ type: 'paragraph', content: [{ type: 'text', text: 'Hello!' }] })
// you can change the loading state of your application for example
isLoading = false
// make sure you call the setEditorContent function if you want to run
// the default insertion behavior of the extension
// setEditorContent()
// but since we modified the content, we need to do the insertion manually
editor.commands.setContent(content)
},
})
.focus()
.run()
### [](https://tiptap.dev/docs/conversion/legacy/overview#import-options)
Import options
| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `appId` | `string` | `undefined` | The convert app ID from the Convert settings page: [https://cloud.tiptap.dev/convert-settings](https://cloud.tiptap.dev/convert-settings) |
| `token` | `string` | `undefined` | The JWT generated from your server via secret |
| `imageUploadCallbackUrl` | `string` | `undefined` | The URL to upload images to, if not provided, images will be stripped from the document |
| `experimentalDocxImport` | `boolean` | `false` | Enables the experimental DOCX import which should better preserve content styling (experimental, and this API may not be completely stable while in alpha), only applies to DOCX files uploaded |
### [](https://tiptap.dev/docs/conversion/legacy/overview#commands)
Commands
| Name | Description |
| --- | --- |
| `import` | Import a file into the editor |
#### [](https://tiptap.dev/docs/conversion/legacy/overview#import)
`import`
##### [](https://tiptap.dev/docs/conversion/legacy/overview#arguments)
Arguments
| Name | Type | Default | Options | Description |
| --- | --- | --- | --- | --- |
| `file` | `File` | `undefined` | Any file | The file to import |
| `format` | `string` | `undefined` | `gfm` | Is used for special cases where the format is not clear, for example markdown gfm |
| `onImport` | `Function` | `undefined` | `fn(context)` | A callback used to customize the import behavior. The context argument includes information about the content, errors and a `setEditorContent` function to modify the content |
### [](https://tiptap.dev/docs/conversion/legacy/overview#supported-formats)
Supported formats
* `docx` - Microsoft Word, Google Docs, OpenOffice, LibreOffice and others
* `odt` - OpenDocument Text format used by OpenOffice, LibreOffice and others
* `rtf` - Rich Text Format used by Microsoft Word, Google Docs and others
* Commonmark `markdown` - Markdown format used by various editors and platforms
* GFM `markdown` - GitHub Flavored Markdown used by GitHub
### [](https://tiptap.dev/docs/conversion/legacy/overview#caveats-and-limitations)
Caveats and limitations
* **Image upload** - Images are assumed to be inline within the document so, your editor should be setup with `Image.configure({ inline: true })` to display them correctly, otherwise they will be stripped from the document
* **Unsupported docx elements on import** - Importing docx files currently does not support page breaks, page headers and footers, horizontal rules or text styles
* **Content added via suggestion mode** - Content added via suggestion mode is not included in the imported ProseMirror document
* **PDF import & export** - Importing and exporting PDF files is not yet supported
* **Inline styles** - Inline styles are currently not parsed and are not available in import & export
[](https://tiptap.dev/docs/conversion/legacy/overview#export-legacy-extension)
Export legacy extension
------------------------------------------------------------------------------------------------------
### Legacy Export Extension — Deprecated
The **extension-export** package is deprecated and will be sunset at some point in 2026 with prior communication from the team. We strongly recommend transitioning to our updated [export extension](https://tiptap.dev/docs/conversion/getting-started/overview)
, which offers improved customization and ongoing support.
// Start with importing the extension
import { Export } from '@tiptap-pro/extension-export'
const editor = new Editor({
// ...
extensions: [\
// ...\
Export.configure({\
// The Convert App-ID from the convert settings page: https://cloud.tiptap.dev/convert-settings\
appId: 'your-app-id',\
\
// The JWT you generated in the previous step\
token: 'your-jwt',\
}),\
],
})
### [](https://tiptap.dev/docs/conversion/legacy/overview#export-a-document)
Export a document
// Do a simple export to docx
// Supported formats: docx, odt, md and gfm
editor.chain().focus().export({ format: 'docx' }).run()
### [](https://tiptap.dev/docs/conversion/legacy/overview#customize-the-export-behavior)
Customize the export behavior
// You can also use the onExport callback to customize the export behavior
editor.chain().export({
format: 'docx',
onExport(context) {
const { blob, error, download, filename } = context
// add error handling
if (error) {
showErrorToast({ message: error.message })
}
// you can change the loading state of your application for example
isLoading = false
// you could modify the filename or handle the blob differently here
// but we will keep them as they are
// you can trigger a download directly by calling the download method
download()
// keep in mind that the download method will only work in the browser
// and if the blob and filename was changed before must be managed manually
},
})
### [](https://tiptap.dev/docs/conversion/legacy/overview#export-options)
Export options
| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `appId` | `string` | `undefined` | The convert app ID from the Convert settings page: [https://cloud.tiptap.dev/convert-settings](https://cloud.tiptap.dev/convert-settings) |
| `token` | `string` | `undefined` | The JWT generated from your server via secret |
### [](https://tiptap.dev/docs/conversion/legacy/overview#commands)
Commands
| Name | Description |
| --- | --- |
| `export` | Export the editor content |
#### [](https://tiptap.dev/docs/conversion/legacy/overview#export)
`export`
##### [](https://tiptap.dev/docs/conversion/legacy/overview#arguments)
Arguments
| Name | Type | Default | Options | Description |
| --- | --- | --- | --- | --- |
| `format` | `string` | `undefined` | `docx,odt,md,gfm` | The format you want to export to |
| `content` | `JSONContent` | `undefined` | Any Tiptap JSON | The Tiptap JSON content you want to export |
| `onExport` | `Function` | `undefined` | `fn(context)` | A callback used to customize the export behavior. The context argument includes information about the blob, filename, errors and a `download` function to download the document directly |
### [](https://tiptap.dev/docs/conversion/legacy/overview#supported-formats)
Supported formats
* `docx` - Microsoft Word, Google Docs, OpenOffice, LibreOffice and others
* `odt` - OpenDocument Text format used by OpenOffice, LibreOffice and others
* Commonmark `markdown` - Markdown format used by various editors and platforms
* GFM `markdown` - GitHub Flavored Markdown used by GitHub
### [](https://tiptap.dev/docs/conversion/legacy/overview#caveats-and-limitations)
Caveats and limitations
* **Custom Node exports** - Exporting custom nodes or marks is supported in the new [Conversion](https://tiptap.dev/docs/conversion/getting-started/overview)
extensions and endpoints.
* **Custom docx templates** - Review our new [Conversion](https://tiptap.dev/docs/conversion/getting-started/overview)
extensions and endpoints to integrate docx templates.
* **PDF import & export** - Exporting PDF files is not yet supported
* **Inline styles** - Review our new [Conversion](https://tiptap.dev/docs/conversion/getting-started/overview)
extensions and endpoints to integrate inline styles.
[](https://tiptap.dev/docs/conversion/legacy/overview#legacy-rest-api)
Legacy REST API
--------------------------------------------------------------------------------------
### Legacy /v1/ Conversion REST API — Deprecated
The **/v1/ Document Conversion REST API** is deprecated and will be sunset at some point in 2026 with prior communication from the team. For continued updates and maintenance, migrate to newer [API endpoints](https://tiptap.dev/docs/conversion/getting-started/overview)
or check out our [Postman Collection](https://www.postman.com/tiptap-platform/workspace/tiptap-workspace/collection/33042171-bcc93ecb-8bad-4484-8cb0-d995ee23ae60)
for the latest resources.
The legacy document conversion API supports DOCX, ODT, and Markdown conversion from and to Tiptap's JSON format.
### [](https://tiptap.dev/docs/conversion/legacy/overview#import-endpoint)
/import endpoint
The `/import` endpoint enables the conversion of `docx`, `odt`, or `markdown` files into Tiptap's JSON format. Users can POST documents to this endpoint and use various parameters to customize how different document elements are handled during the conversion process.
* **Method**: `POST`
#### [](https://tiptap.dev/docs/conversion/legacy/overview#required-headers)
Required headers
| Name | Description |
| --- | --- |
| `Authorization` | The JWT to authenticate the request. Example: `Bearer your-jwt` |
| `X-App-Id` | The Convert App-ID from the Collaboration settings page: [https://cloud.tiptap.dev/convert-settings](https://cloud.tiptap.dev/convert-settings) |
#### [](https://tiptap.dev/docs/conversion/legacy/overview#body)
Body
| Name | Type | Description |
| --- | --- | --- |
| `file` | `File` | The file to convert |
| `imageUploadCallbackUrl` | `string` | The callback endpoint to upload images that were encountered within the uploaded document |
#### [](https://tiptap.dev/docs/conversion/legacy/overview#query-parameters)
Query parameters
Specify how source document elements are mapped to ProseMirror nodes or marks, and adjust the conversion to meet your specific styling and structural preferences.
| Name | Default | Description |
| --- | --- | --- |
| `paragraph` | `paragraph` | Defines which ProseMirror type is used for paragraph conversion |
| `heading` | `heading` | Defines which ProseMirror type is used for heading conversion |
| `blockquote` | `blockquote` | Defines which ProseMirror type is used for blockquote conversion |
| `codeblock` | `codeblock` | Defines which ProseMirror type is used for codeblock conversion |
| `bulletlist` | `bulletlist` | Defines which ProseMirror type is used for bulletList conversion |
| `orderedlist` | `orderedlist` | Defines which ProseMirror type is used for orderedList conversion |
| `listitem` | `listitem` | Defines which ProseMirror type is used for listItem conversion |
| `hardbreak` | `hardbreak` | Defines which ProseMirror type is used for hardbreak conversion |
| `horizontalrule` | `horizontalrule` | Defines which ProseMirror type is used for horizontalRule conversion |
| `table` | `table` | Defines which ProseMirror type is used for table conversion |
| `tablecell` | `tablecell` | Defines which ProseMirror type is used for tableCell conversion |
| `tableheader` | `tableheader` | Defines which ProseMirror type is used for tableHeader conversion |
| `tablerow` | `tablerow` | Defines which ProseMirror type is used for tableRow conversion |
| `bold` | `bold` | Defines which ProseMirror mark is used for bold conversion |
| `italic` | `italic` | Defines which ProseMirror mark is used for italic conversion |
| `underline` | `underline` | Defines which ProseMirror mark is used for underline conversion |
| `strikethrough` | `strike` | Defines which ProseMirror mark is used for strikethrough conversion |
| `link` | `link` | Defines which ProseMirror mark is used for link conversion |
| `code` | `code` | Defines which ProseMirror mark is used for code conversion |
| `image` | `image` | Defines which ProseMirror mark is used for image conversion |
### [](https://tiptap.dev/docs/conversion/legacy/overview#import-docx-endpoint-experimental)
/import-docx endpoint (experimental)
The `/import-docx` endpoint enables the conversion of docx files into Tiptap's JSON format and allows for more docx-specific functions than the /import endpoint. Users can POST documents to this endpoint and use various parameters to customize how different document elements are handled during the conversion process.
* **Method**: `POST`
#### [](https://tiptap.dev/docs/conversion/legacy/overview#required-headers)
Required headers
| Name | Description |
| --- | --- |
| `Authorization` | The JWT to authenticate the request. Example: `Bearer your-jwt` |
| `X-App-Id` | The Convert App-ID from the Collaboration settings page: [https://cloud.tiptap.dev/convert-settings](https://cloud.tiptap.dev/convert-settings) |
#### [](https://tiptap.dev/docs/conversion/legacy/overview#body)
Body
| Name | Type | Description |
| --- | --- | --- |
| `file` | `File` | The file to convert |
| `imageUploadCallbackUrl` | `string` | The callback endpoint to upload images that were encountered within the uploaded document |
#### [](https://tiptap.dev/docs/conversion/legacy/overview#query-parameters)
Query parameters
Specify how source document elements are mapped to ProseMirror nodes or marks, and adjust the conversion to meet your specific styling and structural preferences.
| Name | Default | Description |
| --- | --- | --- |
| `paragraph` | `paragraph` | Defines which ProseMirror type is used for paragraph conversion |
| `heading` | `heading` | Defines which ProseMirror type is used for heading conversion |
| `blockquote` | `blockquote` | Defines which ProseMirror type is used for blockquote conversion |
| `codeblock` | `codeblock` | Defines which ProseMirror type is used for codeblock conversion |
| `bulletlist` | `bulletlist` | Defines which ProseMirror type is used for bulletList conversion |
| `orderedlist` | `orderedlist` | Defines which ProseMirror type is used for orderedList conversion |
| `listitem` | `listitem` | Defines which ProseMirror type is used for listItem conversion |
| `hardbreak` | `hardbreak` | Defines which ProseMirror type is used for hardbreak conversion |
| `horizontalrule` | `horizontalrule` | Defines which ProseMirror type is used for horizontalRule conversion |
| `table` | `table` | Defines which ProseMirror type is used for table conversion |
| `tablecell` | `tablecell` | Defines which ProseMirror type is used for tableCell conversion |
| `tableheader` | `tableheader` | Defines which ProseMirror type is used for tableHeader conversion |
| `tablerow` | `tablerow` | Defines which ProseMirror type is used for tableRow conversion |
| `bold` | `bold` | Defines which ProseMirror mark is used for bold conversion |
| `italic` | `italic` | Defines which ProseMirror mark is used for italic conversion |
| `underline` | `underline` | Defines which ProseMirror mark is used for underline conversion |
| `strikethrough` | `strike` | Defines which ProseMirror mark is used for strikethrough conversion |
| `link` | `link` | Defines which ProseMirror mark is used for link conversion |
| `code` | `code` | Defines which ProseMirror mark is used for code conversion |
| `image` | `image` | Defines which ProseMirror mark is used for image conversion |
### [](https://tiptap.dev/docs/conversion/legacy/overview#export-endpoint)
/export endpoint
The `/export` endpoint converts Tiptap documents back into formats like `docx`, `odt`, or `markdown`.
* **Method**: `POST`
Convert a Tiptap document to a different format.
#### [](https://tiptap.dev/docs/conversion/legacy/overview#required-headers)
Required headers
| Name | Description |
| --- | --- |
| `Authorization` | The JWT to authenticate the request. Example: `Bearer your-jwt` |
| `X-App-Id` | The Convert App-ID from the Collaboration settings page: [https://cloud.tiptap.dev/convert-settings](https://cloud.tiptap.dev/convert-settings) |
#### [](https://tiptap.dev/docs/conversion/legacy/overview#body)
Body
| Name | Type | Description |
| --- | --- | --- |
| `content` | `Object` | The Tiptap document |
| `format` | `string` | The format to convert to, can be `docx`, `odt` or `markdown` |
#### [](https://tiptap.dev/docs/conversion/legacy/overview#query-parameters)
Query parameters
| Name | Default | Description |
| --- | --- | --- |
| `gfm` | `0` | Use Github Flavored Markdown for markdown export |
| `paragraph` | `paragraph` | Defines which ProseMirror type is used for paragraph conversion |
| `heading` | `heading` | Defines which ProseMirror type is used for heading conversion |
| `blockquote` | `blockquote` | Defines which ProseMirror type is used for blockquote conversion |
| `codeblock` | `codeblock` | Defines which ProseMirror type is used for codeblock conversion |
| `bulletlist` | `bulletlist` | Defines which ProseMirror type is used for bulletList conversion |
| `orderedlist` | `orderedlist` | Defines which ProseMirror type is used for orderedList conversion |
| `listitem` | `listitem` | Defines which ProseMirror type is used for listItem conversion |
| `hardbreak` | `hardbreak` | Defines which ProseMirror type is used for hardbreak conversion |
| `horizontalrule` | `horizontalrule` | Defines which ProseMirror type is used for horizontalRule conversion |
| `table` | `table` | Defines which ProseMirror type is used for table conversion |
| `tablecell` | `tablecell` | Defines which ProseMirror type is used for tableCell conversion |
| `tableheader` | `tableheader` | Defines which ProseMirror type is used for tableHeader conversion |
| `tablerow` | `tablerow` | Defines which ProseMirror type is used for tableRow conversion |
| `bold` | `bold` | Defines which ProseMirror mark is used for bold conversion |
| `italic` | `italic` | Defines which ProseMirror mark is used for italic conversion |
| `underline` | `underline` | Defines which ProseMirror mark is used for underline conversion |
| `strikethrough` | `strike` | Defines which ProseMirror mark is used for strikethrough conversion |
| `link` | `link` | Defines which ProseMirror mark is used for link conversion |
| `code` | `code` | Defines which ProseMirror mark is used for code conversion |
[PreviouslyREST API](https://tiptap.dev/docs/conversion/export/markdown/rest-api)
[Next upODT](https://tiptap.dev/docs/conversion/legacy/odt/editor-extensions)
---
# Privacy | Tiptap Content AI
At Tiptap we value your privacy. So when using Tiptap Content AI that topic may raise some questions which we like to address in detail below.
[](https://tiptap.dev/docs/content-ai/resources/privacy#cloud-integration)
Cloud integration
--------------------------------------------------------------------------------------------
The Tiptap Content AI Cloud Version **acts as a proxy** between your client (the Tiptap Editor) and OpenAI. This is done to protect your OpenAI secret from the client, as it is an anti-pattern to share secrets publicly and user-facing.
When using our cloud service, you’ll need to enter your OpenAI secret in your AI settings page. This key is then encrypted and stored in our database. We use this key to perform requests against the OpenAI API on your behalf.
### [](https://tiptap.dev/docs/content-ai/resources/privacy#data-flow)
Data Flow
The general flow of data looks like this:
1. Your client (the Editor) performs an AI command like `translate`
2. The AI extension sends a request to our backend service
3. The backend performs a lookup of your given App ID and ensures that the provided token (the generated JWT) is valid and continuous the request or aborts with an error code
4. After the authorization process the backend builds a prompt based on the desired command and uses your OpenAI API key to send the request to the OpenAI API
5. Once the request is processed, the response is send back to the client
### [](https://tiptap.dev/docs/content-ai/resources/privacy#what-we-record)
What we record
During the process described above, we record the following data on a regular basis to ensure that our services works correctly and reliable:
* HTTP logs including timestamps, a correlation ID, the referrer, user agent and IP including the relevant request headers and portion of the request body in order to ensure that the service is operational and prevent any abuse
* Performed operation mapped to your team including the timestamp, the correlation ID, the command (e.g. “translate”) and a timestamp in order to track the usage of your plan limits
To be able to help you in case there’s something off, we implemented a mechanism called “enhanced logging”.
**Only when asked to do so (e.g. for debugging) and with your permission** we may enable enhanced logging. To be clear: This is NOT the default. However, this leads to the following additional fields recorded:
* Input text and options, the built prompt of our backend and the response of OpenAI
Those data points help us to trace errors on each side and help you sort out any issues you may experience. After debugging is complete, we immediately disable the enhanced logging.
### [](https://tiptap.dev/docs/content-ai/resources/privacy#encryption)
Encryption
As mentioned above, we’re storing the following values encrypted in our database:
* Your OpenAI API Key
* Your JWT Secret
Those secrets are only decrypted when needed to authorize or fulfill a request.
All traffic between the client, the backend and OpenAI is encrypted using the latest SSL standards.
[](https://tiptap.dev/docs/content-ai/resources/privacy#on-your-premises)
On your premises
------------------------------------------------------------------------------------------
The on-premise version works technically the same as the cloud version. It acts as a proxy between the client and OpenAI, too.
Some things are different for obvious reasons:
* The OpenAI API key is stored in your desired location and is never sent to us
* We do **not** get any usage information on the performed requests
* The logs are sent to stdout and you’re able to use them as you’d need and like
Therefore, the on-prem version gives you full control over the gathered data and metrics.
---
# Collaboration | Tiptap Content AI
The [Content AI](https://tiptap.dev/docs/content-ai/getting-started/overview)
suite is compatible with Tiptap's [Collaboration](https://tiptap.dev/docs/collaboration/getting-started/overview)
features and extensions. In this section, we explain how AI extensions behave in a real-time collaborative environment.
[](https://tiptap.dev/docs/content-ai/resources/collaboration#ai-generation)
AI Generation
------------------------------------------------------------------------------------------
The [AI Generation](https://tiptap.dev/docs/content-ai/capabilities/generation/overview)
extension uses AI models to generate content. The content will be seen by all connected users once it is inserted into the editor.
However, to preview the AI responses before they are inserted, you can [store them in the extension storage](https://tiptap.dev/docs/content-ai/capabilities/generation/text-generation/manage-responses)
. While in that state, the responses are not seen by other connected users. When the user accepts the response, it will be added the editor contentand visible to all connected users.
Likewise, [autocompletion suggestions](https://tiptap.dev/docs/content-ai/capabilities/generation/text-generation/autocompletion)
are only visible to the user who triggered them, and they are not displayed to other users until the user accepts them.
[](https://tiptap.dev/docs/content-ai/resources/collaboration#ai-suggestion)
AI Suggestion
------------------------------------------------------------------------------------------
The [AI Suggestion](https://tiptap.dev/docs/content-ai/capabilities/suggestion/overview)
extension shows suggestions generated by AI. The suggestions are not shown to other connected users. This lets you show different suggestions to each user, based on their preferences or context. Once a suggestion is accepted, it will be inserted into the editor and made visible to all connected users.
[](https://tiptap.dev/docs/content-ai/resources/collaboration#ai-changes)
AI Changes
------------------------------------------------------------------------------------
The [AI Changes](https://tiptap.dev/docs/content-ai/capabilities/changes/overview)
extension tracks changes made by AI. The new editor content is available to all connected users as soon as it is inserted into the editor. However, the diff of the changes is not displayed to other connected users.
[](https://tiptap.dev/docs/content-ai/resources/collaboration#ai-agent)
AI Agent
--------------------------------------------------------------------------------
The [AI Agent](https://tiptap.dev/docs/content-ai/capabilities/agent/overview)
extension gives text-editing capabilities to your AI agent. By default, the changes are shown to other users as soon as they are inserted into the editor. However, you can configure a [review workflow](https://tiptap.dev/docs/content-ai/capabilities/agent/review)
so that changes are previewed before they are applied. In that case, the changes will not be visible to other users until the user accepts them.
---
# Clever Editor example | Tiptap Editor Docs
Tiptap is a powerful editor that allows you to create customized extensions for your needs. In this example, we create various extensions to handle **color highlighting**, a **emoji replacer** and a **typography replacer** to show how easy it is to handle content and replace it.
[PreviouslyText direction & RTL support](https://tiptap.dev/docs/examples/basics/text-direction)
[Next upCollaborative editing](https://tiptap.dev/docs/examples/advanced/collaborative-editing)
---
# Drawing example | Tiptap Editor Docs
Did you ever want to draw in a text editor? Me neither. Anyway, here is an example how that could work with Tiptap. If you want to build something like that, [learn more about node views](https://tiptap.dev/docs/editor/extensions/custom-extensions/node-views)
.
[PreviouslyCollaborative editing](https://tiptap.dev/docs/examples/advanced/collaborative-editing)
[Next upForced content structure](https://tiptap.dev/docs/examples/advanced/forced-content-structure)
---
# Collaborative editing example | Tiptap Editor Docs
This example shows how you can use Tiptap to let multiple users collaborate in the same document in real-time.
It connects all clients to a WebSocket server and merges changes to the document with the power of [Y.js](https://github.com/yjs/yjs)
. If you want to learn more about collaborative text editing, check out [our installation guide on collaborative editing](https://tiptap.dev/docs/collaboration/getting-started/overview)
.
[PreviouslyClever editor](https://tiptap.dev/docs/examples/advanced/clever-editor)
[Next upDrawing](https://tiptap.dev/docs/examples/advanced/drawing)
---
# Mentions example | Tiptap Editor Docs
The [mention extension](https://tiptap.dev/docs/editor/extensions/nodes/mention)
allows you to create a text editor that supports mentions. This example shows you how to build a text editor with mentions using Tiptap.
[PreviouslyMenus](https://tiptap.dev/docs/examples/advanced/menus)
[Next upSyntax highlighting](https://tiptap.dev/docs/examples/advanced/syntax-highlighting)
---
# Forced content structure example | Tiptap Editor Docs
Does your editor require content in a specific structure? You can enforce specific content structures via **custom documents** that extend our default `@tiptap/extension-document` extension. See below to learn more about how to create a custom document schema.
> **Note**: If you use the Trailing Node extension in combination with a custom document structure, adjust the `node` option manually - otherwise ProseMirror will fall back to the default node from your schema. For example, in the heading + block demo a heading node might be assumed as the default trailing node. See the [Trailing Node docs](https://tiptap.dev/docs/editor/extensions/functionality/trailing-node)
> for more details.
[PreviouslyDrawing](https://tiptap.dev/docs/examples/advanced/drawing)
[Next upInteractive React & Vue views](https://tiptap.dev/docs/examples/advanced/interactive-react-and-vue-views)
---
# Interactive React & Vue views example | Tiptap Editor Docs
Thanks to [node views](https://tiptap.dev/docs/editor/extensions/custom-extensions/node-views)
you can add interactivity to your nodes. If you can write it in JavaScript, you can add it to the editor.
[](https://tiptap.dev/docs/examples/advanced/interactive-react-and-vue-views#implementing-react-or-vue-components-as-nodeviews)
Implementing React or Vue components as NodeViews
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
[](https://tiptap.dev/docs/examples/advanced/interactive-react-and-vue-views#editable-content)
Editable content
---------------------------------------------------------------------------------------------------------------
React & Node NodeViews also support editable content. See the following example to learn how to create a node view with editable content.
[PreviouslyForced content structure](https://tiptap.dev/docs/examples/advanced/forced-content-structure)
[Next upReact performance](https://tiptap.dev/docs/examples/advanced/react-performance)
---
# Node views | Tiptap Editor Docs
Node views are the best thing since sliced bread, at least if you are a fan of customization (and bread). With node views you can add interactive nodes to your editor. That can literally be everything. If you can write it in JavaScript, you can use it in your editor.
Node views are amazing to improve the in-editor experience, but can also be used in a read-only instance of Tiptap. They are unrelated to the HTML output by design, so you have full control about the in-editor experience _and_ the output.
[](https://tiptap.dev/docs/editor/extensions/custom-extensions/node-views#different-types-of-node-views)
Different types of node views
--------------------------------------------------------------------------------------------------------------------------------------
Depending on what you would like to build, node views work a little bit different and can have their very specific capabilities, but also pitfalls. The main question is: How should your custom node look like?
### [](https://tiptap.dev/docs/editor/extensions/custom-extensions/node-views#editable-text)
Editable text
Yes, node views can have editable text, just like a regular node. That’s simple. The cursor will exactly behave like you would expect it from a regular node. Existing commands work very well with those nodes.
text
text
text
That’s how the [`TaskItem`](https://tiptap.dev/docs/editor/extensions/nodes/task-item)
node works.
### [](https://tiptap.dev/docs/editor/extensions/custom-extensions/node-views#non-editable-text)
Non-editable text
Nodes can also have text, which is not editable. The cursor can’t jump into those, but you don’t want that anyway.
Tiptap adds a `contenteditable="false"` to those by default.
text
text
text
That’s how you could render mentions, which shouldn’t be editable. Users can add or delete them, but not delete single characters.
Statamic uses those for their Bard editor, which renders complex modules inside Tiptap, which can have their own text inputs.
### [](https://tiptap.dev/docs/editor/extensions/custom-extensions/node-views#mixed-content)
Mixed content
You can even mix non-editable and editable text. That’s great to build complex things, and still use marks like bold and italic inside the editable content.
**BUT**, if there are other elements with non-editable text in your node view, the cursor can jump there. You can improve that with manually adding `contenteditable="false"` to the specific parts of your node view.
text
non-editable text
editable text
text
[](https://tiptap.dev/docs/editor/extensions/custom-extensions/node-views#markup)
Markup
----------------------------------------------------------------------------------------
But what happens if you [access the editor content](https://tiptap.dev/docs/guides/output-json-html)
? If you’re working with HTML, you’ll need to tell Tiptap how your node should be serialized.
The editor **does not** export the rendered JavaScript node, and for a lot of use cases you wouldn’t want that anyway.
Let’s say you have a node view which lets users add a video player and configure the appearance (autoplay, controls, …). You want the interface to do that in the editor, not in the output of the editor. The output of the editor should probably only have the video player.
I know, I know, it’s not that easy. Just keep in mind, that you‘re in full control of the rendering inside the editor and of the output.
### What if you store JSON?
That doesn’t apply to JSON. In JSON, everything is stored as an object. There is no need to configure the “translation” to and from JSON.
### [](https://tiptap.dev/docs/editor/extensions/custom-extensions/node-views#render-html)
Render HTML
Okay, you’ve set up your node with an interactive node view and now you want to control the output. Even if your node view is pretty complex, the rendered HTML can be simple:
renderHTML({ HTMLAttributes }) {
return ['my-custom-node', mergeAttributes(HTMLAttributes)]
},
// Output:
Make sure it’s something distinguishable, so it’s easier to restore the content from the HTML. If you just need something generic markup like a `
` consider to add a `data-type="my-custom-node"`.
### [](https://tiptap.dev/docs/editor/extensions/custom-extensions/node-views#parse-html)
Parse HTML
The same applies to restoring the content. You can configure what markup you expect, that can be something completely unrelated to the node view markup. It just needs to contain all the information you want to restore.
Attributes are automagically restored, if you registered them through [`addAttributes`](https://tiptap.dev/docs/editor/extensions/custom-extensions/extend-existing#attributes)
.
// Input:
parseHTML() {
return [{\
tag: 'my-custom-node',\
}]
},
### [](https://tiptap.dev/docs/editor/extensions/custom-extensions/node-views#render-javascriptvuereact)
Render JavaScript/Vue/React
But what if you want to render your actual JavaScript/Vue/React code? Use the [Static Renderer](https://tiptap.dev/docs/editor/api/utilities/static-renderer)
. This utility lets you render your content as HTML, Markdown, or React components, without an Editor instance.
[PreviouslyExtend existing](https://tiptap.dev/docs/editor/extensions/custom-extensions/extend-existing)
[Next upJavascript](https://tiptap.dev/docs/editor/extensions/custom-extensions/node-views/javascript)
---
# Trailing Node extension | Tiptap Editor Docs
This extension adds a node after the last block node in the editor. This can be useful for adding a trailing node like a placeholder or a button.
[](https://tiptap.dev/docs/editor/extensions/functionality/trailing-node#install)
Install
-----------------------------------------------------------------------------------------
npm install @tiptap/extensions
[](https://tiptap.dev/docs/editor/extensions/functionality/trailing-node#usage)
Usage
-------------------------------------------------------------------------------------
import { Editor } from '@tiptap/core'
import { TrailingNode } from '@tiptap/extensions'
new Editor({
extensions: [TrailingNode],
})
[](https://tiptap.dev/docs/editor/extensions/functionality/trailing-node#settings)
Settings
-------------------------------------------------------------------------------------------
### [](https://tiptap.dev/docs/editor/extensions/functionality/trailing-node#node)
node
The node type that should be inserted at the end of the document. When you leave it unset, Trailing Node asks the ProseMirror schema for whatever fallback node it defines (usually the default block node), but you can override that behavior with the `node` option. This is particularly relevant when you define a custom document structure - see the [Forced content structure example](https://tiptap.dev/docs/examples/advanced/forced-content-structure)
for how custom documents may affect the assumed fallback node.
Default: `undefined`
TrailingNode.configure({
node: 'paragraph',
})
### [](https://tiptap.dev/docs/editor/extensions/functionality/trailing-node#notafter)
notAfter
The node types after which the trailing node should not be inserted.
Default: `['paragraph']`
TrailingNode.configure({
node: 'paragraph',
notAfter: ['heading', 'blockquote'],
})
[](https://tiptap.dev/docs/editor/extensions/functionality/trailing-node#source-code)
Source code
-------------------------------------------------------------------------------------------------
[packages/extensions/src/trailing-node/](https://github.com/ueberdosis/tiptap/blob/main/packages/extensions/src/trailing-node/)
[](https://tiptap.dev/docs/editor/extensions/functionality/trailing-node#minimal-install)
Minimal Install
---------------------------------------------------------------------------------------------------------
import { Editor } from '@tiptap/core'
import { TrailingNode } from '@tiptap/extensions/trailing-node'
new Editor({
extensions: [TrailingNode],
})
[PreviouslyText align](https://tiptap.dev/docs/editor/extensions/functionality/textalign)
[Next upTypography](https://tiptap.dev/docs/editor/extensions/functionality/typography)
---
# Install the Editor | Tiptap Editor Docs
Tiptap is framework-agnostic and even works with vanilla JavaScript (if that's your thing). Use the following guides to integrate Tiptap into your JavaScript project.
[\
\
### JavaScript](https://tiptap.dev/docs/editor/getting-started/install/vanilla-javascript)
[\
\
### React](https://tiptap.dev/docs/editor/getting-started/install/react)
[\
\
### Next](https://tiptap.dev/docs/editor/getting-started/install/nextjs)
[\
\
### Vue 3](https://tiptap.dev/docs/editor/getting-started/install/vue3)
[\
\
### Vue 2](https://tiptap.dev/docs/editor/getting-started/install/vue2)
[\
\
### Nuxt](https://tiptap.dev/docs/editor/getting-started/install/nuxt)
[\
\
### Svelte](https://tiptap.dev/docs/editor/getting-started/install/svelte)
[\
\
### Alpine.js](https://tiptap.dev/docs/editor/getting-started/install/alpine)
[\
\
### PHP](https://tiptap.dev/docs/editor/getting-started/install/php)
[\
\
### CDN](https://tiptap.dev/docs/editor/getting-started/install/cdn)
### [](https://tiptap.dev/docs/editor/getting-started/install#community-efforts)
Community efforts
[\
\
### Angular](https://github.com/sibiraj-s/ngx-tiptap)
[\
\
### Solid](https://github.com/LXSMNSYC/solid-tiptap)
[PreviouslyOverview](https://tiptap.dev/docs/editor/getting-started/overview)
[Next upVanilla JavaScript](https://tiptap.dev/docs/editor/getting-started/install/vanilla-javascript)
---
# React | Tiptap Editor Docs
This guide describes how to integrate Tiptap with your React project. We're using Vite, but the workflow should be similar with other setups.
[](https://tiptap.dev/docs/editor/getting-started/install/react#create-a-react-project-optional)
Create a React project (optional)
----------------------------------------------------------------------------------------------------------------------------------
Start with a fresh React project called `my-tiptap-project`. [Vite](https://vitejs.dev/guide/)
will set up everything we need.
# create a project with npm
npm create vite@latest my-tiptap-project -- --template react-ts
# OR, create a project with pnpm
pnpm create vite@latest my-tiptap-project --template react-ts
# OR, create a project with yarn
yarn create vite my-tiptap-project --template react-ts
# change directory
cd my-tiptap-project
[](https://tiptap.dev/docs/editor/getting-started/install/react#install-tiptap-dependencies)
Install Tiptap dependencies
------------------------------------------------------------------------------------------------------------------------
Next, install the `@tiptap/react` package, `@tiptap/pm` (the ProseMirror library), and `@tiptap/starter-kit`, which includes the most common extensions to get started quickly.
* **@tiptap/react**: The React bindings for Tiptap including Tiptap's core functionality.
* **@tiptap/pm**: Tiptap's ProseMirror dependencies, which are required for the editor to function.
* **@tiptap/starter-kit**: A collection of commonly used extensions that provide basic functionality like paragraphs, headings, bold, italic, and more.
npm install @tiptap/react @tiptap/pm @tiptap/starter-kit
If you followed steps 1 and 2, you can now start your project with `npm run dev` and open [http://localhost:3000](http://localhost:3000/)
in your browser.
[](https://tiptap.dev/docs/editor/getting-started/install/react#integrate-tiptap-into-your-react-app)
Integrate Tiptap into your React app
------------------------------------------------------------------------------------------------------------------------------------------
### New: React Composable API
Tiptap now offers a declarative `` component with automatic context management and built-in subcomponents. Perfect for complex UIs with multiple child components. [Learn more →](https://tiptap.dev/docs/guides/react-composable-api)
To start using Tiptap, create a new component. Let's call it `Tiptap` and add the following code in `src/Tiptap.tsx`:
// src/Tiptap.tsx
import { useEditor, EditorContent } from '@tiptap/react'
import { FloatingMenu, BubbleMenu } from '@tiptap/react/menus'
import StarterKit from '@tiptap/starter-kit'
const Tiptap = () => {
const editor = useEditor({
extensions: [StarterKit], // define your extension array
content: '
Hello World!
', // initial content
})
return (
<>
This is the floating menuThis is the bubble menu
>
)
}
export default Tiptap
### [](https://tiptap.dev/docs/editor/getting-started/install/react#add-it-to-your-app)
Add it to your app
Finally, replace the content of `src/App.tsx` with our new `Tiptap` component.
import Tiptap from './Tiptap'
const App = () => {
return (
)
}
export default App
[](https://tiptap.dev/docs/editor/getting-started/install/react#using-the-editorcontext)
Using the EditorContext
----------------------------------------------------------------------------------------------------------------
Tiptap provides a React context called `EditorContext`, that allows you to access the editor instance and its state from anywhere in your component tree. This is particularly useful for building custom toolbars, menus, or other components that need to interact with the editor.
// src/Tiptap.tsx
import { useEditor, EditorContent, EditorContext } from '@tiptap/react'
import { FloatingMenu, BubbleMenu } from '@tiptap/react/menus'
import StarterKit from '@tiptap/starter-kit'
import { useMemo } from 'react'
const Tiptap = () => {
const editor = useEditor({
extensions: [StarterKit], // define your extension array
content: '
Hello World!
', // initial content
})
// Memoize the provider value to avoid unnecessary re-renders
const providerValue = useMemo(() => ({ editor }), [editor])
return (
This is the floating menuThis is the bubble menu
)
}
export default Tiptap
### [](https://tiptap.dev/docs/editor/getting-started/install/react#consume-the-editor-context-in-child-components)
Consume the Editor context in child components
If you use the `EditorProvider` to set up your Tiptap editor, you can now access your editor instance from any child component using the `useCurrentEditor` hook.
import { useCurrentEditor } from '@tiptap/react'
const EditorJSONPreview = () => {
const { editor } = useCurrentEditor()
return
{JSON.stringify(editor.getJSON(), null, 2)}
}
**Important**: This won't work if you use the `useEditor` hook to setup your editor.
You should now see a pretty barebones example of Tiptap in your browser.
[](https://tiptap.dev/docs/editor/getting-started/install/react#reacting-to-editor-state-changes)
Reacting to Editor state changes
----------------------------------------------------------------------------------------------------------------------------------
To react to editor state changes, you can use the `useEditorState` hook from `@tiptap/react`. This hook can be used to fetch information from the editor state without causing re-renders on the editor component or it's children.
import { useEditorState } from '@tiptap/react'
function MyEditorComponent() {
// ... your editor setup code
const editorState = useEditorState({
editor,
// the selector function is used to select the state you want to react to
selector: ({ editor }) => {
if (!editor) return null;
return {
isEditable: editor.isEditable,
currentSelection: editor.state.selection,
currentContent: editor.getJSON(),
// you can add more state properties here e.g.:
// isBold: editor.isActive('bold'),
// isItalic: editor.isActive('italic'),
};
},
});
}
[](https://tiptap.dev/docs/editor/getting-started/install/react#use-ssr-with-react-and-tiptap)
Use SSR with React and Tiptap
----------------------------------------------------------------------------------------------------------------------------
Tiptap can be used with server-side rendering (SSR) in React applications. However, to ensure that the editor is only initialized on the client side, you need to use the `immediatelyRender` option when creating the editor instance to prevent it from rendering on the server.
Here is an example of how to set up Tiptap with SSR in a React component:
'use client'
import { useEditor, EditorContent } from '@tiptap/react'
import StarterKit from '@tiptap/starter-kit'
export function MyEditor() {
const editor = useEditor({
extensions: [StarterKit],
content: '
Hello World!
',
// Disable immediate rendering to prevent SSR issues
immediatelyRender: false,
})
if (!editor) {
return null // Prevent rendering until the editor is initialized
}
return
}
[](https://tiptap.dev/docs/editor/getting-started/install/react#optimize-your-performance)
Optimize your performance
--------------------------------------------------------------------------------------------------------------------
We recommend visiting the [React Performance Guide](https://tiptap.dev/docs/guides/performance)
to integrate the Tiptap Editor efficiently. This will help you avoid potential issues as your app scales.
[](https://tiptap.dev/docs/editor/getting-started/install/react#alternative-composable-react-api)
Alternative: Composable React API
-----------------------------------------------------------------------------------------------------------------------------------
Tiptap also provides a declarative `` component that simplifies editor setup with automatic context management and built-in subcomponents. This composable API is ideal for complex UIs with many child components. Learn more in the [React Composable API guide](https://tiptap.dev/docs/guides/react-composable-api)
.
[](https://tiptap.dev/docs/editor/getting-started/install/react#next-steps)
Next steps
--------------------------------------------------------------------------------------
* [Configure your editor](https://tiptap.dev/docs/editor/getting-started/configure)
* [Add styles to your editor](https://tiptap.dev/docs/editor/getting-started/style-editor)
* [Learn more about Tiptap concepts](https://tiptap.dev/docs/editor/core-concepts/introduction)
* [Learn how to persist the editor state](https://tiptap.dev/docs/editor/core-concepts/persistence)
* [Start building your own extensions](https://tiptap.dev/docs/editor/extensions/custom-extensions)
[PreviouslyVanilla JavaScript](https://tiptap.dev/docs/editor/getting-started/install/vanilla-javascript)
[Next upNext.js](https://tiptap.dev/docs/editor/getting-started/install/nextjs)
---
# Build AI features on the server
Build AI agents with document-editing superpowers on the server without a browser-based editor. The Server AI Toolkit provides flexible primitives and pre-built tools that enable your AI to read, edit, and manipulate Tiptap documents server-side.
### More details
For detailed information on installation, configuration, and comprehensive guides, please visit our [Server AI Toolkit feature page](https://tiptap.dev/docs/content-ai/capabilities/server-ai-toolkit/overview)
.
---