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
- 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.
React / Next.js
Other Frameworks
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>
);
}
<body>
<velt-comments text-mode="false"></velt-comments>
</body>
Step 2: Install the Velt TinyMCE extension
React / Next.js
Other Frameworks
npm i @veltdev/tinymce-velt-comments @tinymce/tinymce-react tinymce
npm i @veltdev/tinymce-velt-comments @veltdev/client 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.
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.
React / Next.js
Other Frameworks
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)}
/>
);
}
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;
});
},
});
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.
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.
React / Next.js
Other Frameworks
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>
<button id="add-comment">Add Comment</button>
<div id="editor"></div>
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' });
});
- 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)
React / Next.js
Other Frameworks
import { addComment } from '@veltdev/tinymce-velt-comments';
const addCommentRequest = {
editor,
editorId: 'EDITOR_ID',
context: {
storyId: 'story-id',
storyName: 'story-name',
},
};
void addComment(addCommentRequest);
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.
- 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.
React / Next.js
Other Frameworks
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]);
import { renderComments } from '@veltdev/tinymce-velt-comments';
const commentElement = Velt.getCommentElement();
commentElement.getAllCommentAnnotations().subscribe((annotations) => {
renderComments({
editor,
editorId: 'EDITOR_ID',
commentAnnotations: annotations,
});
});
renderComments filters annotations by context.textEditorConfig.editorId. Resolved comments with status.type === 'terminal' are hidden unless they are currently selected in Velt.
- 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,
});
- 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
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.
React / Next.js
Other Frameworks
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',
}}
/>
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',
});
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);
Creates a Velt comment annotation for the currently selected TinyMCE text.
React / Next.js
Other Frameworks
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>
import { addComment } from '@veltdev/tinymce-velt-comments';
void addComment({
editor,
editorId: 'my-editor',
context: { customData: 'value' },
});
Resolves Velt comment annotations back to TinyMCE DOM ranges and renders stable <velt-comment-text> overlay elements for the visible comments.
React / Next.js
Other Frameworks
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]);
import { renderComments } from '@veltdev/tinymce-velt-comments';
const commentElement = Velt.getCommentElement();
commentElement.getAllCommentAnnotations().subscribe((annotations) => {
renderComments({
editor,
editorId: 'my-editor',
commentAnnotations: annotations,
});
});