Using the TinyMCE rich text editor in Bryntum Scheduler

TinyMCE is a popular WYSIWYG rich text editor that can be used to create a Microsoft Word-like editing experience in web apps. The basic open-source version of the editor is available for free. You can expand its functionality by getting a TinyMCE subscription and add-on features, including an AI assistant, math equation tools, and revision history.
See how the TinyMCE editor works inside some of our Bryntum components by browsing the demos below:
Tutorial outline
In this tutorial, you’ll learn to integrate the TinyMCE rich text editor with Bryntum Scheduler, a powerful resource scheduling component for building interactive timeline views. We’ll work with the standard iframe-based TinyMCE editor throughout this guide. You can explore the inline version in the Bryntum Scheduler TinyMCE event editor demo.
You’ll learn how to:
- Create a Bryntum Scheduler
- Create a custom TinyMCE input widget
- Use the TinyMCE input as a custom editor for cells of a column
- Integrate TinyMCE into the event editor popup
- Display rich content in event tooltips
You can find the code for the completed tutorial on the complete-scheduler
branch of this GitHub repository.
Getting started
Clone the starter GitHub repository.
This repository uses the development server and JavaScript bundler, Vite. You need Node.js version 18+ for Vite to work.
Install the dependencies by running the following command:
npm install
Then, run the local dev server as follows:
npm run dev
Finally, open http://localhost:5173
. You should see a blank white screen.
The src/public
folder has two example data files that contain information about resources and events:
- Simple Resources and Simple Events: The data in
resourcesSimple.json
andeventsSimple.json
includes simple text data in thedescription
fields. - Resources and Events: The data in
resources.json
andevents.json
includes rich text HTML data in thedescription
fields.
Creating a Bryntum Scheduler component
Let’s start by creating a Bryntum Scheduler to display the Simple Resources and Simple Events data. We’ll install the Bryntum Scheduler, configure it, and style it.
Installing Bryntum Scheduler
Before you can install the Bryntum Scheduler component, you first need to access and log in to the Bryntum npm registry.
Bryntum components are licensed commercial products, but you can use the free trial version of Bryntum Scheduler for this tutorial.
If you’re installing the trial version, run the following command:
npm install @bryntum/scheduler@npm:@bryntum/scheduler-trial
If you have a Bryntum Scheduler license, install the component as follows:
npm install @bryntum/scheduler
We’ll use the RichTextField
widget to create a custom TinyMCE input field.
Configuring and rendering the Bryntum Scheduler
Replace the console log in the src/main.js
file with the following lines of code:
import { Scheduler } from '@bryntum/scheduler';
const scheduler = new Scheduler({
appendTo : 'container',
startDate : '2025-01-01',
endDate : '2025-01-15',
viewPreset : 'dayAndWeek',
columns : [
{
text : 'Name',
field : 'name',
width : 150
},
{
text : 'Type',
field : 'type',
width : 120
},
{
text : 'Description',
field : 'description',
width : 300,
flex : 2,
minWidth : 334
},
{
text : 'Capacity',
field : 'capacity',
width : 80,
align : 'center'
}
],
resourceStore : {
readUrl : 'resourcesSimple.json',
autoLoad : true
},
eventStore : {
readUrl : 'eventsSimple.json',
autoLoad : true
}
});
Here, we create an instance of the Bryntum Scheduler and set the following configuration properties:
- The
appendTo
config appends the Scheduler component to thediv
with anid
ofcontainer
. - The
columns
config creates a column for each field in the Simple Resources data. - The
resourceStore
andeventStore
load data from external JSON files using thereadUrl
configuration andautoLoad
properties. TheautoLoad
property instructs the Scheduler to automatically fetch the data when the component initializes. For simplicity, in this guide, we set the URL to theresourcesSimple.json
andeventsSimple.json
files in thesrc/public
folder.
Styling the Bryntum Scheduler
Now that we have a component, we can style it.
By default, the Scheduler component is configured to occupy 100%
of the parent DOM element and has a min-height
of 10em
.
In the src/styles.css
file, make the Scheduler take up the full height of the screen by adding the following styles to its parent div
element:
#container {
margin : 0;
display : flex;
flex-direction : column;
height : 100vh;
font-size : 14px;
}
Import the Bryntum Scheduler Stockholm theme by adding the following line of code to the top of the src/styles.css
file:
@import "../node_modules/@bryntum/scheduler/scheduler.stockholm.css";
The Stockholm theme is one of five available themes for Bryntum Scheduler. If you want to create a custom theme or use multiple themes, check out the Bryntum Scheduler styling documentation.
Run the local dev server using npm run dev
. You’ll see a Bryntum Scheduler component displaying the Simple Resources and Simple Events data:

Setting up TinyMCE in your application
We’re going to build a custom TinyMCE input field for use as a cell editor in the Description column in our Scheduler. This will let us format text and add images to the description content.
First, you’ll need a TinyMCE account. Register for a free account that comes with a 14-day trial of premium features – no credit card required.
To use TinyMCE, you first need to create an account. You can sign up for a free account, which includes a 14-day trial of advanced features. No credit card details are required.

After you log in, you’ll see the Integrate TinyMCE page in your dashboard. There are three ways to install TinyMCE: using Tiny Cloud, package managers, or self-hosted downloads. We’ll use Tiny Cloud for this guide.
Get your API key by clicking the Get your API Key button.

In the src/index.html
file in your Bryntum Scheduler app, add the following script below the div
with id="container"
. Replace your-api-key
with your TinyMCE API key:
<script src="https://cdn.tiny.cloud/1/your-api-key/tinymce/7/tinymce.min.js" referrerpolicy="origin"></script>
To load the script, TinyMCE needs at least one approved root domain. By default, localhost is an approved domain. When you deploy your app, add the domain using the Approved Domains page in your Tiny Cloud dashboard.
Keep in mind that API keys are used to load TinyMCE from Tiny Cloud. If you choose to self-host TinyMCE instead, use a license key rather than an API key.
Building a TinyMCE widget for rich text editing
Let’s start by creating a TinyMCE input field widget.
Create a lib
folder in the src
folder. In src/lib
, create a TinyMceField.js
file and add the following lines of code to it:
import { DomHelper, GlobalEvents, RichTextField } from '@bryntum/scheduler';
export default class TinyMceField extends RichTextField {
static $name = 'TinyMceField';
static type = 'tinymcefield';
tinymce = null;
}
TinyMceField.initClass();
The TinyMceField
class extends the RichTextField
class. We assign the widget its own $name
and type
, and create a property that will store the TinyMCE instance. The initClass()
method registers the widget with the base Widget
class, enabling us to create the TinyMceField
widget by type.
Understanding Bryntum widgets
Bryntum components include UI elements called widgets that you can add to your project interface. Widgets like Button
, DatePicker
, and RichTextField
are class instances that extend the base Widget
class.
The RichTextField
widget serves as an interface class built for rich text field editors like TinyMCE. It employs a div
as the input field, which rich text editors target as their main element. Rather than using RichTextField
directly, we’ll use it as a foundation for building a class specific to the rich text editor.
Configuring the widget
Add the following static configurable
property below the tinymce = null
property, making sure to replace 'your-api-key'
with your TinyMCE API key:
static configurable = {
tinyMceConfig : {},
apiKey : 'your-api-key',
inline : false,
resize : false,
menubar : false,
autoFocus : true,
rootBlock : 'div',
inputAttributes : {
tag : 'textarea'
}
};
This setup turns off widget resizing and removes the drop-down menu bar. When the widget becomes visible, we’ll populate the tinyMceConfig
using the standard iframe-based TinyMCE editor.
Setting up the constructor
Add the following construct
method below the configurable property:
construct(config = {}) {
super.construct(config);
const me = this;
GlobalEvents.ion({
theme : 'destroyEditor',
thisObj : me
});
me.up(w => w.isPopup)?.ion({
hide : 'destroyEditor',
thisObj : me
});
me.ion({
paint : '_onPaint',
thisObj : me
});
}
The construct
method handles the configuration setup. We call destroyEditor
when the global theme changes or when a surrounding popup hides, and we trigger _onPaint
on every paint event.
Initializing the editor
Add the following _onPaint
method below the construct
method:
_onPaint() {
const me = this;
if (me.editor) {
me.destroyEditor();
}
if (!me.editor) {
const html = me.value ?? '';
me.input.value = html;
globalThis.tinymce.init({
...me.tinyMceConfig,
apiKey : me.apiKey,
auto_focus : me.autoFocus,
inline : me.inline,
forced_root_block : me.rootBlock,
menubar : me.menubar,
resize : me.resize,
height : me.height,
target : me.input,
skin : DomHelper.themeInfo?.name
?.toLowerCase().endsWith('-dark')
? 'oxide-dark'
: 'oxide',
ui_mode : 'split',
setup : editor => {
editor.on('init', () => editor.setContent(html, { format : 'html' }));
editor.on('NodeChange', () => {
if (me.isDestroying) return;
const newVal = editor.getContent();
if (newVal !== me.value) {
me.richText = newVal;
me.triggerFieldChange({
value : newVal,
oldValue : me.value,
userAction : true
});
}
});
editor.on('blur', () => {
const scheduler = me.up('scheduler');
scheduler.finishEditing();
});
}
}).then(editors => (me.editor = editors[0]));
}
}
The onPaint
method executes when the widget or any element within it becomes visible. The above _onPaint
runs when the TinyMCE cell editor opens in the Bryntum Scheduler.
First, we remove the existing editor (if there is one). Then, we build a new TinyMCE editor instance with our configuration options. The TinyMCE setup
option provides a callback that runs before the TinyMCE editor renders.
Within the setup option, we attach three event handlers:
- When the editor initializes, we use the
setContent
method to populate it with the Bryntum Scheduler cell value. - The
NodeChange
event triggers when the editor’s content changes, updating the Bryntum Scheduler cell with the new content. - The
blur
event occurs when the editor loses focus, calling the Bryntum SchedulerfinishEditing
method to save the cell value and close the editor. This is necessary because we’ll turn offmanagedCellEditing
to control when the TinyMCE editor closes.
Managing widget ownership and cleanup
Add the following owns
method below the _onPaint
method:
owns(target) {
return super.owns(target) || Boolean(target?.closest('.tox-tinymce'));
}
The owns
method returns true
when the widget owns the given element, event, or widget. We use this to specify that the TinyMCE floating toolbar element belongs to this widget.
Finally, add the following destroyEditor
method below the owns
method:
destroyEditor() {
this.editor?.destroy(); // Destroy the existing instance
this.editor = null;
}
This method handles editor cleanup in _onPaint
by calling destroy
on the editor object.
Adding the TinyMCE editor to the Description column
In the src/main.js
file, import your custom TinyMCE input field widget:
import './lib/TinyMceField.js';
Add the following properties to the 'Description'
column object in the Scheduler columns
config array:
type : 'template',
template : ({ value }) => value,
autoHeight : true,
revertOnEscape : false,
managedCellEditing : false,
cellEditor : {
// The rich text editor is allowed to overflow the cell height.
matchSize : {
height : false
}
},
editor : {
type : 'tinymcefield',
inline : false,
height : 400
}
We change the column type to template
, allowing us to use a template for the cell content, so we can render rich HTML content, like image tags, in the cell. We use the editor
config object to create the input field for editing cells in the Description column and set its type
to the TinyMCE input field.
In the resourceStore
and eventStore
config, replace the Simple Resources and Simple Events data with the rich text data in the Resources and Events JSON files by changing the readUrl
as follows:
resourceStore: {
- readUrl: 'resourcesSimple.json',
+ readUrl: 'resources.json',
autoLoad: true
},
eventStore: {
- readUrl: 'eventsSimple.json',
+ readUrl: 'events.json',
autoLoad: true
}
Add the following styles to the src/styles.css
file:
.b-grid-cell[data-column="description"] {
display : block;
padding-block : .75em;
h3 {
margin-block : 0 .5em;
}
}
.b-tinymcefield {
.tox.tox-tinymce {
width : 100%;
border-radius : 2px;
&:not(.tox-tinymce-inline) {
border : 1px solid #d0d1d2;
}
&.tox-edit-focus {
border-color : rgba(254, 172, 49, 0.6);
}
.tox-edit-area::before {
border : none;
}
}
}
At this point, you can test the integration by reloading the server and double-clicking on any Description cell to see the TinyMCE editor open:

With the development server running, rich text formatting appears in the Description column. You can now style text within and add images to Description cells.
Next steps
Take a look at the TinyMCE editing demo on the Tiny Cloud homepage to see the advanced features that you can add to your editor, such as the Microsoft Word import and export functionality.
Visit the Bryntum Scheduler demo page to see some of the other features you can add to your component, such as: