Create a plugin for TinyMCE

TinyMCE is designed to be easily extended by custom plugins; with APIs for registering custom plugins, and creating and localizing custom UI.

Requirements

To be recognized as a plugin by TinyMCE, the code for a custom plugin must have a JavaScript file with a single entry point that registers the plugin with TinyMCE using the PluginManager API. Any other code or resources can be in separate files and can be loaded in any standard manner. TinyMCE also has various APIs for loading scripts and stylesheets.

TinyMCE does not require any special file structure or tooling apart from these requirements, so custom plugins can be developed using most frameworks and tools.

Yeoman Generator

Tiny maintains a Yeoman generator to assist with creating plugins for TinyMCE. The Yeoman Generator will create the files and boilerplate code required for a custom plugin, and sets up some helpful commands.

Registering a custom plugin with TinyMCE

Register a custom plugin with TinyMCE using the PluginManager. PluginManager.add() takes a string for the plugin identifier and a function that contains the code for initializing the plugin.

The plugin identifier passed to PluginManager.add() is used by TinyMCE as an identifier string. It should:

  • Be an alphanumeric string,

  • Not contain any spaces or other whitespaces,

  • Be a unique identifier that does not match the IDs of plugins provided with TinyMCE or any other TinyMCE plugin in use.

If multiple plugins have the same identifier, one will override the others.

Optionally, the function passed to PluginManager.add() can return an object that contains data that TinyMCE or other plugins can use. Tiny recommends including a getMetadata callback that returns an object containing data that can be used to populate the list of plugins in the Help plugin dialog. The metadata object should contain the following values:

  • name: A string that contains the plugin’s name, usually in a human-readable format.

  • url: A string that contains a URL, usually used to link to help documentation.

Example: Plugin registration boilerplate

tinymce.PluginManager.add('pluginId', (editor, url) => {
  // add plugin code here

  return {
    getMetadata: () => ({
      name: 'Custom plugin',
      url: 'https://example.com/docs/customplugin'
    })
  }
});

Using custom plugins with TinyMCE

Custom plugins can be added to a TinyMCE instance by either:

  • Using external_plugins: Use the external_plugins option to specify the URL-based location of the entry point file for the plugin.

  • Copy code into plugins folder: Copy the entry point file (and any other files) into the plugins folder of the distributed TinyMCE code. The plugin can then be used by including it in the list of plugins specified by the plugins option.

Tiny recommends using the external_plugins option for custom plugins.

Example: Using external_plugins

tinymce.init({
  selector: 'textarea',  // change this value according to your HTML
  external_plugins: {
    pluginId: 'https://example.com/customplugincode.min.js'
  },
  plugins: ''
});

Example: Copying plugin code into the plugins folder

tinymce.init({
  selector: 'textarea',  // change this value according to your HTML
  plugins: 'pluginId'
});

Testing TinyMCE

Due to the range of browser APIs used by TinyMCE; when testing TinyMCE or any custom plugins, TinyMCE requires a testing framework that runs on a real browser. Testing frameworks that mock the browser will not work.

Language localization

If a custom plugin includes any custom UI created using TinyMCE’s UI APIs, then it may require localization.

TinyMCE comes with translations for many strings in many languages. To add additional strings to a supported language for a custom plugin, use the following procedure.

  1. Create a “langs” directory in the custom plugin’s root directory for custom translations.

  2. For each language that the plugin supports, create a translation file.

    The files should be JavaScript files and use the relevant language code as the file name. For example: TinyMCE will search for a Spanish translation file at <your plugin>/langs/es_ES.js, where <your plugin> is to the directory that contains the plugin’s entry point file. For a list of supported languages, see: Supported languages.

  3. In each translation file, add translation strings by passing an object containing key-value pairs of source strings and translation strings to the tinymce.addI18n() API.

  4. In the plugin’s entry point file, call tinymce.PluginManager.requireLangPack() and pass it the plugin identifier and a comma-delimitated string of the language codes to load.

Example: The content of a translation file for additional Spanish translations

tinymce.addI18n('es_ES', {
  'Example plugin': 'Complemento de ejemplo'
});

Example: Loading additional translation files for a custom plugin

// Register the custom plugin
tinymce.PluginManager.add('pluginId', (editor, url) => {
  // add plugin code here
});
// Load the required translation files
tinymce.PluginManager.requireLangPack('pluginId', 'es_ES,de_AT');

Example plugin

This example plugin demonstrates how to add a simple toolbar button and menu item. This button opens a dialog that allows a title to be entered into the editor. The menu item will open the same dialog as the button.

  • TinyMCE

  • HTML

  • JS

  • Edit on CodePen

<textarea id="custom-plugin"><p>To add a `Title: ` to the editor content, click "My button" and fill the dialog, then save the change.</p><p>&nbsp;</p></textarea>
/*
  Note: We have included the plugin in the same JavaScript file as the TinyMCE
  instance for display purposes only. Tiny recommends not maintaining the plugin
  with the TinyMCE instance and using the `external_plugins` option.
*/
tinymce.PluginManager.add('example', (editor, url) => {
  const openDialog = () => editor.windowManager.open({
    title: 'Example plugin',
    body: {
      type: 'panel',
      items: [
        {
          type: 'input',
          name: 'title',
          label: 'Title'
        }
      ]
    },
    buttons: [
      {
        type: 'cancel',
        text: 'Close'
      },
      {
        type: 'submit',
        text: 'Save',
        buttonType: 'primary'
      }
    ],
    onSubmit: (api) => {
      const data = api.getData();
      /* Insert content when the window form is submitted */
      editor.insertContent('Title: ' + data.title);
      api.close();
    }
  });
  /* Add a button that opens a window */
  editor.ui.registry.addButton('example', {
    text: 'My button',
    onAction: () => {
      /* Open window */
      openDialog();
    }
  });
  /* Adds a menu item, which can then be included in any menu via the menu/menubar configuration */
  editor.ui.registry.addMenuItem('example', {
    text: 'Example plugin',
    onAction: () => {
      /* Open window */
      openDialog();
    }
  });
  /* Return the metadata for the help plugin */
  return {
    getMetadata: () => ({
      name: 'Example plugin',
      url: 'http://exampleplugindocsurl.com'
    })
  };
});

/*
  The following is an example of how to use the new plugin and the new
  toolbar button.
*/
tinymce.init({
  selector: 'textarea#custom-plugin',
  plugins: 'example help',
  toolbar: 'example | help'
});