Adding custom buttons in TinyMCE editor in WordPress

An image showing a browser screen with a block inside

Today I’ll show you how to add your own custom buttons to TinyMCE Editor included with WordPress. I’ll be adding a button with which you can add columns in your editor. This however can be customized to your liking, to add certain classes, or stuff like that.

Notice: This article was written in 2016. WordPress has changed a lot since then, so take this article with a grain of salt…

As a base I’ll use Twenty sixteen theme. In your functions.php you’ll need to register the function which will modify the editor, and also include additional editor style. This is done so that when you add your columns, you can actually see them when writing – which is more user friendly.

add_action( 'after_setup_theme', 'mythemeslug_theme_setup' );

if ( ! function_exists( 'mythemeslug_theme_setup' ) ) {
	function mythemeslug_theme_setup(){
		/********* Registers an editor stylesheet for the theme ***********/
		add_action( 'admin_init', 'mythemeslug_theme_add_editor_styles' );
		/********* TinyMCE Buttons ***********/
		add_action( 'init', 'mythemeslug_buttons' );
	}
}
Code language: PHP (php)

Next, you’ll add the necessary functions

/********* Registers an editor stylesheet for the theme ***********/
if ( ! function_exists( 'mythemeslug_theme_add_editor_styles' ) ) {
	function mythemeslug_theme_add_editor_styles() {
	    add_editor_style( 'custom-editor-style.css' );
	}
}

/********* TinyMCE Buttons ***********/
if ( ! function_exists( 'mythemeslug_buttons' ) ) {
	function mythemeslug_buttons() {
		if ( ! current_user_can( 'edit_posts' ) && ! current_user_can( 'edit_pages' ) ) {
	        return;
	    }

	    if ( get_user_option( 'rich_editing' ) !== 'true' ) {
	        return;
	    }

	    add_filter( 'mce_external_plugins', 'mythemeslug_add_buttons' );
	    add_filter( 'mce_buttons', 'mythemeslug_register_buttons' );
	}
}

if ( ! function_exists( 'mythemeslug_add_buttons' ) ) {
	function mythemeslug_add_buttons( $plugin_array ) {
	    $plugin_array['columns'] = get_template_directory_uri().'/js/tinymce_buttons.js';
	    return $plugin_array;
	}
}

if ( ! function_exists( 'mythemeslug_register_buttons' ) ) {
	function mythemeslug_register_buttons( $buttons ) {
	    array_push( $buttons, 'columns' );
	    return $buttons;
	}
}Code language: PHP (php)

Now, the first is the registered editor stylesheet, and the second is the function for our buttons. In it you can check if you’re on post/page edit and if the rich text editor – TinyMCE is present. If those conditions are set, you can add filters that will modify the existing filters for the TinyMCE editor – mce_external_plugins and mce_buttons. If you want to know more about actions and filters I recommend an article Mastering WooCommerce Actions and Filters.

Notice that you have added the javascript file that will govern the behavior of the button, and you’ve added the name of your button to the existing buttons array called columns. This name is important because it acts as an identifier for your button. It’s best to give it a unique name.

Insert Column button in TinyMCE
A new button that you’ve added to your visual editor

JavaScript code for TinyMCE

The code inside tinymce_buttons.js looks like this

(function() {
    tinymce.PluginManager.add( 'columns', function( editor, url ) {
        // Add Button to Visual Editor Toolbar
        editor.addButton('columns', {
            title: 'Insert Column',
            cmd: 'columns',
            image: url + '/columns.jpg',
        });

        editor.addCommand('columns', function() {
            var selected_text = editor.selection.getContent({
                'format': 'html'
            });
            if ( selected_text.length === 0 ) {
                alert( 'Please select some text.' );
                return;
            }
            var open_column = '<div class="column">';
            var close_column = '</div>';
            var return_text = '';
            return_text = open_column + selected_text + close_column;
            editor.execCommand('mceReplaceContent', false, return_text);
            return;
        });

    });
})();Code language: JavaScript (javascript)

Since TinyMCE editor is included with WordPress*, you can readily use the options for it. We are adding a new button, so we are adding the columns to the tinymce object. First we add the button using addButton method. If you want to have translatable name I recommend localization. Just add the translatable string to the localized object and pass it to the title. The image needs to be located in the same file where the js file is located – or you can use localization to specify the path of the image to use. The command refers to the addCommand method. That method determines the functionality of your button. In our case we want to get the selected text and wrap it in a column div.

CSS part

To actually be able to visualize our columns we need the inline editor styles. In the custom-editor-style.css add

#tinymce .column{
	width: 48%;
	display: inline-block;
	vertical-align: top;
	margin-bottom: 10px;
	background: #F1F1F1;
}

#tinymce .column:nth-of-type(2n+2) {
    margin-left: 4%;
}


#tinymce .column img{
    max-width: 100%;
    margin: 0;
    margin-bottom: 10px;
}Code language: CSS (css)

This works for 2 columns – wraps the text in a div, and makes it approximately half the size, and adds a gray background so that you can see the column more clearly.

And that’s it. Simple enough. You can of course modify this to add different functionality. There are number of examples on the web.

Note about the usage of the columns – you need to write your text first, select the text you want in one column, push the button, select the text in the second column, push the button, and you’ll have properly formatted columns.

Formatted text using new button
Formatted text using new button for TinyMCE editor

Hope this helps with the creation of different buttons that you can use on your plugins as well. If you have anything to comment or ask, please do so below in the comment section. Happy coding!

Update 28.09.2020

The classic editor is now a plugin, that will be supported until the end of 2021. The new editor, or Gutenberg, is here to stay. I haven’t tested these tutorials with the classic editor plugin, but it should be working fine.

13 responses

  1. […] on the TinyMCE post I made earlier, I want to show you how to add a button on the TinyMCE editor for your custom […]

  2. About the first Code Snippet in your post:
    Why do you only add the two action hooks within the ‘after_theme_setup’ hook? Don’t we have to add the two action hooks on every page load?

    1. Well you can have other hooks inside the after_setup_theme hook as well. The first hook just adds additional style for the tinymce editor, and the second one will make sure that register hooks for the tinymce will hook in the correct place (init hook).
      Not sure where is the problem actually.

      1. Ah I accidentally read “after_switch_theme”, don’t mind me. Is there an advantage of some kind in adding the filters within the “after_theme_setup” hook?

        1. Yes there is. It lets all the the basic functionality of your theme loads, so you can safely add additional functionality to it, like registering tinymce buttons. Some functions have to be added in certain places so that they can use underlying code loaded before them.

          It’s a feature of dynamic languages, like php. If php was a compile language, you’d probably be able to write a function anywhere and not use filter and action hooks, and the compiler would do all the ordering and magic it does. But it’s not so this is something you need to be careful when doing programming in WordPress and php :)

          1. I’m quite experienced in WordPress and PHP so I know about Hooks & Filters in general but I always just add them outside of the ‘after_setup_theme’ hook so I’m curious what the benefits are.

          2. Well it definitely depends where you put them outside :D If I’m not mistaken, after_setup_theme hook will execute functions after all the necessary theme functionality has been loaded. Plus it loads before init hook, so some functionality won’t work if you put them on init and not on after_setup_theme hook (I think that add_theme_support won’t work on init, and has to be loaded on after_setup_theme).

          3. add_action() works anytime. Thank’s for the article though. It helped me!

          4. Well yeah, of course it will work. It’s basically a wrapper for call_user_func :) But the place where some function calls are made is important – that was my point :) I’m glad I could help :)

  3. Brian Musinski Avatar
    Brian Musinski

    So how would you unwrap it if its all ready wrapped in the div?

    1. You mean if it’s in two columns, and you click on them again to remove columns? You’d have to search for the added class in the tinymce javascript code, and then remove the wrapping div. Of the top of my head, probably copying the inner contents, removing the div altogether and then just insert the copied content in the position of the div. Tricky, but not impossible.

      1. Brian Musinski Avatar
        Brian Musinski

        So I ended up using some different code to add a class then I use css3 column-count to set columns. but basically it searches through the dom and of the active editor i think… and then finds what it needs and adds a class or removes the class It just adds a class to a certain node. and if there is no node it adds a tag.

        so i made a custom plugin for the tinyMCE and did it this way

        (function() {
        tinymce.PluginManager.add(‘customBtns’, function(editor,url){
        editor.addButton(‘customBtns’, {
        title : ‘Css 2 Col’,
        icon : ‘icon layout-layout3’,
        text : ‘ 2 Col’,
        onclick : function() {
        if(tinyMCE.activeEditor.dom.hasClass(tinyMCE.activeEditor.selection.getNode(), ‘cols’)) {
        tinyMCE.activeEditor.dom.removeClass(tinyMCE.activeEditor.selection.getNode(), ‘cols’);
        } else {
        tinyMCE.activeEditor.dom.addClass(tinyMCE.activeEditor.selection.getNode(), ‘cols’);

        }
        }
        });
        });
        })();

        1. And this works? If so then great :)

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.