Last updated: Jun 18 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. There are also some plugins that are configured using specific configuration methods. Those are described in more detail under the Episerver TinyMCE plugins heading.

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 overwrite 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 is 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;
}

Episerver's TinyMCE plugins

These are the plugins that Episerver provides in the Episerver.CMS.TinyMce package:

  • 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.

  • The epi-image-tools plugin (only for version 2.3.0 and higher)

    This plugin lets you navigate directly to the image content with a push of a button, or see the image location path by hovering the button.



    This plugin is dependent on the TinyMCE plugin imagetools. The TinyMCE imagetools plugin is not fully compatible with Episerver since it is not integrated with how Episerver saves images, so in the default configuration we have also defined the imagetools_toolbar setting to only include Episerver's "epi-gotomedia" button.

    From version 2.4.0: Another useful feature added by this plugin is to allow the editor to drag & drop an image directly from a local disk, without having to upload the image to the media library before using it inside the TinyMCE editor.

    An image preview is displayed immediately upon dropping the image while it is being uploaded to the server.
    All dropped images are automatically placed in the local For This Page or For This Block folder.

    Please note that although it is technically possible to drag & drop multiple images at the same time, it does not work deterministically. There is a bug reported in TinyMCE #4055 which Episerver has submitted a bug fix  to. The bug is still pending review.

    Configuring this plugin should be done with the EnableImageTools/DisableImageTools configuration methods:

    context.Services.Configure<TinyMceConfiguration>(config =>
    {
        ...
        // Disable the image tools
        config.For<ArticlePage>(t => t.MainBody)
            .DisableImageTools()
        ...
        // Enable the image tools for a property (if the property does not have it already)
        config.For<ArticlePage>(t => t.MainBody)
            .EnableImageTools();
     
        ...
    }

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");

Is it possible to set these configurations for blocks? I'm running into an issue where i cant remove buttons for a config set in blocks.
It just enables the default buttons that i can't edit.

How to use AddSetting for a property like 'image_class_list' in 'image' plugin?

image_class_list: [{title: 'None', value: ''}, {title: 'class one', value: 'one'}]


@J Jacobs: 

You should be able to configure xhtml properties on blocks.

config.For<TeaserBlock>(t => t.TestXhtmlString)
    .BlockFormats("Header1=h1;Header2=h2")
    .ContentCss("/Assets/css/editor.css");


We have however a known issue regarding blocks when they are included on a page model: https://world.episerver.com/support/Bug-list/bug/CMS-10715

A bugfix for that issue will be included in the next release we make for the EpiServer.Cms.TinyMce package (version 2.4.0).

@Helani

You can use something like this:

    .AddSetting("image_class_list", new[] {
        new { title = "None", value = "" },
        new { title = "class one", value = "one" }
    });

@ Nicklas Israelsson Thank you

Did the "Edit CSS" (TinyMCEButtons.StyleProperties) plugin not come over with the new Editor?

@Michael The old TinyMCEButtons.StyleProperties referenced this plugin: https://www.tinymce.com/docs-3x/reference/plugins/Plugin3x@style/

Unfortunately there doesn't seem to be any similar plugin in the new version of TinyMCE: https://www.tinymce.com/docs/plugins/

How to put the styles under category e.g.

 .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" } }
                );

should be shown under "Text Styles" . When you hover "text styles" the abov ethree styles will show.

@Rich You can read more about configuring TinyMCE style formats here: https://www.tinymce.com/docs/configure/content-formatting/#style_formats

All the configuration options for the TinyMceSettings object get translated to JSON and passed to the editor on startup. So to add groups you need to write C# code that will generate the same JSON object as defined in the TinyMCE documentation. E.g.

.StyleFormats(
    new { title= "Text Styles", items = new object[] {
        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" } }
    }}
)

@Ben thank you. I am getting more help and understanding of new TinyMCE plugin then anywhere else. While you all are too generous in helping my help can you help me understand how to add default CSS class to the table? When I add a table it should have a default CSS class e.g class="table" without any styles attributes. Currently when i add the table it comes with syles style="border-collapse: collapse; width: 100%;" border="1"

@Rich You can look at the available configuration options for the table plugin here: https://www.tinymce.com/docs/plugins/table/, perhaps table_default_attributes or table_class_list will help. I haven't tested so I'm not sure.

Also can you please ask any further questions in the forums. That will make it easier for people with similar questions to find the answer as well!

What about localization for StyleFormats? F.E. How to add localizations for titles: "Text Styles", "Bold text", "Red text", "Red header"?