Most teams finish the migration in under a day. Your existing HTML content keeps working, toolbar strings map mostly 1→1, and the features you’re paying a premium tier for today ship in our base license. Below: side-by-side config mapping, feature-equivalence tables, and a step-by-step checklist.
<richtextbox> as a first-class Razor citizen, not a JS wrapper.dotnet add package RichTextBox.AspNetCore --version 1.0.0-preview.12Program.cs: builder.Services.AddRichTextBox(); · app.MapRichTextBoxUploads();RichTextBox.lic file next to Program.cs (contact sales for a trial key — the demo resolver runs key-less).<textarea id="..."> + TinyMCE init script with a single <richtextbox asp-for="Body" toolbar="full" /> tag.plugins: and toolbar: config to our toolbar attribute — see the cheat sheet below.builder.Services.AddRichTextBoxOpenAiResolver(opts => opts.ApiKey = ...). No proxy server needed.config.filterhtml / filterbeforepaste callbacks.tinymce.init({
selector: '#editor',
plugins: 'lists link image table code',
toolbar: 'bold italic | bullist numlist | link image',
height: 500
});
<richtextbox
asp-for="Body"
toolbar="custom"
height="500px" />
@* Define "custom" toolbar once at site level *@
<script>
RTE_DefaultConfig.toolbar_custom =
"{bold,italic}|{insertunorderedlist,insertorderedlist}|{insertlink,insertimage}";
</script>
tinymce.init({
plugins: 'ai',
toolbar: 'ai',
ai_request: (request, respondWith) => {
respondWith.stream((streamMessage) =>
fetch('/my-proxy-to-openai', {
method: 'POST',
body: JSON.stringify({ ... })
}).then(/* ... */)
);
}
});
builder.Services.AddRichTextBox();
builder.Services.AddRichTextBoxOpenAiResolver(opts =>
{
opts.ApiKey = builder.Configuration["OpenAI:ApiKey"];
opts.Model = "gpt-4o-mini";
});
@* Page *@
<richtextbox asp-for="Body"
enable-ai-toolkit="true" />
Three separate TinyMCE premium add-ons → three base-license attributes.
tinymce.init({
plugins: 'tinycomments a11ychecker '
+ 'revisionhistory',
tinycomments_mode: 'embedded',
tinycomments_author: 'current user',
// Track Changes requires separate license
});
<richtextbox asp-for="Body"
enable-tracked-changes="true"
enable-comments="true"
enable-revision-history="true"
current-user-id="@User.Id"
current-user-name="@User.Name" />
| TinyMCE plugin | RichTextBox equivalent |
|---|---|
link | built-in (insertlink toolbar item) |
image, imagetools | built-in (insertimage, imageeditor) |
media | built-in (insertvideo, insertyoutube) |
table | built-in (inserttable + table control toolbars) |
lists, advlist | built-in (insertorderedlist, insertunorderedlist, insertchecklist) |
codesample | built-in (insertcode, syntaxhighlighter) |
emoticons | built-in (insertemoji) |
template | built-in (inserttemplate) |
mentions (premium) | enable-mentions="true" (base license) |
tinycomments (premium) | enable-comments="true" (base license) |
tinymcespellchecker (premium) | native browser spellcheck + spellcheck toolbar toggle |
revisionhistory (premium) | enable-revision-history="true" (base license) |
| Track Changes (premium) | enable-tracked-changes="true" (base license) |
ai (premium + credits) | enable-ai-toolkit="true" + provider resolver |
exportword (premium) | built-in DOCX endpoint + editor.aiToolkit.exportDocx() |
exportpdf (premium) | html2pdf toolbar item (client-side) |
wordcount | native via editor.getText().length or host wrapper |
autosave | revision history auto-snapshots + form-level autosave |
config.filterhtml.
change event → our editor.attachEvent("change", fn). init / setup → our new RichTextEditor(selector, config) is synchronous; bind events right after construction.
content_css: "..." → our config.contentCssUrl or inline config.contentCssText.
/ inline picker) — more mature than TinyMCE's autocompleter API.dotnet add package RichTextBox.AspNetCore --version 1.0.0-preview.12Program.cs: builder.Services.AddRichTextBox(); · app.MapRichTextBoxUploads();RichTextBox.lic next to Program.cs.ClassicEditor.create(...) init code with a single <richtextbox> tag.toolbar: [ ... ] items to our toolbar attribute (see cheat sheet).enable-collab="true" and load Yjs from npm or a CDN.import ClassicEditor from '@ckeditor/ckeditor5-build-classic';
ClassicEditor.create(document.querySelector('#editor'), {
toolbar: {
items: ['bold', 'italic', '|',
'bulletedList', 'numberedList', '|',
'link', 'imageUpload']
}
});
<richtextbox
asp-for="Body"
toolbar="custom" />
<script>
RTE_DefaultConfig.toolbar_custom =
"{bold,italic}|{insertunorderedlist,insertorderedlist}|{insertlink,insertimage}";
</script>
ClassicEditor.create(el, {
cloudServices: {
tokenUrl: '/api/ckeditor-token',
webSocketUrl: 'wss://cs.cke-cs.com/...'
},
collaboration: {
channelId: documentId
}
});
<richtextbox asp-for="Body"
enable-collab="true"
current-user-id="@User.Id"
current-user-name="@User.Name" />
<script type="module">
import * as Y from "https://esm.sh/yjs";
import { WebsocketProvider } from "https://esm.sh/y-websocket";
const ydoc = new Y.Doc();
const p = new WebsocketProvider(
"wss://your-server/yjs", docId, ydoc);
editor.collab.attach({
doc: ydoc, provider: p,
textSync: true // concurrent typing
});
</script>
ClassicEditor.create(el, {
ai: {
openAI: {
requestHeaders: {
Authorization: 'Bearer ...'
},
apiUrl: 'https://api.openai.com/v1/...'
}
}
});
builder.Services.AddRichTextBox();
builder.Services.AddRichTextBoxOpenAiResolver(opts =>
{
opts.ApiKey = builder.Configuration["OpenAI:ApiKey"];
opts.Model = "gpt-4o-mini";
});
| CKEditor 5 plugin / feature | RichTextBox 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) | enable-mentions="true" (base license) |
Comments (premium) | enable-comments="true" (base license) |
TrackChanges (premium) | enable-tracked-changes="true" (base license) |
RevisionHistory (premium) | enable-revision-history="true" (base license) |
RealTimeCollaborativeEditing (premium) | enable-collab="true" + peer-dependency Yjs (self-host) |
AIAssistant (premium + usage) | enable-ai-toolkit="true" + provider resolver |
ExportWord (premium Cloud Services) | built-in DOCX endpoint + editor.aiToolkit.exportDocx() |
ExportPdf (premium) | html2pdf toolbar item (client-side) |
Autosave | revision history auto-snapshots + form-level autosave |
PasteFromOffice | built-in (paste-from-Word with improved list fidelity in preview.12) |
contenteditable. For most apps the difference is invisible — both produce standard HTML. If you have a custom CKEditor 5 schema, our structured-content JSON mode is the closest equivalent.
textSync: true is in preview — it covers the same RFP checkbox, but caret behavior on remote apply is rougher. Per-node binding is on the 2026 roadmap.
ckeditor5-inspector and build tooling behind.
Download the trial, run it on your project, and see the migration in practice.
Need help with migration? [email protected]