Tiptap Editor Integration into CMS
Tiptap is a headless rich-text editor based on ProseMirror. Unlike TinyMCE and CKEditor, it has no built-in UI—you build the interface yourself. This provides full control over appearance but requires more startup work. Ideal for React projects with custom design.
Installation
npm install @tiptap/react @tiptap/pm @tiptap/starter-kit
npm install @tiptap/extension-image @tiptap/extension-link @tiptap/extension-placeholder
Basic Editor
import { useEditor, EditorContent } from '@tiptap/react';
import StarterKit from '@tiptap/starter-kit';
import Image from '@tiptap/extension-image';
import Link from '@tiptap/extension-link';
function TiptapEditor({ content, onChange }) {
const editor = useEditor({
extensions: [
StarterKit,
Image.configure({ inline: false }),
Link.configure({ openOnClick: false })
],
content,
onUpdate: ({ editor }) => onChange(editor.getHTML())
});
if (!editor) return null;
return (
<div className="border rounded-lg">
<EditorToolbar editor={editor} />
<EditorContent editor={editor} className="prose max-w-none p-4" />
</div>
);
}
Custom Toolbar
function EditorToolbar({ editor }) {
return (
<div className="flex gap-1 p-2 border-b">
<button
onClick={() => editor.chain().focus().toggleBold().run()}
className={editor.isActive('bold') ? 'bg-gray-200 rounded' : ''}
>
<BoldIcon className="w-4 h-4" />
</button>
<button onClick={() => editor.chain().focus().toggleItalic().run()}>
<ItalicIcon className="w-4 h-4" />
</button>
<button
onClick={() => {
const url = window.prompt('URL:');
if (url) editor.chain().focus().setLink({ href: url }).run();
}}
>
<LinkIcon className="w-4 h-4" />
</button>
{/* Image upload button */}
<button onClick={() => document.getElementById('image-upload').click()}>
<ImageIcon className="w-4 h-4" />
</button>
<input
id="image-upload"
type="file"
className="hidden"
accept="image/*"
onChange={async (e) => {
const file = e.target.files[0];
const url = await uploadImage(file);
editor.chain().focus().setImage({ src: url }).run();
}}
/>
</div>
);
}
Custom Nodes (content blocks)
Tiptap lets you create custom blocks—e.g., "Quote with Author" block:
import { Node, mergeAttributes } from '@tiptap/core';
import { ReactNodeViewRenderer } from '@tiptap/react';
const QuoteBlock = Node.create({
name: 'quoteBlock',
group: 'block',
content: 'inline*',
addAttributes() {
return {
author: { default: null },
position: { default: null }
};
},
parseHTML() {
return [{ tag: 'blockquote[data-type="quote-block"]' }];
},
renderHTML({ HTMLAttributes }) {
return ['blockquote', mergeAttributes(HTMLAttributes, { 'data-type': 'quote-block' }), 0];
},
addNodeView() {
return ReactNodeViewRenderer(QuoteBlockComponent);
}
});
JSON vs HTML Storage
Tiptap can save content as JSON (native ProseMirror format) or HTML. JSON is preferable: it's structured, doesn't require sanitization, easy to transform:
// Save as JSON
const jsonContent = editor.getJSON();
// On display — convert JSON → HTML on server-side
// or use generateHTML from @tiptap/html
import { generateHTML } from '@tiptap/html';
const html = generateHTML(jsonContent, [StarterKit, Image, Link]);
Collaborative Editing
Tiptap supports collaborative editing via Yjs:
npm install @tiptap/extension-collaboration @tiptap/extension-collaboration-cursor yjs y-websocket
Integration timeline: 2–3 days for full-featured editor with toolbar, image upload, and custom blocks.







