Skip to main content
Add text comments in an editor 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.
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>
  );
}

Step 2: Install the Velt TinyMCE extension

npm i @veltdev/tinymce-velt-comments @tinymce/tinymce-react tinymce
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.
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)}
    />
  );
}
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.

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

Step 5: Call addComment to add a comment

  • Call this method to add a comment to selected text in the TinyMCE editor.
  • Params: AddCommentRequest. 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. (optional)
import { addComment } from '@veltdev/tinymce-velt-comments';

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

void addComment(addCommentRequest);
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. 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.
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]);
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.
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.
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.
npm install @veltdev/client
<velt-comments text-mode="false"></velt-comments>
<button id="add-comment-btn">Add Comment</button>
<div id="editor"></div>
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.
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:
import type {
  AddCommentRequest,
  RenderCommentsRequest,
  TinymceVeltCommentsConfig,
  CommentAnnotationContext,
} from '@veltdev/tinymce-velt-comments';
import type { CommentAnnotation } from '@veltdev/types';
Runtime exports:
import {
  VeltCommentsPlugin,
  registerVeltCommentsPlugin,
  addComment,
  renderComments,
} from '@veltdev/tinymce-velt-comments';

Complete Example

APIs

VeltCommentsPlugin

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.
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',
  }}
/>

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
import tinymce from 'tinymce';
import { registerVeltCommentsPlugin } from '@veltdev/tinymce-velt-comments';

registerVeltCommentsPlugin(tinymce);

addComment()

Creates a Velt comment annotation for the currently selected TinyMCE text.
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>

renderComments()

Resolves Velt comment annotations back to TinyMCE DOM ranges and renders stable <velt-comment-text> overlay elements for the visible comments.
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]);