Last updated: May 22 2018

Area: Episerver CMS Applies to versions: TinyMCE 2 and higher
Other versions:

Customizing the TinyMCE editor

Note: Episerver supports TinyMCE version 4. See the TinyMCE website for information on how to create the client-side code of a TinyMCE plugin.

Note: In CMS 11, the TinyMCE editor and related plugin configuration features were moved into a separate NuGet package as an add-on with its own versioning number and breaking changes.

Note: From version 2.0, you can no longer customize the TinyMCE editor from the CMS admin view. All changes are done through code.

This topic describes how to configure the TinyMCE editor. It also covers how to add your own plugins, and tips for using TinyMCE on your template page.

In this topic

Default settings

Episerver comes with a preconfigured TinyMCE editor that renders all XHTML properties by default.

The default configuration for TinyMCE is using the constants defined on the class EPiServer.Cms.TinyMce.Core.DefaultValues.

The constant DefaultValues.EpiserverPlugins defines the default Episerver plugins:

"epi-link epi-image-editor epi-dnd-processor epi-personalized-content"

Note: The epi-dnd-processor is a required plugin to be able to drag and drop Episerver content.

The constant DefaultValues.TinyMcePlugins defines the default TinyMCE plugins:

"help image fullscreen lists searchreplace"

The constant DefaultValues.Toolbar defines the default toolbar configuration:

"formatselect | bold italic | epi-link image epi-image-editor epi-personalized-content | bullist numlist outdent indent | searchreplace fullscreen | help"

Overriding the defaults

In order to override the default settings, add a new initialization module with a dependency on the default one.

The TinyMceSettings class provides helper methods for setting the most common settings within TinyMCE. These map to the configuration settings documented on TinyMCE's website: https://www.tinymce.com/docs/configure/

However, it is still possible to configure settings that do not have a helper method by using the AddSetting or RawSettings methods.

[ModuleDependency(typeof(TinyMceInitialization))]
public class CustomizedTinyMceInitialization : IConfigurableModule
{
    public void Initialize(InitializationEngine context)
    {
    }

    public void Uninitialize(InitializationEngine context)
    {
    }

    public void ConfigureContainer(ServiceConfigurationContext context)
    {
        context.Services.Configure<TinyMceConfiguration>(config =>
        {
            config.Default()
                .BodyClass("custom_body_class")
                .ContentCss("/static/css/editor.css")
                .AddSetting("directionality", "rtl");
        });
    }
}

Property configuration

If you want to define a configuration to use on different properties you can do that in a few ways.

One way to accomplish that is to create a configuration for a property on one page type first and later copy that configuration to another page types property:

    // create configuration for the main body property on the article page type
    config.For<ArticlePage>(t => t.MainBody)
        .AddPlugin("print")
        .Toolbar("cut", "paste")
        .DisableMenubar();

    // use settings from the article page types MainBody property 
    // on the SecondaryBody property of the standard page type.
    config.Use<ArticlePage, StandardPage>(p => p.MainBody, s => s.SecondaryBody);

Another option is to create a configuration instance and then use that on multiple properties:

    // create a simple configuration by cloning the default and modifying it
    var simpleConfiguration = config.Default().Clone()
        .BodyClass("simple_body")
        .Plugins("spellchecker");
    
    // use the configurations we created earlier on different properties
    config.For<ArticlePage>(a => a.MainBody, simpleConfiguration);
    config.For<NewsPage>(a => a.MainBody, simpleConfiguration);

To build on the example above you can also extend existing configurations if you need some additional configuration for a specific property:

    // extend the simple configuration with more advanced plugins
    var advancedConfiguration = simpleConfiguration.Clone()
        .AddPlugin("epi-link")
        .AddPlugin("code")
        .AppendToolbar("epi-link | code");

    config.For<StandardPage>(a => a.MainBody, advancedConfiguration);

Remember to call the Clone method so that you don't affect the configuration that you want to extend.

Setting custom configuration

Some settings are not available as strongly typed API, for instance you could have developed your own tinyMCE plugin that requires a custom setting.

To set a custom setting there are a couple of options. You can use either of the AddSetting or RawSetting methods:

    var extraCustomSettings = new Dictionary<string, object> {{"custom_tinymce_setting", "foo"}};

    var customConfig = config.Default().Clone()
        .AddSetting("my_own_setting", "lorem ipsum")
        .RawSettings(extraCustomSettings);

    config.For<EditorialBlock>(a => a.MainBody, customConfig);

The RawSetting method also has an overload that takes a JSON string.

Example with a lot of options set

Here is an example with a lot of options set and a toolbar with three rows:

    config.For<ProductPage>(p => p.MainBody)
        .Menubar("file edit insert view format table tools help")
        .Plugins("epi-link epi-image-editor epi-dnd-processor epi-personalized-content print preview searchreplace autolink directionality visualblocks visualchars fullscreen image link media template codesample table charmap hr pagebreak nonbreaking anchor toc insertdatetime advlist lists textcolor wordcount imagetools contextmenu colorpicker textpattern help")
        .Toolbar(
            "epi-link | epi-image-editor | epi-personalized-content | cut copy paste | fullscreen",
            "styleselect  formatselect | bold italic strikethrough forecolor backcolor | link | alignleft aligncenter alignright alignjustify  | numlist bullist outdent indent  | removeformat",
            "table | toc | codesample");

Note that the Plugins and Toolbar methods overwrites the values that had previously been configured so the end result might not be exactly what you expect. If you want to add to an existing configuration it's easier to use AddPlugin and AppendToolbar. AppendToolbar was released in version 2.1.2 of the Episerver.CMS.TinyMce package.

If we put all examples together you might end up with a configuration that looks something like this:

    [ModuleDependency(typeof(TinyMceInitialization))]
    public class CustomizedTinyMceInitialization : IConfigurableModule
    {
        public void Initialize(InitializationEngine context) { }

        public void Uninitialize(InitializationEngine context) { }

        public void ConfigureContainer(ServiceConfigurationContext context)
        {
            context.Services.Configure<TinyMceConfiguration>(config =>
            {
                // create configuration for the main body property on the article page type
                config.For<ArticlePage>(t => t.MainBody)
                    .AddPlugin("print")
                    .Toolbar("cut", "paste")
                    .DisableMenubar();

                // use settings from the article page types MainBody property 
                // on the SecondaryBody property of the standard page type.
                config.Use<ArticlePage, StandardPage>(p => p.MainBody, s => s.SecondaryBody);

                // create a simple configuration
                var simpleConfiguration = config.Default().Clone()
                    .BodyClass("simple_body")
                    .Plugins("spellchecker");

                // use the configurations we created earlier on different properties
                config.For<ArticlePage>(a => a.MainBody, simpleConfiguration);
                config.For<NewsPage>(a => a.MainBody, simpleConfiguration);

                // extend the simple configuration with more advanced plugins
                var advancedConfiguration = simpleConfiguration.Clone()
                    .AddPlugin("epi-link")
                    .AddPlugin("code")
                    .AppendToolbar("epi-link | code");

                config.For<StandardPage>(a => a.MainBody, advancedConfiguration);

                // for setting custom settings that are not available as API methods you can use these options
                var extraCustomSettings = new Dictionary<string, object> {{"custom_tinymce_setting", "foo"}};

                var customConfig = config.Default().Clone()
                    .AddSetting("my_own_setting", "lorem ipsum")
                    .RawSettings(extraCustomSettings);

                config.For<EditorialBlock>(a => a.MainBody, customConfig);

                // An example with a highly configured property using three rows in its toolbar
                config.For<ProductPage>(p => p.MainBody)
                    .Menubar("file edit insert view format table tools help")
                    .Plugins("epi-link epi-image-editor epi-dnd-processor epi-personalized-content print preview searchreplace autolink directionality visualblocks visualchars fullscreen image link media template codesample table charmap hr pagebreak nonbreaking anchor toc insertdatetime advlist lists textcolor wordcount imagetools contextmenu colorpicker textpattern help")
                    .Toolbar(
                        "epi-link | epi-image-editor | epi-personalized-content | cut copy paste | fullscreen",
                        "styleselect  formatselect | bold italic strikethrough forecolor backcolor | link | alignleft aligncenter alignright alignjustify  | numlist bullist outdent indent  | removeformat",
                        "table | toc | codesample");
            });
        }
    }

Setting transformations

If you want to modify the settings object based on, for example, the current content, you can do that by registering a SettingsTransform callback for a given property or plugin. You can also remove registered transformations if you know the key. The current settings instance, content, and property name will be passed to the callback. The content item can be NULL.

Add a setting transformation

context.Services.Configure<TinyMceConfiguration>(config =>
{
    var locator = ServiceLocator.Current;
 
    ...
    
    config.For<ArticlePage>(t => t.MainBody)
        .AddSettingsTransform("custom", (settings, content, propertyName) => {
            settings["preferred-culture"] = locator.GetInstance<LanguageResolver>().GetPreferredCulture().Name;
        });
 
    // Remove a specific transform
    config.For<ArticlePage>(t => t.MainBody)
        .RemoveSettingsTransform("custom");
 
    ...
}

Add a setting transformation for a plugin

If you are a plugin developer and need to run some custom initialization code based on the current content item, you should register the transformation callback with your plugin.

context.Services.Configure<TinyMceConfiguration>(config =>
{
    ...
    
    config.Default()
        .AddPlugin("custom-plugin", (settings, content, propertyName) => {
            settings["custom-plugin-setting"] = content.Id;
        })
        .RemovePlugin("custom-plugin"); // Will remove the plugin and any registered transformations;
 
    config.Default()
        .AddExternalPlugin("custom-external-plugin", "http://url.js", (settings, content, propertyName) => {
            settings["custom-external-setting"] = content.Id;
        });
 
    ...
}

Exceptions

You should try to write your code so that it does not throw any unexpected exceptions. However, if it throws an exception, the exception is handled, logged, and passed to the client and an error message is displayed to the user.

Order of execution

If there are transformations registered both for plugins and on the setting, the plugin transformation runs first and then any transformation on the setting. This will give the site developer the possibility to modify the settings after the plugin developer has done their modifications.

context.Services.Configure<TinyMceConfiguration>(config =>
{
    ...
    
    config.Default()
        .AddPlugin("custom-plugin", (settings, content, propertyName) => {
            settings["custom-plugin-setting"] = "plugin value";
        });
 
    config.Default()
        .AddSettingsTransform("custom-settings", (settings, content propertyName) => {
            settings["custom-plugin-setting"] = "I have modified the plugins settings value";
        });
 
    ...
}

Inherit settings from ancestors

To be able to inherit settings from an ancestor, you need to set the InheritSettingsFromAncestor property to true. It is set to false by default.

config.InheritSettingsFromAncestor = true

This is a global setting that will be applied for all types that are configured.

If InheritSettingsFromAncestor is true, Episerver tries to resolve the setting for a given property starting with the leaf; after that, it traverses upwards in the inheritance hierarchy to try to find the closest ancestor that have a setting for that property. If it cannot be found in the class hierarchy, Default() is used.

Example

abstract class ProductBase
{
    public virtual XHtml ProductDescription {get;set;}
}

class ProductPage : ProductBase
{
}

class ShoeProductPage : ProductPage
{
}

class ShirtProductPage : ProductPage
{
}

class BlueShirtProductPage : ShirtProductPage
{
}

class VerySpecialProductPage : ProductPage
{
}

class AndEvenMoreSpecialProductPage : VerySpecialProductPage
{
}

// Init
context.Services.Configure<TinyMceConfiguration>(config =>
{
    // Enable inheritance of settings
    config.InheritSettingsFromAncestor= true;

    config.Default()
        .Toolbar("epi-image");

    var baseSettings = config.Default().Clone();
    baseSettings
        .AddPlugin("product-selector")
        .Toolbar("product-selector cut paste");

    // Will be applied for every descendants unless they have set a specific setting
    config.For<ProductBase>(t=>t.ProductDescription, baseSettings);
    
    // Will be based on Default() and not the setting configured on the ancestor
    config.For<BlueShirtProductPage>(t=>t.ProductDescription)
        .AppendToolbar("color-picker");

    config.For<VerySpecialProductPage>(t=>t.ProductDescription, baseSettings)
        .Toolbar("cut past | product-selector | spellchecker);
});

/**
* Page                              Toolbar
* ==========================================================================
* ProductPage                       product-select cut paste
* ShoeProductPage                   product-select cut paste
* ShirtProductPage                  product-select cut paste
* BlueShirtProductPage              epi-image | color-picker
* VerySpecialProductPage            cut paste | product-select | spellchecker
* AndEvenMoreSpecialProductPage     cut paste | product-select | spellchecker
**/

Note: This property should not be changed by any plugins. It should only be used when configuring the site.

Custom style formats

Please read the TinyMCE documentation for more information about how style formats can be configured: https://www.tinymce.com/docs/configure/content-formatting/#style_formats

[ModuleDependency(typeof(TinyMceInitialization))]
public class CustomizedTinyMceInitialization : IConfigurableModule
{
    public void Initialize(InitializationEngine context)
    {
    }

    public void Uninitialize(InitializationEngine context)
    {
    }

    public void ConfigureContainer(ServiceConfigurationContext context)
    {
        context.Services.Configure<TinyMceConfiguration>(config =>
        {
            config.For<StandardPage>(t => t.MainBody)
                .Toolbar("styleselect")
                .StyleFormats(
                    new { title = "Bold text", inline = "strong" },
                    new { title = "Red text", inline = "span", styles = new { color = "#ff0000" } },
                    new { title = "Red header", block = "h1", styles = new { color = "#ff0000" } }
                );
        });
    }
}

Adding a TinyMCE plugin

The following steps show how to add your own plugin to the TinyMCE editor.

  1. Add an AMD module with your plugin code (more on plugins can be found here).
  2. Add the following piece of code to your custom TinyMCE initialization module.
[ModuleDependency(typeof(TinyMceInitialization))]
public class CustomizedTinyMceInitialization : IConfigurableModule
{
    public void Initialize(InitializationEngine context)
    {
    }

    public void Uninitialize(InitializationEngine context)
    {
    }

    public void ConfigureContainer(ServiceConfigurationContext context)
    {
        context.Services.Configure<TinyMceConfiguration>(config =>
        {
            config.Default()
                .AddExternalPlugin("custom-plugin", "alloy/plugins/custom-plugin.js");
        });
    }
}

Adding client side configuration

Some plugins require you to register a callback function and that can be hard to achieve using C# configuration. In that case, it might be more convenient to use client side configuration.

To enable that you need to first register an initialization script server side by using the InitializationScript method:

    [ModuleDependency(typeof(TinyMceInitialization))]
    public class CustomizedTinyMceInitialization : IConfigurableModule
    {
        public void Initialize(InitializationEngine context)
        {
        }

        public void Uninitialize(InitializationEngine context)
        {
        }

        public void ConfigureContainer(ServiceConfigurationContext context)
        {
            context.Services.Configure<TinyMceConfiguration>(config =>
            {
                config.Default()
                    .InitializationScript("alloy/advancedTinyMCEConfig");
            });
        }
    }

The initialization script will then be loaded and should return a function that takes a settings object as a parameter. The function must also return a settings object as that will be used to initialize the tinyMCE editor in the UI.

    define([], function () {

        return function (settings) {

            console.log("initialize TinyMCE advanced config");

            return Object.assign(settings, {
                myCallback: function () {
                    alert('hello');
                }
            });
        }
    });

Using TinyMCE on your template pages

If you want to use TinyMCE on your template pages, download your own version and place it on your website. This is because the TinyMCE version shipped with Episerver has a dependency to the user interface.

Localizing CSS stylesheets

Add the translations to an XML file. Add translation strings as children of the element editorstyles. The following example shows the CSS and XML. This also applies to block formats and style formats, just add the title as the key in the XML. The title need to be a valid XML key, so for example, you cannot use spaces.

XML

<language name="svenska" id="sv">
  <tinymce>
    <editorstyles>
      <my-class>Min klass</my-class>
      <header.dim>Nedtonad rubrik</header.dim>
    </editorstyles>
  </tinymce>
</language>

Custom Css

.header.dim {
    margin: 2% 0;
    opacity: 0.3;
}

.my-class {
    color: red;
}

Episervers TinyMCE plugins

Currently, Episerver has implemented four plugins: epi-link, epi-personalized-content, epi-image-editor and epi-dnd-processor.

  • The epi-link plugin
    This linking tool can handle links to Episerver content as well as links to external content. It is recommended to  use this plugin instead of TinyMCEs link plugin.
  • The epi-personalized-content plugin
    This plugin enables editing of personalized content in a TinyMCE editor.
  • The epi-image-editor plugin
    This plugin opens Episerver's custom image editor.
  • The epi-dnd-processor plugin
    This is a required plugin if you want to be able to drag and drop Episerver content, such as blocks and pages to your TinyMCE editor.

Related topics

Comments

how to add two rows of toolbar?

@Helani

The Toolbar method on the TinyMceconfiguration wants a string array

for example:
string[] defaultToolbar = {
"epi-link anchor image epi-image-editor media | cut copy pastetext paste removeformat | searchreplace fullscreen |",
"bold italic underline | numlist bullist indent outdent | formatselect styleselect | undo, redo | alignleft aligncenter alignright alignjustify |",
"table | lists | code | epi-dnd-processor |"
};

context.Services.Configure<TinyMceConfiguration>(config =>
{
config.Default()
.ContentCss("/static/css/editor.css")
.AddPlugin("media wordcount anchor code searchreplace paste table")
.Toolbar(defaultToolbar)

@Helani

What @Radek mentions is correct, the toolbar takes a string array where each array item is a row in the toolbar. The documentation has been updated with an example where we configure a property to have a toolbar with three rows:

// An example with a highly configured property using three rows in its toolbar
config.For<ProductPage>(p => p.MainBody)
    .Menubar("file edit insert view format table tools help")
    .Plugins("epi-link epi-image-editor epi-dnd-processor epi-personalized-content print preview searchreplace autolink directionality visualblocks visualchars fullscreen image link media template codesample table charmap hr pagebreak nonbreaking anchor toc insertdatetime advlist lists textcolor wordcount imagetools contextmenu colorpicker textpattern help")
    .Toolbar(
        "epi-link | epi-image-editor | epi-personalized-content | cut copy paste | fullscreen",
        "styleselect  formatselect | bold italic strikethrough forecolor backcolor | link | alignleft aligncenter alignright alignjustify  | numlist bullist outdent indent  | removeformat",
        "table | toc | codesample");

Thanks

Does nonbreaking and media exist? I'm trying to use them but they don't apperar in my toolbar.

Hi Carl,

Yes, all non premium plugins that are listed on tinymce's list of plugins should exist in a default installation.

Have you remembered to add the plugin to the toolbar?

config.Default()
    .AddPlugin("nonbreaking media")
    .Toolbar(DefaultValues.Toolbar + " | nonbreaking media");

It's a bit cumbersome to add to the existing toolbar at the moment but we're soon releasing a version were it will be easier to append plugins to an existing toolbar.

Did anyone succeed to add an importCss plugin and map a custom CSS file? Thank you

Ah, cheers! Yes, I forgot to add the plugin. I didn't realise I had to add plug in AND name to toolbar.

@PaulVoie Do you mean this?

.ContentCss("/static/css/yourCssFile.css")

How to add extended_valid_elements in v2?

@Rich You can use the generic AddSetting method to add any setting you want:

config.Default()
    .AddSetting("extended_valid_elements", "your setting here");