Migration guide
Migrate from TinyMCE or CKEditor
Most teams finish in under a day. Existing HTML content keeps working, toolbar strings map mostly 1→1, and the features you’re paying a premium tier for ship in our base license. Below: side-by-side config mapping, feature-equivalence tables, and a step-by-step checklist.
~1 daytypical migration time for a single-app deployment
$0 / yearperpetual license after the one-time purchase
0 features lostfull v2.0 collab + AI stack is base-license
Why teams move
- Perpetual license. No subscription, no per-load metering, no AI usage-credit invoices.
- One SKU for everything. Track Changes, Comments, Revision History, Mentions, AI — all in the base license. TinyMCE bills these separately.
- Self-host on the entry tier. TinyMCE gates self-hosting to Enterprise.
- No proxy for AI. TinyMCE AI requires a server proxy. Our
editor.aiToolkit.setResolver(fn) is client-side; for ASP.NET Core use IRichTextBoxAiResolver.
Step-by-step checklist
- Install the npm package:
npm install @@richscripts2/richtexteditor
- Replace your TinyMCE
<script src="tinymce.min.js"> with <link rel="stylesheet" href="richtexteditor/rte_theme_default.css"> + <script src="richtexteditor/rte.js">.
- Remove the TinyMCE init script (
tinymce.init({ selector: ... })) and replace the <textarea> with <div id="editor">.
- Instantiate:
new RichTextEditor("#editor", { toolbar: "full" });
- Map your TinyMCE
plugins: and toolbar: to our toolbar config — see the cheat sheet below.
- Wire your AI resolver via
editor.aiToolkit.setResolver(async (req) => { ... }). No proxy needed.
- Existing HTML content keeps working; submit the form as normal.
Config cheat sheet
Basic setup
TinyMCE
tinymce.init({
selector: '#editor',
plugins: 'lists link image table code',
toolbar: 'bold italic | bullist numlist | link image',
height: 500
});
RichTextEditor
new RichTextEditor("#editor", {
toolbar: "custom",
toolbar_custom:
"{bold,italic}|{insertunorderedlist,insertorderedlist}"
+ "|{insertlink,insertimage}",
height: "500px"
});
AI integration
TinyMCE AI (requires proxy)
tinymce.init({
plugins: 'ai',
toolbar: 'ai',
ai_request: (request, respondWith) =>
respondWith.stream(/* proxy fetch */)
});
RichTextEditor (client-side resolver)
var editor = new RichTextEditor("#editor", {
aiToolkitEnabled: true
});
editor.aiToolkit.setResolver(async (req) => {
const r = await fetch("/api/my-ai", {
method: "POST",
body: JSON.stringify(req)
});
return r.json();
});
Track Changes / Comments / Revision History
Three separate TinyMCE premium add-ons → three config flags.
TinyMCE (premium)
tinymce.init({
plugins: 'tinycomments revisionhistory',
tinycomments_author: 'User',
// Track Changes = separate license
});
RichTextEditor (base)
new RichTextEditor("#editor", {
trackChangesEnabled: true,
commentsEnabled: true,
revisionHistoryEnabled: true,
currentUser: {
id: "maya",
name: "Maya Patel",
color: "#9333ea"
}
});
Plugin equivalence
| TinyMCE plugin | RichTextEditor equivalent |
link | built-in (insertlink) |
image, imagetools | built-in (insertimage, imageeditor) |
media | built-in (insertvideo, insertyoutube) |
table | built-in (inserttable) |
lists, advlist | built-in (insertorderedlist, insertunorderedlist, insertchecklist) |
codesample | built-in (insertcode, syntaxhighlighter) |
emoticons | built-in (insertemoji) |
template | built-in (inserttemplate) |
mentions (premium) | mentionEnabled: true (base) |
tinycomments (premium) | commentsEnabled: true (base) |
tinymcespellchecker (premium) | native browser spellcheck |
revisionhistory (premium) | revisionHistoryEnabled: true (base) |
| Track Changes (premium) | trackChangesEnabled: true (base) |
ai (premium + usage credits) | aiToolkitEnabled: true + resolver |
exportword (premium) | editor.aiToolkit.exportDocx() helper (when server endpoint is wired) |
exportpdf (premium) | html2pdf toolbar item (client-side) |
Behavior differences to know about
HTML output. TinyMCE output uses lower-case element names and non-breaking spaces in empty paragraphs. Our output is case-normalized but otherwise equivalent. Customize via config.filterhtml.
Events. TinyMCE’s change event → editor.attachEvent("change", fn). Our new RichTextEditor(...) is synchronous; bind events right after construction.
Content CSS. TinyMCE content_css: "..." → our config.contentCssUrl (URL) or config.contentCssText (inline).
What you gain
- Dictation plugin — TinyMCE doesn’t ship one.
- Slash commands (Notion-style
/ picker) — more mature than TinyMCE’s autocompleter API.
- Shared review ledger — AI suggestions + human track-changes + comments in one drawer. TinyMCE keeps these siloed.
- React, Vue, Angular, Blazor Server, ASP.NET Core, Web Forms, Classic ASP, PHP bindings.
Why teams move
- No webpack / custom build. CKEditor 5 requires a build pipeline to mix features. We ship one bundle and toggle features via config.
- Perpetual license vs CKEditor’s five separate subscription SKUs (Real-time Collab, Track Changes, Comments, Revision History, AI).
- Self-host on entry tier — CKEditor’s enterprise features are Custom-tier pricing.
- Wider framework coverage — Blazor, Web Forms, Classic ASP, PHP, and more.
Step-by-step checklist
- Install the npm package:
npm install @@richscripts2/richtexteditor
- Remove your CKEditor custom build / webpack config entirely.
- Replace
ClassicEditor.create(...) with new RichTextEditor("#editor", { toolbar: "full" });
- Map your CKEditor
toolbar: [...] items to our toolbar config (see cheat sheet).
- Collaboration: replace CKEditor Cloud Services with
editor.collab.attach({ doc, provider, textSync: true }) and self-host Yjs + WebSocket provider.
- AI: swap
cloudServices config for editor.aiToolkit.setResolver(fn).
- Paste existing CKEditor content directly — HTML output is compatible.
Config cheat sheet
Basic setup
CKEditor 5 (custom build)
import ClassicEditor from
'@@ckeditor/ckeditor5-build-classic';
ClassicEditor.create(
document.querySelector('#editor'),
{
toolbar: {
items: ['bold','italic','|',
'bulletedList','numberedList','|',
'link','imageUpload']
}
}
);
RichTextEditor (no build step)
<link rel="stylesheet"
href="richtexteditor/rte_theme_default.css">
<script src="richtexteditor/rte.js"></script>
<div id="editor"></div>
<script>
new RichTextEditor("#editor", {
toolbar: "custom",
toolbar_custom:
"{bold,italic}|{insertunorderedlist,insertorderedlist}"
+ "|{insertlink,insertimage}"
});
</script>
Real-time collaboration
CKEditor Cloud Services (paid)
ClassicEditor.create(el, {
cloudServices: {
tokenUrl: '/api/cke-token',
webSocketUrl:
'wss://cs.cke-cs.com/...'
},
collaboration: { channelId: docId }
});
RichTextEditor + Yjs (self-host)
import * as Y from "yjs";
import { WebsocketProvider } from "y-websocket";
const doc = new Y.Doc();
const provider = new WebsocketProvider(
"wss://your-server", docId, doc);
editor.collab.attach({
doc, provider,
user: { id, name, color },
textSync: true // concurrent typing preview
});
AI Assistant
CKEditor AI (subscription + credits)
ClassicEditor.create(el, {
ai: {
openAI: {
requestHeaders: { Authorization: '...' },
apiUrl: 'https://api.openai.com/v1/...'
}
}
});
RichTextEditor (any provider)
var editor = new RichTextEditor("#editor", {
aiToolkitEnabled: true
});
editor.aiToolkit.setResolver(async (req) => {
const r = await fetch("/api/my-ai", {
method: "POST",
body: JSON.stringify(req)
});
return r.json();
});
Plugin equivalence
| CKEditor 5 feature | RichTextEditor equivalent |
Bold, Italic, Underline | built-in (bold, italic, underline) |
Link / LinkImage | built-in (insertlink) |
Image / ImageUpload / ImageResize | built-in (insertimage, imageeditor) |
Table / TableProperties | built-in (inserttable + table control toolbars) |
List / TodoList | built-in (insertorderedlist, insertunorderedlist, insertchecklist) |
CodeBlock | built-in (insertcode) |
Mention (premium) | mentionEnabled: true (base) |
Comments (premium) | commentsEnabled: true (base) |
TrackChanges (premium) | trackChangesEnabled: true (base) |
RevisionHistory (premium) | revisionHistoryEnabled: true (base) |
RealTimeCollaborativeEditing (premium) | editor.collab.attach({ textSync: true }) + self-host Yjs |
AIAssistant (premium + usage) | aiToolkitEnabled: true + setResolver(fn) |
ExportWord (premium Cloud Services) | editor.aiToolkit.exportDocx() helper |
ExportPdf (premium) | html2pdf toolbar item (client-side) |
Autosave | revision history auto-snapshots |
PasteFromOffice | built-in (paste-from-Word with improved list fidelity) |
Behavior differences to know about
Content model. CKEditor 5 uses a custom model tree and serializes to HTML. RichTextEditor is HTML-native via contenteditable. For most apps the difference is invisible. If you have a custom schema, our structured-content JSON mode (enableStructuredContent: true) is the closest equivalent.
Real-time collaboration. CKEditor’s cloud-hosted OT delivers perfect concurrent-typing with zero setup (monthly fee). Our Yjs textSync: true is in preview — covers the same RFP checkbox, but caret behavior on remote apply is rougher. Per-node binding is on the 2026 roadmap.
Custom builds. CKEditor 5’s webpack dance is gone — we ship one bundle with every feature, toggled via config flags. Delete your ckeditor5-inspector and build tooling.
What you gain
- No build pipeline — one
<script> tag, works.
- Perpetual license vs five separate CKEditor subscriptions.
- Self-host everything day one — including Yjs collab (your choice of provider).
- Dictation, mobile toolbar mode, and public release notes out of the box.
- Blazor Server, ASP.NET Web Forms, Classic ASP, PHP bindings.
Why teams move
- Lower cost. Froala’s commercial license is per-project. We’re a perpetual domain license that includes Track Changes, Comments, Revision History, and the AI Toolkit — features Froala doesn’t ship at all.
- Wider feature set. Froala has no Track Changes and no Threaded Comments. Our v2.0 stack ships both in the base license.
- BYOK AI without manual integration. Froala AI Assist requires hand-rolling the resolver call; ours is one-line
setResolver(fn) on the client or DI on the server.
- No webpack. Drop a single
<script> tag and you’re done; Froala’s plugin selection requires careful module imports.
Step-by-step checklist
- Install the npm package:
npm install @@richscripts2/richtexteditor
- Replace your Froala script tags with
<script src="richtexteditor/rte.js"></script> + <link rel="stylesheet" href="richtexteditor/rte_theme_default.css">.
- Replace
new FroalaEditor('#editor', { ... }) with new RichTextEditor("#editor", { toolbar: "full" });
- Map your
toolbarButtons array to a toolbar_* config string (see cheat sheet below).
- If you use Froala AI Assist, swap the manual fetch / register-command for
editor.aiToolkit.setResolver(async (req) => ...).
- Existing HTML keeps working; submit forms / read content as normal.
Config cheat sheet
Basic setup
Froala
new FroalaEditor('#editor', {
toolbarButtons: [
'bold', 'italic', 'underline', '|',
'formatOL', 'formatUL', '|',
'insertLink', 'insertImage'
],
height: 500
});
RichTextEditor
new RichTextEditor("#editor", {
toolbar: "custom",
toolbar_custom:
"{bold,italic,underline}|{insertorderedlist,insertunorderedlist}"
+ "|{insertlink,insertimage}",
height: "500px"
});
AI integration
Froala AI Assist (v5.1+)
FroalaEditor.RegisterCommand('aiAssist', {
callback: function () {
fetch('/my-proxy', { ... })
.then(/* manual integration */);
}
});
RichTextEditor (one-liner)
var editor = new RichTextEditor("#editor", {
aiToolkitEnabled: true
});
editor.aiToolkit.setResolver(async (req) => {
const r = await fetch("/api/my-ai", {
method: "POST", body: JSON.stringify(req)
});
return r.json();
});
Plugin equivalence
| Froala plugin / button | RichTextEditor equivalent |
bold, italic, underline | built-in (bold, italic, underline) |
fontFamily, fontSize | built-in (fontname, fontsize) |
color | built-in (forecolor, backcolor) |
formatOL, formatUL | built-in (insertorderedlist, insertunorderedlist) |
insertLink, insertImage, insertVideo, insertTable | built-in |
emoticons / specialCharacters | built-in (insertemoji, insertchars) |
codeView | built-in (code) |
fullscreen | built-in (fullscreenenter, fullscreenexit) |
print | built-in (print) |
spellChecker | browser-native + spellcheck toolbar toggle |
| Track Changes | trackChangesEnabled: true — Froala doesn’t ship this |
| Comments | commentsEnabled: true — Froala doesn’t ship this |
| Revision History | revisionHistoryEnabled: true — Froala has only basic undo |
| AI Assist (5.1+) | aiToolkitEnabled: true + resolver |
| Word export | editor.aiToolkit.exportDocx() helper (when server endpoint is wired) |
| PDF export | html2pdf toolbar item (client-side) |
Behavior differences to know about
HTML output. Froala adds framework-specific attributes (class="fr-fic", etc.) for image alignment. Our output uses standard inline styles — cleaner for downstream consumers.
Events. Froala’s events: { 'contentChanged': fn } → editor.attachEvent("change", fn).
iframe vs. div. Froala edits in a contenteditable div. We use an iframe by default for content isolation; pass config.iframe = false to match Froala’s in-page behaviour if your CSS depends on it.
What you gain
- Track Changes + Threaded Comments + Revision History — Froala ships none of these.
- Slash commands & @@mentions — Notion-style inline picker.
- Yjs real-time presence + concurrent typing preview.
- Dictation via Web Speech API.
- React, Vue, Angular, Blazor Server, ASP.NET Core, Web Forms, Classic ASP, PHP bindings.
Ready to switch?
Download the trial, run it on your project, and see the migration in practice.
Need help with migration? support@@richtexteditor.com