RoosterJS puts a content model between the editor and the DOM
roosterjs is a framework-independent javascript rich text editor.
At a glance
- What is it?
- Rooster is a MIT licensed TypeScript rich text editor from Microsoft that keeps its content in a separate Content Model data structure and converts to and from HTML at the edges, shipped as a facade package plus five lower level ones. The 8.x packages are frozen on a separate branch, and an EditorAdapter class bridges the two generations.
- Who is it for?
- RoosterJS fits a team building a rich text editor on Vue, React or a custom framework that needs the editing semantics separated from the DOM and the ability to override core API behaviour, since the Content Model layer is what makes that possible. It does not fit a team that just wants a textarea with bold and italic, and it does not fit a Vue 3 project without work, since the README's quick start is a plain script tag and the packages target the older ecosystem.
- Can I use it commercially?
- Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
- Is it still maintained?
- Yes. The repository received new commits within the last day.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The Content Model is the architecture
Rooster is described as a framework-independent JavaScript rich-text editor neatly nested inside one HTML div element, where editing operations performed by end users are handled in simple ways to generate the final HTML.
Rooster is working on top of a middle layer data structure called Content Model. All format API and editing operation are using this Content Model layer as content format, and finally convert to HTML and show it in editor.
So there are three layers. The DOM, which is what the browser renders. The Content Model, which is a data representation of the content. And the editing operations, which work on the model and produce HTML at the end.
That structure is the whole point of the library and it explains the package layout. Most rich text editors work directly on the DOM, which means the editor's internal state is whatever the browser currently has, and every operation is a DOM mutation. That is simple to start and hard to extend, because testing an editor operation means driving a DOM, and because a feature that needs to reason about the content, a table operation or a list numbering, has to reason about the DOM.
With a Content Model, an operation reads and writes a plain data structure, and the conversion to HTML is a separate step at the boundary. That is what makes the editor framework-independent in the first place, and it is why there is a dedicated package for the conversion.
The trade is that you now have three representations to keep in sync, and the Content Model has to express everything the editor supports. The README's own summary of the design, editing operations handled in simple ways to generate the final HTML, is a description of a system that keeps the HTML generation simple by keeping the model simple too.
Six packages, and you pick how deep you go
The package list is the clearest documentation of the architecture, because each package corresponds to one of the three layers or to a concern around them.
The roosterjs package is a facade of all Rooster code for those who want a quick start, and the createEditor() function in it creates an editor with default configurations. That is the package most people install.
roosterjs-content-model-core defines the core editor and plugin infrastructure, and the README says to use it instead of roosterjs to build and customize your own editor. roosterjs-content-model-api defines APIs for editor operations, to modify content and formatting in the editor you built. roosterjs-content-model-dom defines APIs for Content Model and DOM operations, doing conversion between the DOM tree and the Content Model. roosterjs-content-model-plugins defines basic plugins for common features. And roosterjs-content-model-types defines public interfaces and enumerations, including Content Model types, API parameters and other types.
So the split is facade, core, API, DOM, plugins, types, and the choice of which to install is a choice of how much of the framework you take. The types package in particular is a separate install, which is unusual and sensible, since an application that only consumes the API types does not need the editor itself.
There are extension packages too. roosterjs-color-utils provides color transformation utility to make the editor work under dark mode, which is a specific and common problem, the hardcoded colours in a theme that do not survive a dark theme. And roosterjs-content-model-markdown defines public APIs to enable conversions between Markdown and ContentModel, which is the piece that lets a Markdown-based workflow coexist with the rich editor rather than being a separate tool.
The API surface is then described in three groups. The Content Model level APIs in roosterjs-content-model-dom, with several levels of operations, create Content Model elements, convert DOM tree to Content Model, convert Content Model to DOM tree, format handlers, and a few DOM level API. The core APIs in roosterjs-content-model-core, which the Editor class calls to perform basic editor operations, and which can be overridden by specifying API overrides in Editor options when creating the editor. And the scenario-based APIs in roosterjs-content-model-api, triggered by user interaction.
The 8.x line is frozen, and the adapter is the bridge
The upgrade section is the part of the README that most directly affects an adoption decision, and it is handled explicitly.
All old packages, the 8.x ones, are moved to a branch called roosterjsv8, and the list is long, roosterjs-editor-core, roosterjs-editor-api, roosterjs-editor-dom, roosterjs-editor-plugins, roosterjs-editor-types, roosterjs-editor-types-compatible and roosterjs-react. And the commitment is explicit: we will not update these branches any more unless there are new security bugs.
That is a good way to handle a major version change, because the old code stays available and the security commitment is narrow and specific. A team that is on 8.x has a path, they keep using the branch, and they get security fixes if any appear, and they can migrate at their own pace.
The bridge between the two is the EditorAdapter class in the roosterjs-editor-adapter package. It provides an adapter class to work with Editor, the 9.x one, and legacy plugins, via EditorAdapterOptions.legacyPlugins.
That is the interesting part of the design. The 9.x rewrite changed the API surface substantially, and the adapter lets a team with existing 8.x plugins run them against a 9.x editor, so the migration is incremental, plugin by plugin, rather than a single big-bang rewrite.
The upgrade guide itself lives in the wiki, linked as RoosterJs-9, and the README points there rather than summarising it. For an evaluator, that link is the first thing to read, because the difference between 8.x and 9.x is the difference between the editor you know and the editor you would be adopting.
The release history shows the 9.x line moving, with v9.52.0 on 2026-05-11, v9.53.0 on 2026-06-01 and v9.56.0 on 2026-07-02, and the last push to master on 2026-10-15.
Plugins are the extension point, and the event shape is simple
Rooster supports plugins, and you can use built-in plugins or build your own. Plugins call APIs to communicate with the editor, and when an operation is performed by the user or when content is changed by code, the editor will trigger events for the plugins to handle.
The sample plugin in the README is short and shows the contract.
class HelloRooster implements EditorPlugin {
getName() {
return 'HelloRooster';
}
initialize(editor: IEditor) {}
dispose() {}
onPluginEvent(e: PluginEvent) {
if (e.eventType == 'input' && e.rawEvent.which == 65) {
alert('Hello Rooster');
}
}
}Four methods, a name, an initialise with the editor, a dispose and a single event handler that switches on the event type. The plugin does not call the editor to perform the operation, it observes the event and calls APIs to react.
That shape is what makes the Content Model layer pay off. A plugin that wants to act on a formatting change can read the model, and a plugin that wants to observe raw input can hook the input event as the example does. The event carries a rawEvent, so the low level browser event is available to plugins that need it.
The dispose method is worth noting. A plugin holds a reference to the editor, and a plugin that registers listeners or timers has to be able to release them, and the presence of an explicit dispose in the interface makes that a contract rather than a convention.
The API override mechanism in the core package is the other extension point. The Editor class calls the core APIs to perform basic editor operations, and those APIs can be overridden by specifying API overrides in the Editor options when creating the editor. So a team that needs, say, different behaviour for a particular operation can replace that one API rather than forking the editor, which is a considerably lighter intervention than the plugin model allows.
A plain script tag, and a build that is a single Node script
The Installation section gives the npm command and the yarn equivalent, and the quick start is a static HTML file with a script tag.
<html>
<body>
<div style="width: 500px; height: 400px; border: solid 1px black" id="contentDiv"></div>
<button id="buttonB">B</button> <button id="buttonI">I</button>
<button id="buttonU">U</button>
<script src="rooster.js"></script>
<script>
var contentDiv = document.getElementById('contentDiv');
var editor = roosterjs.createEditor(contentDiv);
editor.setContent('Welcome to <b>RoosterJs</b>!');
document.getElementById('buttonB').addEventListener('click', function () {
roosterjs.toggleBold(editor);
});So the library is usable without a bundler, which is unusual for a modern TypeScript editor and points at its intended audience. The API is global, roosterjs.createEditor(editor), roosterjs.toggleBold(editor), and the editor is bound to a div. That is the shape of a rich text editor that gets embedded in an existing page or a legacy application, and it is the shape the 8.x generation had, with the roosterjs-react package in the frozen branch suggesting the React integration belonged to that era.
The build system is a single Node script with a subcommand per step, and the build command chains them.
"build": "node tools/build.js clean checkdep normalize eslint buildcommonjs dts packprod builddemo",
"build:ci": "node --max-old-space-size=8192 tools/build.js --noProgressBar clean checkdep normalize eslint buildcommonjs buildamd buildmjs buildtest dts pack packprod builddemo builddoc"So the steps are clean, checkdep for dependency checking, normalize, eslint, buildcommonjs, dts, packprod for the production pack, builddemo and builddoc. The CI variant adds buildamd, buildmjs and buildtest and raises the Node heap to 8192 MB, and the separate amd and mjs steps are the module format builds, which is the pattern for shipping both AMD and ES module outputs from one source.
Tests run under Karma, with separate scripts for Chrome, Firefox, a fast configuration using karma.fast.conf.js, a debug mode that does not run once, and a separate Safari runner through tools/testSafari.js. The repository listing has both karma.conf.js and karma.fast.conf.js, and the demo/ directory with its own index.html and scripts, plus a webpack.config.js at the root.
Where RoosterJS is the wrong choice
The first wrong case is a simple comment field. If the requirement is a textarea with bold, italic and a link, Rooster is an enormous dependency for it. The facade package gives you the whole editor, the Content Model, the plugin system, the API override mechanism, and a build with a demo, a documentation site and a compatibility adapter for a previous major version. Most of that is not needed for a comment box, and every part of it has to be kept working as the browser and the framework move.
The second wrong case is a project that already has a rich text editor and is happy with it. ProseMirror, Lexical, Quill, Tiptap and Slate all exist, and each of them is a serious project with a large user base. Adopting Rooster means adopting a Microsoft-maintained editor with a particular model of how content should be represented, and the reason to do that is usually familiarity or an existing integration rather than a capability you cannot get elsewhere.
The third wrong case is a team that wants the modern JavaScript ecosystem. The quick start is a script tag with a global object, the packages target the 8.x era of the ecosystem, and the React integration lives in the frozen 8.x branch. A Vue 3 or a modern React 19 project would be integrating with something that predates both. The framework-independent claim is about the editor not depending on a UI framework, which is a different thing from being current with how those frameworks are built.
The fourth wrong case is a team that needs a fully specified, stable API. The project is on a 9.x line with frequent releases and a wiki link to an upgrade guide, and the 8.x line is frozen except for security fixes. A codebase that has to be auditable against a fixed specification is poorly served by a library that says the upgrade path is in the wiki.
How it differs from a DOM-based editor
The comparison worth making is with an editor that works directly on the DOM, and the difference in approach is where the state lives.
A DOM-based editor treats the browser's tree as the state. Every formatting operation is a DOM mutation, and the current selection is the browser's selection. That is why those editors are usually quick to build and hard to extend, and why adding a feature like a table with proper cell selection or a numbered list with correct renumbering means writing DOM code that is aware of the browser's specific range and selection behaviour.
Rooster's Content Model changes that. The content is a data structure, the operations are functions over that structure, and the DOM is a projection of it. The advantages follow from that. An operation can be tested without a browser. A feature that needs to reason about the document reasons about the model. And the conversion in one direction or the other can be changed, which matters for a project that has to support several output formats.
The cost is real and it is the usual cost of a normalised representation. The model has to be able to express everything the editor supports, and it has to be a faithful representation of what the browser will render, and every feature that changes content has to keep the model and the DOM in step. A bug in the conversion shows up as content that looks right in the model and wrong in the browser, which is a harder class of bug to find than a DOM operation that did not do what it said.
The second difference is the API override mechanism, and it only exists because of the layering. Since the Editor calls a defined set of core APIs, and those APIs can be replaced in the Editor options, a team can change the behaviour of a specific operation without forking the editor. A DOM-based editor has no equivalent seam, because there is no defined set of operations to override.
The third difference is the framework independence claim, and it is a direct consequence. The editor does not own a UI, it owns a model and a set of operations, so the buttons, the toolbars and the surrounding application are the host's business. The quick start shows that with plain buttons and addEventListener calls.
Editorial conclusion
RoosterJS fits a team building a rich text editor on Vue, React or a custom framework that needs the editing semantics separated from the DOM and the ability to override core API behaviour, since the Content Model layer is what makes that possible. It does not fit a team that just wants a textarea with bold and italic, and it does not fit a Vue 3 project without work, since the README's quick start is a plain script tag and the packages target the older ecosystem. Before adopting it, read the 9.x upgrade notes linked from the README, because the 8.x to 9.x transition moved the whole API surface, and note that the last release is v9.56.0 from 2026-07-02 with the last push on 2026-10-15.
Frequently asked questions
What is the Content Model in RoosterJS?
It is a middle layer data structure that holds the content separately from the DOM. All format APIs and editing operations use the Content Model as their content format, and the model is finally converted to HTML and shown in the editor. The roosterjs-content-model-dom package does the conversion between the DOM tree and the Content Model.
Which packages does RoosterJS publish?
Six core packages, roosterjs as a facade with createEditor(), roosterjs-content-model-core for the editor and plugin infrastructure, roosterjs-content-model-api for editor operations, roosterjs-content-model-dom for DOM and Content Model conversion, roosterjs-content-model-plugins and roosterjs-content-model-types for public types. Extensions include roosterjs-color-utils and roosterjs-content-model-markdown.
What happened to the RoosterJS 8.x packages?
All old 8.x packages were moved to a branch called roosterjsv8, including roosterjs-editor-core, roosterjs-editor-api, roosterjs-editor-dom, roosterjs-editor-plugins, roosterjs-editor-types, roosterjs-editor-types-compatible and roosterjs-react. The README states those branches will not be updated any more unless there are new security bugs, and the EditorAdapter class bridges the 9.x editor with legacy plugins.
How do I add a plugin to a RoosterJS editor?
Implement the EditorPlugin interface with getName, initialize, dispose and onPluginEvent, and the editor triggers events to plugins when an operation is performed by the user or when content is changed by code. The README's sample watches the input event and shows a dialog.
How is RoosterJS built and tested?
The build is a single Node script, tools/build.js, with a subcommand per step, and the build command chains clean, checkdep, normalize, eslint, buildcommonjs, dts, packprod, builddemo and builddoc. Tests run under Karma, with separate scripts for Chrome, Firefox, Safari and a fast configuration, and the repository also carries an eslintrc, a prettierrc, a karma.conf.js and a webpack.config.js.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/microsoft-roosterjs)Was this useful?