TinyMCE Editor Integration into CMS
TinyMCE is a mature WYSIWYG editor with rich plugin ecosystem. Suitable for non-technical users who need to format content without HTML knowledge. Cloud version (API key, CDN) and self-hosted available.
Installation (self-hosted)
npm install tinymce
import { Editor } from '@tinymce/tinymce-react';
function ContentEditor({ value, onChange }) {
return (
<Editor
tinymceScriptSrc="/tinymce/tinymce.min.js"
value={value}
onEditorChange={(content) => onChange(content)}
init={{
height: 600,
language: 'en',
plugins: [
'advlist', 'autolink', 'lists', 'link', 'image',
'charmap', 'preview', 'anchor', 'searchreplace',
'visualblocks', 'code', 'fullscreen', 'insertdatetime',
'media', 'table', 'wordcount'
],
toolbar: 'undo redo | formatselect | bold italic | '
+ 'alignleft aligncenter alignright | '
+ 'bullist numlist | link image media | code fullscreen',
content_css: '/css/editor-content.css',
images_upload_handler: async (blobInfo) => {
const formData = new FormData();
formData.append('image', blobInfo.blob(), blobInfo.filename());
const response = await fetch('/api/media/upload', {
method: 'POST',
body: formData
});
const data = await response.json();
return data.url;
}
}}
/>
);
}
Image Upload
TinyMCE supports three approaches:
-
images_upload_handler— custom upload function (shown above) -
images_upload_url— endpoint URL, TinyMCE sends POST itself - Media manager via
tinydriveplugin (paid)
HTML Sanitization on Server
TinyMCE generates HTML saved to database. Before saving—mandatory sanitization:
// composer require ezyang/htmlpurifier
$config = HTMLPurifier_Config::createDefault();
$config->set('HTML.Allowed', 'p,br,strong,em,a[href|title],ul,ol,li,h2,h3,h4,blockquote,img[src|alt|width|height],table,thead,tbody,tr,th,td');
$config->set('AutoFormat.AutoParagraph', false);
$purifier = new HTMLPurifier($config);
$cleanHtml = $purifier->purify($request->content);
Styling Content on Public Site
HTML from TinyMCE needs styling on public site via prose classes (Tailwind Typography):
<div
className="prose prose-lg max-w-none"
dangerouslySetInnerHTML={{ __html: sanitizedContent }}
/>
Integration timeline: 1–2 working days with media upload and server sanitization.







