---
title: "Manage threads in your editor"
description: "Manage discussions in Tiptap Editor using threads and comments with editor commands. More in our docs!"
canonical_url: "https://tiptap-docs.lin52025i.qzz.io/comments/core-concepts/manage-threads"
---

# Manage threads in your editor

Manage discussions in Tiptap Editor using threads and comments with editor commands. More in our 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-docs.lin52025i.qzz.io/comments/integrate/editor-commands.md) page.

You can also interact with comments from outside your editor via our [Comments REST API](https://tiptap-docs.lin52025i.qzz.io/comments/integrate/rest-api.md).

To customize what happens when an anchor disappears or a thread changes state, configure [`onThreadAnchorRemoved`](https://tiptap-docs.lin52025i.qzz.io/comments/core-concepts/configure.md#onthreadanchorremoved) and [`onThreadStateChanged`](https://tiptap-docs.lin52025i.qzz.io/comments/core-concepts/configure.md#onthreadstatechanged).

## 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.

## 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.

```js
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.

```js
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.

## Receive and watch threads and comments

Use `subscribeToThreads` from `@tiptap-pro/extension-comments` to keep your thread list up to date. Pass `editor` to include each thread's current document ranges.

The callback receives the initial list immediately. With an editor, later updates are grouped into a microtask after thread data or ranges change. Ranges update after local edits, collaborative edits, and undo or redo. Selection changes alone do not trigger the callback.

```js
import { subscribeToThreads } from '@tiptap-pro/extension-comments'

const unsubscribe = subscribeToThreads({
  editor,
  provider: yourTiptapCollabProvider,
  callback: (threads) => {
    console.log(threads.map((thread) => ({ id: thread.id, ranges: thread.ranges })))
  },
  getThreadsOptions: {
    types: ['archived', 'unarchived'],
  },
})
```

By default, only unarchived threads are returned. Use `getThreadsOptions.types` to include archived threads, which are soft-deleted threads.

With `editor`, the subscription defaults to the editor's collaboration field. Override it with `getThreadsOptions.field`. Field filtering also includes legacy threads that have no field assigned.

Call `unsubscribe()` when you no longer need updates. The subscription also cleans up when the supplied editor is destroyed.

Without `editor`, `subscribeToThreads` returns provider thread records without `ranges` and watches only provider changes.

## Read current thread ranges

Use `getThreads` from `@tiptap-pro/extension-comments` to read thread records with their current anchors in the editor. The editor must have `Comments` or `CommentsKit` installed.

```js
import { getThreads } from '@tiptap-pro/extension-comments'

const threads = getThreads({
  editor,
  provider: yourTiptapCollabProvider,
})
```

Each result keeps the provider's thread data and adds a read-only `ranges` array. The package exports the `ThreadWithPositions<Data, CommentData>` and `ThreadRange` types.

Each range contains these fields:

| Field  | Description                                                            |
| ------ | ---------------------------------------------------------------------- |
| `from` | Start position in the current ProseMirror document.                    |
| `to`   | Exclusive end position in the current ProseMirror document.            |
| `type` | `'inline'` for a marked range or `'block'` for a block thread wrapper. |

Block ranges include the wrapper node. A thread can have several separate anchors, so `ranges` can contain multiple entries ordered by document position. Threads without an anchor in the supplied editor have `ranges: []`.

These positions describe the current document, not permanent anchors. Read them again after edits or use `subscribeToThreads` with `editor` to receive updated ranges.

`getThreads` accepts the same `getThreadsOptions` filters as the subscription and defaults to the editor's collaboration field. It preserves provider order rather than sorting threads by document position.

To display threads in document order, sort by the first range. This example places threads without anchors last and uses the thread ID to break ties:

```js
const firstPosition = (thread) => thread.ranges[0]?.from ?? Infinity

const orderedThreads = [...threads].sort(
  (left, right) => firstPosition(left) - firstPosition(right) || left.id.localeCompare(right.id),
)
```

## 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.

```js
// 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)`.

```js
provider.unwatchThreads(getThreads)
```

The watcher callback also receives the Yjs events and transaction. Use `transaction.local` to distinguish local and remote changes, and pass the same callback to `unwatchThreads` when you unsubscribe:

```js
const handleThreadChanges = (events, transaction) => {
  console.log('Thread changes', events, transaction.local ? 'local' : 'remote')
}

provider.watchThreads(handleThreadChanges)

// Unsubscribe when the watcher is no longer needed.
provider.unwatchThreads(handleThreadChanges)
```

## Keep thread ranges in React state

Use an editor-aware subscription in your React hook to receive both thread data and range updates:

The hook also works without `editor`. In that case, it receives provider thread records without `ranges`.

```jsx
import { subscribeToThreads } from '@tiptap-pro/extension-comments'
import { useEffect, useState } from 'react'

const useThreads = (provider, editor) => {
  const [threads, setThreads] = useState([])

  useEffect(() => {
    if (!provider) {
      return
    }

    return subscribeToThreads({
      editor,
      provider,
      callback: setThreads,
    })
  }, [provider, editor])

  return threads
}
```

Use the returned threads to render your UI. React calls the unsubscribe function when the dependencies change or the component unmounts.

## 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.

```js
editor.commands.updateThread({
  id: '123',
  {
    data: {
      seen: true,
    }
  }
})
```

This will update the thread with the ID `123` and set the `seen` property to `true`.

## 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.

```js
editor.commands.removeThread({
  id: '123',
  deleteThread: true,
})
```

## 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.

```js
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.

```js
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.

```js
editor.commands.deleteComment({
  threadId: '123',
  id: '456',
})
```

## Resolve and unresolve threads

To resolve a thread you can use the `resolveThread` command. This command will resolve the thread with the given ID.

```js
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.

```js
editor.commands.unresolveThread({
  id: '123',
})
```

## 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.

```js
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.

## Select a thread

To select a thread you can use the `selectThread` command. This command selects the thread with the specified ID.

```js
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.

```js
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.
