Arsalan Khattak
28 August 2025

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 […]

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:

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:

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:

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:

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:

Arsalan Khattak

Bryntum Scheduler