> ## Documentation Index
> Fetch the complete documentation index at: https://velt.dev/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# TinyMCE Setup

> Add Velt text comments to a TinyMCE editor with the Velt TinyMCE comments plugin.

<img src="https://mintcdn.com/velt/gAz_vLsG-ukKamYM/gifs/Add-Text-Comments.gif?s=1da499b73486c2acd4667b517baab389" alt="Add text comments in an editor" width="1280" height="720" data-path="gifs/Add-Text-Comments.gif" />

The TinyMCE integration renders comment highlights as view-only overlay elements positioned over the commented text. It does not modify your TinyMCE content, schema, undo history, or saved HTML.

## Setup

#### Step 1: Add Comment components

* Add the `Velt Comments` component to the root of your app. This component is required to create and render comments in your app.
* Authenticate the user with `authProvider` and set the Velt document before users add comments.
* Set the `textMode` prop to `false` to hide the default Velt text comment tool. TinyMCE selections are handled by `@veltdev/tinymce-velt-comments`.
* Add `VeltCommentsSidebar` if you want a Google Docs-style comment sidebar.

<Tabs>
  <Tab title="React / Next.js">
    ```tsx theme={null}
    import { VeltComments, VeltCommentsSidebar, VeltProvider, useSetDocument } from '@veltdev/react';

    const user = {
      userId: 'user-1',
      organizationId: 'org-1',
      name: 'User One',
      email: 'user@example.com',
    };

    function VeltSetup() {
      useSetDocument('document-id', { documentName: 'Document name' });
      return null;
    }

    function App() {
      return (
        <VeltProvider apiKey="API_KEY" authProvider={{ user }}>
          <VeltSetup />
          <VeltComments textMode={false} />
          <VeltCommentsSidebar />
          {/* Your app content */}
        </VeltProvider>
      );
    }
    ```
  </Tab>

  <Tab title="Other Frameworks">
    ```html theme={null}
    <body>
      <velt-comments text-mode="false"></velt-comments>
    </body>
    ```
  </Tab>
</Tabs>

#### Step 2: Install the Velt TinyMCE extension

<Tabs>
  <Tab title="React / Next.js">
    ```bash theme={null}
    npm i @veltdev/tinymce-velt-comments @tinymce/tinymce-react tinymce
    ```
  </Tab>

  <Tab title="Other Frameworks">
    ```bash theme={null}
    npm i @veltdev/tinymce-velt-comments @veltdev/client tinymce
    ```
  </Tab>
</Tabs>

`tinymce` is a peer dependency and must be provided by your TinyMCE app. Import the self-hosted TinyMCE assets used by your editor so `@tinymce/tinymce-react` and the Velt plugin use the same TinyMCE instance.

#### Step 3: Configure the TinyMCE editor with the Velt Comments plugin

Add `VeltCommentsPlugin` to the TinyMCE `plugins` list and set `velt_comments_editor_id` in the editor init options. Capture the TinyMCE editor instance from `onInit` or the `init` event, then render Velt comment annotations into the editor with `renderComments`.

<Tabs>
  <Tab title="React / Next.js">
    ```tsx theme={null}
    import { useEffect, useState } from 'react';
    import { Editor as TinyMCEReact } from '@tinymce/tinymce-react';
    import { useCommentAnnotations } from '@veltdev/react';
    import type { Editor as TinyMCEEditor } from 'tinymce';
    import { VeltCommentsPlugin, renderComments } from '@veltdev/tinymce-velt-comments';

    import 'tinymce';
    import 'tinymce/icons/default';
    import 'tinymce/themes/silver';
    import 'tinymce/models/dom';
    import 'tinymce/plugins/lists';
    import 'tinymce/plugins/link';
    import 'tinymce/skins/ui/oxide/skin.min.css';
    import contentUiCss from 'tinymce/skins/ui/oxide/content.min.css?raw';
    import contentCss from 'tinymce/skins/content/default/content.min.css?raw';

    const EDITOR_ID = 'my-editor';

    function TinyMCEEditor() {
      const [editor, setEditor] = useState<TinyMCEEditor | null>(null);
      const annotations = useCommentAnnotations();

      useEffect(() => {
        if (!editor) return;

        renderComments({
          editor,
          editorId: EDITOR_ID,
          commentAnnotations: annotations ?? [],
        });
      }, [editor, annotations]);

      return (
        <TinyMCEReact
          licenseKey="gpl"
          initialValue="<p>Select text, then add a comment.</p>"
          init={{
            license_key: 'gpl',
            skin: false,
            content_css: false,
            content_style: `${contentUiCss}\n${contentCss}`,
            plugins: ['lists', 'link', VeltCommentsPlugin],
            toolbar: 'undo redo | bold italic | bullist numlist | link | addveltcomment',
            velt_comments_editor_id: EDITOR_ID,
          }}
          onInit={(_event, nextEditor) => setEditor(nextEditor)}
        />
      );
    }
    ```
  </Tab>

  <Tab title="Other Frameworks">
    ```js theme={null}
    import tinymce from 'tinymce';
    import { VeltCommentsPlugin } from '@veltdev/tinymce-velt-comments';

    let editor;

    tinymce.init({
      selector: '#editor',
      license_key: 'gpl',
      plugins: [VeltCommentsPlugin],
      toolbar: 'undo redo | bold italic | addveltcomment',
      velt_comments_editor_id: 'my-editor',
      setup: (nextEditor) => {
        nextEditor.on('init', () => {
          editor = nextEditor;
        });
      },
    });
    ```
  </Tab>
</Tabs>

<Tip>
  The package registers a TinyMCE plugin named `veltcomments`. You can pass the exported `VeltCommentsPlugin` constant in `plugins`, or use the string `'veltcomments'` after the package has been imported. It also registers a TinyMCE toolbar button named `addveltcomment`.
</Tip>

#### Step 4: Add a comment button to your TinyMCE editor

Add a button that users can click to add comments after selecting text in the TinyMCE editor.

**Important:** Use `onMouseDown` with `preventDefault()` so the browser does not move focus away from the editor before `addComment` reads the current selection. Keep the actual `addComment` call in `onClick`.

<Tabs>
  <Tab title="React / Next.js">
    ```tsx theme={null}
    import { addComment } from '@veltdev/tinymce-velt-comments';

    const handleAddComment = () => {
      if (!editor) return;

      void addComment({
        editor,
        editorId: EDITOR_ID,
      });
    };

    <button
      onMouseDown={(event) => event.preventDefault()}
      onClick={handleAddComment}
    >
      Add Comment
    </button>
    ```
  </Tab>

  <Tab title="Other Frameworks">
    ```html theme={null}
    <button id="add-comment">Add Comment</button>
    <div id="editor"></div>
    ```

    ```js theme={null}
    import { addComment } from '@veltdev/tinymce-velt-comments';

    const addCommentButton = document.querySelector('#add-comment');

    addCommentButton.addEventListener('mousedown', (event) => {
      event.preventDefault();
    });

    addCommentButton.addEventListener('click', () => {
      void addComment({ editor, editorId: 'my-editor' });
    });
    ```
  </Tab>
</Tabs>

#### Step 5: Call `addComment` to add a comment

* Call this method to add a comment to selected text in the TinyMCE editor.
* Params: [`AddCommentRequest`](/api-reference/sdk/models/data-models#addcommentrequest-9). It has the following properties:
  * `editor`: TinyMCE `Editor` instance.
  * `editorId`: Id of the editor. Use this if you have multiple TinyMCE editors on the same page. (optional)
  * `context`: Add custom metadata to the Comment Annotation. [Learn more](/async-collaboration/comments/customize-behavior#metadata). (optional)

<Tabs>
  <Tab title="React / Next.js">
    ```tsx theme={null}
    import { addComment } from '@veltdev/tinymce-velt-comments';

    const addCommentRequest = {
      editor,
      editorId: 'EDITOR_ID',
      context: {
        storyId: 'story-id',
        storyName: 'story-name',
      },
    };

    void addComment(addCommentRequest);
    ```
  </Tab>

  <Tab title="Other Frameworks">
    ```js theme={null}
    import { addComment } from '@veltdev/tinymce-velt-comments';

    const addCommentRequest = {
      editor,
      editorId: 'EDITOR_ID',
      context: {
        storyId: 'story-id',
        storyName: 'story-name',
      },
    };

    void addComment(addCommentRequest);
    ```
  </Tab>
</Tabs>

The library automatically writes `context.textEditorConfig` with the selected text, its 1-based occurrence index in the document, and the editor ID when one is provided. If the editor is inside an element with `data-velt-location-id`, that value is sent to Velt as the annotation location.

#### Step 6: Render comments in TinyMCE editor

* Get the comment data from Velt SDK and render it in the TinyMCE editor.
* Params: [`RenderCommentsRequest`](/api-reference/sdk/models/data-models#rendercommentsrequest-9). It has the following properties:
  * `editor`: TinyMCE `Editor` instance.
  * `editorId`: Id of the editor. Use this if you have multiple TinyMCE editors on the same page. (optional)
  * `commentAnnotations`: Array of Comment Annotation objects.

<Tabs>
  <Tab title="React / Next.js">
    ```tsx theme={null}
    import { useEffect } from 'react';
    import { useCommentAnnotations } from '@veltdev/react';
    import { renderComments } from '@veltdev/tinymce-velt-comments';

    const commentAnnotations = useCommentAnnotations();

    useEffect(() => {
      if (!editor) return;

      renderComments({
        editor,
        editorId: 'EDITOR_ID',
        commentAnnotations: commentAnnotations ?? [],
      });
    }, [editor, commentAnnotations]);
    ```
  </Tab>

  <Tab title="Other Frameworks">
    ```js theme={null}
    import { renderComments } from '@veltdev/tinymce-velt-comments';

    const commentElement = Velt.getCommentElement();
    commentElement.getAllCommentAnnotations().subscribe((annotations) => {
      renderComments({
        editor,
        editorId: 'EDITOR_ID',
        commentAnnotations: annotations,
      });
    });
    ```
  </Tab>
</Tabs>

`renderComments` filters annotations by `context.textEditorConfig.editorId`. Resolved comments with `status.type === 'terminal'` are hidden unless they are currently selected in Velt.

#### Step 7: Re-apply TinyMCE comment highlights (optional)

* TinyMCE renders comment highlights as view-only overlay elements in the outer document.
* The integration does not write Velt comment marks into your TinyMCE content or undo history.
* Save your TinyMCE content normally. After you recreate the editor or replace content with `editor.setContent(...)`, call `renderComments` again.

```ts theme={null}
const html = editor.getContent();
// Save html to your backend.

renderComments({
  editor,
  editorId: 'EDITOR_ID',
  commentAnnotations,
});
```

#### Step 8: Style the commented text

* You can style the commented text by adding CSS for the `velt-comment-text` element.
* TinyMCE highlights are overlay elements in the outer document, not inside the editor iframe, so add this CSS to your page stylesheet instead of `content_style`.

```css theme={null}
velt-comment-text {
  background-color: rgba(255, 212, 0, 0.4);
  border-bottom: 2px solid #ffd400;
  cursor: pointer;
}

velt-comment-text:hover {
  background-color: rgba(255, 212, 0, 0.6);
}

velt-comment-text[comment-selected="true"],
velt-comment-text.velt-comment-selected {
  background-color: rgba(255, 212, 0, 0.7);
}
```

#### Using TinyMCE without React

For a vanilla TinyMCE app, initialize Velt with `@veltdev/client`, add the Velt comments web component to the page, and subscribe to annotations through the Velt comment element.

```bash theme={null}
npm install @veltdev/client
```

```html theme={null}
<velt-comments text-mode="false"></velt-comments>
<button id="add-comment-btn">Add Comment</button>
<div id="editor"></div>
```

```ts theme={null}
import { initVelt } from '@veltdev/client';
import tinymce from 'tinymce';
import {
  VeltCommentsPlugin,
  addComment,
  renderComments,
} from '@veltdev/tinymce-velt-comments';

const EDITOR_ID = 'my-editor';

const velt = await initVelt('YOUR_API_KEY');

await velt.identify({
  userId: 'user-1',
  organizationId: 'org-1',
  name: 'User One',
  email: 'user@example.com',
});

velt.setDocument('document-id', { documentName: 'Document name' });

const [editor] = await tinymce.init({
  selector: '#editor',
  license_key: 'gpl',
  plugins: [VeltCommentsPlugin],
  toolbar: 'bold italic | addveltcomment',
  velt_comments_editor_id: EDITOR_ID,
});

document.getElementById('add-comment-btn')?.addEventListener('mousedown', (event) => {
  event.preventDefault();
});

document.getElementById('add-comment-btn')?.addEventListener('click', () => {
  void addComment({ editor, editorId: EDITOR_ID });
});

velt.getCommentElement()?.getAllCommentAnnotations().subscribe((annotations) => {
  renderComments({ editor, editorId: EDITOR_ID, commentAnnotations: annotations });
});
```

#### Multiple TinyMCE editors

When using multiple editors on the same page, provide a unique `editorId` to TinyMCE's `velt_comments_editor_id` init option, `addComment`, and `renderComments`.

```ts theme={null}
const [editor1] = await tinymce.init({
  target: document.querySelector('#editor-1') as HTMLElement,
  license_key: 'gpl',
  plugins: [VeltCommentsPlugin],
  toolbar: 'addveltcomment',
  velt_comments_editor_id: 'editor-1',
});

const [editor2] = await tinymce.init({
  target: document.querySelector('#editor-2') as HTMLElement,
  license_key: 'gpl',
  plugins: [VeltCommentsPlugin],
  toolbar: 'addveltcomment',
  velt_comments_editor_id: 'editor-2',
});

await addComment({ editor: editor1, editorId: 'editor-1' });
await addComment({ editor: editor2, editorId: 'editor-2' });

renderComments({
  editor: editor1,
  editorId: 'editor-1',
  commentAnnotations,
});

renderComments({
  editor: editor2,
  editorId: 'editor-2',
  commentAnnotations,
});
```

#### TypeScript support

The package includes TypeScript definitions. Key type exports:

```ts theme={null}
import type {
  AddCommentRequest,
  RenderCommentsRequest,
  TinymceVeltCommentsConfig,
  CommentAnnotationContext,
} from '@veltdev/tinymce-velt-comments';
import type { CommentAnnotation } from '@veltdev/types';
```

Runtime exports:

```ts theme={null}
import {
  VeltCommentsPlugin,
  registerVeltCommentsPlugin,
  addComment,
  renderComments,
} from '@veltdev/tinymce-velt-comments';
```

## Complete Example

<Frame>
  <iframe src="https://sample-apps-tinymce-comments-demo.vercel.app/" className="w-full" height="500px" />
</Frame>

## APIs

#### [VeltCommentsPlugin](/api-reference/sdk/api/api-methods#veltcommentsplugin-1)

The registered TinyMCE plugin name (`'veltcomments'`) that powers Velt comments. Importing the library registers it; add it to the TinyMCE editor's `plugins` list.

**Init options:**

* `velt_comments_editor_id?: string` - Unique identifier for this editor instance.
* `velt_comments_persist_marks?: boolean` - Reserved for API parity; highlights are always rendered as view-only overlays.

<Tabs>
  <Tab title="React / Next.js">
    ```tsx theme={null}
    import { VeltCommentsPlugin } from '@veltdev/tinymce-velt-comments';

    <TinyMCEReact
      licenseKey="gpl"
      init={{
        license_key: 'gpl',
        plugins: ['lists', 'link', VeltCommentsPlugin],
        toolbar: 'bold italic | addveltcomment',
        velt_comments_editor_id: 'my-editor',
      }}
    />
    ```
  </Tab>

  <Tab title="Other Frameworks">
    ```js theme={null}
    import { VeltCommentsPlugin } from '@veltdev/tinymce-velt-comments';

    tinymce.init({
      selector: '#editor',
      license_key: 'gpl',
      plugins: [VeltCommentsPlugin],
      toolbar: 'bold italic | addveltcomment',
      velt_comments_editor_id: 'my-editor',
    });
    ```
  </Tab>
</Tabs>

#### [registerVeltCommentsPlugin()](/api-reference/sdk/api/api-methods#registerveltcommentsplugin)

Explicitly registers the `veltcomments` plugin with a TinyMCE instance. Importing the package auto-registers the plugin when a TinyMCE instance is available; use this method when TinyMCE is loaded or provided later.

**Parameters:**

* `instance?: TinyMCE` - TinyMCE instance to register against.

**Returns:** `void`

```ts theme={null}
import tinymce from 'tinymce';
import { registerVeltCommentsPlugin } from '@veltdev/tinymce-velt-comments';

registerVeltCommentsPlugin(tinymce);
```

#### [addComment()](/api-reference/sdk/api/api-methods#addcomment-10)

Creates a Velt comment annotation for the currently selected TinyMCE text.

* Signature: `async (request:` [`AddCommentRequest`](/api-reference/sdk/models/data-models#addcommentrequest-9)`) => Promise<void>`
* Params: `request:` [`AddCommentRequest`](/api-reference/sdk/models/data-models#addcommentrequest-9)
* Returns: `Promise<void>`

<Tabs>
  <Tab title="React / Next.js">
    ```tsx theme={null}
    import { addComment } from '@veltdev/tinymce-velt-comments';

    const handleAddComment = () => {
      if (!editor) return;
      void addComment({ editor, editorId: 'my-editor' });
    };

    <button
      onMouseDown={(event) => event.preventDefault()}
      onClick={handleAddComment}
    >
      Comment
    </button>
    ```
  </Tab>

  <Tab title="Other Frameworks">
    ```js theme={null}
    import { addComment } from '@veltdev/tinymce-velt-comments';

    void addComment({
      editor,
      editorId: 'my-editor',
      context: { customData: 'value' },
    });
    ```
  </Tab>
</Tabs>

#### [renderComments()](/api-reference/sdk/api/api-methods#rendercomments-10)

Resolves Velt comment annotations back to TinyMCE DOM ranges and renders stable `<velt-comment-text>` overlay elements for the visible comments.

* Signature: `(request:` [`RenderCommentsRequest`](/api-reference/sdk/models/data-models#rendercommentsrequest-9)`) => void`
* Params: `request:` [`RenderCommentsRequest`](/api-reference/sdk/models/data-models#rendercommentsrequest-9)
* Returns: `void`

<Tabs>
  <Tab title="React / Next.js">
    ```tsx theme={null}
    import { useEffect } from 'react';
    import { useCommentAnnotations } from '@veltdev/react';
    import { renderComments } from '@veltdev/tinymce-velt-comments';

    const annotations = useCommentAnnotations();

    useEffect(() => {
      if (!editor) return;

      renderComments({
        editor,
        editorId: 'my-editor',
        commentAnnotations: annotations ?? [],
      });
    }, [editor, annotations]);
    ```
  </Tab>

  <Tab title="Other Frameworks">
    ```js theme={null}
    import { renderComments } from '@veltdev/tinymce-velt-comments';

    const commentElement = Velt.getCommentElement();
    commentElement.getAllCommentAnnotations().subscribe((annotations) => {
      renderComments({
        editor,
        editorId: 'my-editor',
        commentAnnotations: annotations,
      });
    });
    ```
  </Tab>
</Tabs>
