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

  1. Install the npm package: npm install @@richscripts2/richtexteditor
  2. Replace your TinyMCE <script src="tinymce.min.js"> with <link rel="stylesheet" href="richtexteditor/rte_theme_default.css"> + <script src="richtexteditor/rte.js">.
  3. Remove the TinyMCE init script (tinymce.init({ selector: ... })) and replace the <textarea> with <div id="editor">.
  4. Instantiate: new RichTextEditor("#editor", { toolbar: "full" });
  5. Map your TinyMCE plugins: and toolbar: to our toolbar config — see the cheat sheet below.
  6. Wire your AI resolver via editor.aiToolkit.setResolver(async (req) => { ... }). No proxy needed.
  7. 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 pluginRichTextEditor equivalent
linkbuilt-in (insertlink)
image, imagetoolsbuilt-in (insertimage, imageeditor)
mediabuilt-in (insertvideo, insertyoutube)
tablebuilt-in (inserttable)
lists, advlistbuilt-in (insertorderedlist, insertunorderedlist, insertchecklist)
codesamplebuilt-in (insertcode, syntaxhighlighter)
emoticonsbuilt-in (insertemoji)
templatebuilt-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

  1. Install the npm package: npm install @@richscripts2/richtexteditor
  2. Remove your CKEditor custom build / webpack config entirely.
  3. Replace ClassicEditor.create(...) with new RichTextEditor("#editor", { toolbar: "full" });
  4. Map your CKEditor toolbar: [...] items to our toolbar config (see cheat sheet).
  5. Collaboration: replace CKEditor Cloud Services with editor.collab.attach({ doc, provider, textSync: true }) and self-host Yjs + WebSocket provider.
  6. AI: swap cloudServices config for editor.aiToolkit.setResolver(fn).
  7. 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 featureRichTextEditor equivalent
Bold, Italic, Underlinebuilt-in (bold, italic, underline)
Link / LinkImagebuilt-in (insertlink)
Image / ImageUpload / ImageResizebuilt-in (insertimage, imageeditor)
Table / TablePropertiesbuilt-in (inserttable + table control toolbars)
List / TodoListbuilt-in (insertorderedlist, insertunorderedlist, insertchecklist)
CodeBlockbuilt-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)
Autosaverevision history auto-snapshots
PasteFromOfficebuilt-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

  1. Install the npm package: npm install @@richscripts2/richtexteditor
  2. Replace your Froala script tags with <script src="richtexteditor/rte.js"></script> + <link rel="stylesheet" href="richtexteditor/rte_theme_default.css">.
  3. Replace new FroalaEditor('#editor', { ... }) with new RichTextEditor("#editor", { toolbar: "full" });
  4. Map your toolbarButtons array to a toolbar_* config string (see cheat sheet below).
  5. If you use Froala AI Assist, swap the manual fetch / register-command for editor.aiToolkit.setResolver(async (req) => ...).
  6. 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 / buttonRichTextEditor equivalent
bold, italic, underlinebuilt-in (bold, italic, underline)
fontFamily, fontSizebuilt-in (fontname, fontsize)
colorbuilt-in (forecolor, backcolor)
formatOL, formatULbuilt-in (insertorderedlist, insertunorderedlist)
insertLink, insertImage, insertVideo, insertTablebuilt-in
emoticons / specialCharactersbuilt-in (insertemoji, insertchars)
codeViewbuilt-in (code)
fullscreenbuilt-in (fullscreenenter, fullscreenexit)
printbuilt-in (print)
spellCheckerbrowser-native + spellcheck toolbar toggle
Track ChangestrackChangesEnabled: trueFroala doesn’t ship this
CommentscommentsEnabled: trueFroala doesn’t ship this
Revision HistoryrevisionHistoryEnabled: true — Froala has only basic undo
AI Assist (5.1+)aiToolkitEnabled: true + resolver
Word exporteditor.aiToolkit.exportDocx() helper (when server endpoint is wired)
PDF exporthtml2pdf 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