Migration guide

Migrate from TinyMCE, CKEditor, Froala, Quill, Lexical, or Tiptap

Migration effort depends on custom plugins, schemas, uploads, and collaboration infrastructure. The package now includes an executable adapter for common configuration plus HTML, Quill Delta, Tiptap JSON, and Lexical JSON content. Unsupported items are reported for review rather than silently discarded.

6 adaptersconfig and content mappings with warnings
$0 / yearperpetual license after the one-time purchase
HTML-firstretain content while mapping integrations

Executable, indexable migration paths

Import createMigrationReport, migrateConfig, convertContent, or migrateEditor from @richscripts/richtexteditor/migration. Start with the source-specific guide:

Inventory before you cut over

createMigrationReport(source, { config, content }) produces one reviewable result with mapped and unmapped toolbar items, converted HTML, warnings, blockers, a readyForCutover flag, and a stable manual-review checklist. Run it against representative saved documents in CI; a passing flag covers detected adapter blockers, not custom plugins, application data, accessibility, or production acceptance.

Custom conversion boundary

Resolve custom models without forking the adapter

Pass explicit converter registries for quill.embeds, tiptap.nodes, tiptap.marks, or lexical.nodes. Successful uses appear in appliedConverters with exact source-model paths; converters receive the current context.path. Returning null preserves the normal unsupported-content blocker instead of silently claiming success.

Converter HTML is trusted application output, not sanitized adapter output. Escape external values, apply your sanitizer policy, and compare representative saved HTML before cutover.

const report = createMigrationReport("quill", {
  content: legacyDelta,
  converters: {
    quill: {
      embeds: {
        mention: (value) => renderSafeMention(value)
      }
    }
  }
});

console.table(report.appliedConverters);
Batch migration inventory

Run the same report from your terminal

The package includes the dependency-free rte-migration-report CLI. Point it at one saved configuration and content file, or use a manifest to inventory many editors and documents in one portable JSON report. Quill and Lexical checklist state now carries into task-list HTML instead of flattening to plain bullets.

  • Exit 0: no detected adapter blockers.
  • Exit 2: review blockers before cutover.
  • Exit 1: invalid arguments, input, or file access.

Every document includes an unsupportedContent inventory with node, mark, or embed type, occurrence counts, and exact paths such as ops[4].insert.mention or $.content[2].marks[0]. Batch output rolls those findings into summary.unsupportedByDocument and source/type totals in summary.unsupportedTotals, including affected-document counts. Migration owners can locate, size, and assign custom conversion work without parsing warning strings.

--converters ./migration-converters.cjs applies one reviewed CommonJS registry to a single report or the full batch. It executes trusted local code with the CLI process permissions and validates the exported source, registry groups, and functions before reading the inventory. Add --converter-sha256 <digest> to pin reviewed bytes: malformed or mismatched hashes are rejected before module execution. Without --converters, the CLI stays data-only and preserves unsupported-content blockers.

Schema-versioned reports record schemaVersion: 2, the producing package name/version in generator, and a converterModule provenance value: null for data-only runs, or the module’s non-sensitive file label, SHA-256 digest, and hashVerifiedBeforeExecution state. This lets CI prove which package and converter bytes produced a readiness result without leaking a local absolute path. Reports omit a current-time field so identical inputs remain byte-for-byte deterministic; use artifact-store timestamps for retention. The digest proves byte identity, not converter safety or correctness.

rte-migration-report --print-schema prints the packaged Draft 2020-12 JSON Schema for single and batch artifacts. The standalone command reads no migration inputs and cannot execute a converter module, so CI consumers can validate report structure before processing readiness results.

rte-migration-report --validate-report ./migration-report.json checks a stored artifact without loading source files or converter code. It rejects incompatible schema/generator identity, malformed document fields, readiness/blocker contradictions, and batch totals or unsupported inventories that disagree with nested reports. A valid artifact exits 0 and returns compact JSON with its exact-byte SHA-256.

Add --report-sha256 <digest> in promotion jobs to reject changed bytes before JSON parsing or validation; output records hashVerifiedBeforeValidation. This verifies artifact byte identity, not signer identity, and the integrity check does not replace representative content-fidelity acceptance.

Add --baseline-report ./accepted-baseline.json to fail only on newly introduced migration debt. Both artifacts are validated first; comparison output attributes new blockers and new or increased unsupported findings to document ID, source, type, count, and exact new paths. Exit 0 means no detected regression, exit 2 means new debt, and exit 1 means invalid input. Existing accepted debt remains visible, and a clean comparison is not production acceptance.

--allow-blockers keeps informational report jobs green while preserving blockers in the output. A zero exit code is an automation signal, not production acceptance; test representative content, custom plugins, assets, permissions, collaboration data, accessibility, and saved HTML.

# One editor
npx rte-migration-report \
  --source tinymce \
  --config ./fixtures/tinymce-config.json \
  --content ./fixtures/article.html \
  --output ./artifacts/migration-report.json

# A repository-wide inventory
npx rte-migration-report \
  --input ./migration-manifest.json \
  --converters ./migration-converters.cjs \
  --converter-sha256 "$REVIEWED_SHA256" \
  --output ./artifacts/migration-inventory.json

Inspect the exact migration fixture artifact for version-labelled inputs, SHA-256 hashes, converted HTML, structural expectations, warnings, methodology, and limitations. Quill, Tiptap, and Lexical models are generated through pinned official runtimes and their public export APIs; the corpus now verifies mixed nested lists plus checked and unchecked task state across all three models. This focused standard-schema evidence is not vendor certification or a custom-schema guarantee.

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.
  • Commercial self-hosting from the entry tier. RichTextEditor includes self-hosting in its commercial licenses. TinyMCE also offers GPL open-source self-hosting; review its current license and deployment options for your application.
  • Choose your AI backend. Connect editor.aiToolkit.setResolver(fn) to your authenticated backend; for ASP.NET Core use IRichTextBoxAiResolver. Keep provider secrets on the server. AI calls may send content to your chosen provider and incur provider charges.

Step-by-step checklist

  1. Install the npm package: npm install @richscripts/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) => { ... }) to an authenticated server endpoint. Do not embed a secret provider API key in client code.
  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. To control which elements and attributes are allowed through, use config.contentSanitizer.
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, Svelte, 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