Add a TinyMCE Rich Text Editor to WordPress Admin Pages with wp_editor()

The wp_editor() function renders a full WordPress TinyMCE rich-text editor at any point in a PHP template — admin pages, custom meta boxes, plugin settings screens, or even front-end forms. It initialises TinyMCE with WordPress’s default configuration (media buttons, kitchen-sink toolbar, autosave) and handles all the necessary JavaScript loading automatically. This is the correct tool when you need a content editor outside of the standard post editing screen: a plugin options page where admins write formatted HTML email templates, a meta box for a custom “sidebar content” field on a page, or a front-end form where logged-in users submit rich-text content. The function accepts an extensive configuration array to control toolbar buttons, media upload capability, textarea dimensions, and TinyMCE plugins.

Problem: Your plugin settings page needs a WYSIWYG editor for a custom email template — administrators should be able to format text, add links, and see a live preview without writing HTML manually.

Solution: Call wp_editor() inside the settings form, configure the toolbar and media options via the $settings array, and save the output with wp_kses_post() for security.

<?php
// ── Basic wp_editor() usage ────────────────────────────────────────────
function render_email_template_setting() {
    $content = get_option( 'my_plugin_email_template', '' );

    wp_editor(
        $content,           // initial content (string)
        'my_email_template', // unique ID — used for the textarea id and JS init
        [
            'textarea_name' => 'my_plugin_email_template', // form field name for $_POST
            'textarea_rows' => 15,
            'media_buttons' => false,    // hide "Add Media" button
            'tinymce'       => [
                'toolbar1' => 'bold,italic,underline,separator,link,unlink,separator,undo,redo',
                'toolbar2' => '',        // hide second toolbar row
            ],
            'quicktags'     => true,     // show HTML tab
            'editor_class'  => 'my-editor-css-class',
        ]
    );
}

// ── In a meta box ────────────────────────────────────────────────────────
function render_sidebar_content_meta_box( WP_Post $post ) {
    wp_nonce_field( 'sidebar_content_nonce', 'sidebar_content_nonce' );
    $value = get_post_meta( $post->ID, '_sidebar_content', true );

    wp_editor( $value, 'sidebar_content_editor', [
        'textarea_name' => 'sidebar_content',
        'textarea_rows' => 10,
        'teeny'         => true, // minimal toolbar
    ] );
}

// ── Save the value ────────────────────────────────────────────────────────
add_action( 'save_post', 'save_sidebar_content' );

function save_sidebar_content( $post_id ) {
    if ( ! isset( $_POST['sidebar_content_nonce'] )
         || ! wp_verify_nonce( $_POST['sidebar_content_nonce'], 'sidebar_content_nonce' )
         || defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE
         || ! current_user_can( 'edit_post', $post_id ) ) {
        return;
    }
    update_post_meta(
        $post_id,
        '_sidebar_content',
        wp_kses_post( $_POST['sidebar_content'] ?? '' )
    );
}

NOTE: wp_editor() must be called before wp_footer() fires — the function outputs both the HTML markup and a JavaScript initialisation call. If you call it inside an AJAX response or after the footer has already been sent, TinyMCE will not initialise. For dynamically inserted editors (e.g. in a JavaScript-triggered modal), you need to manually call tinymce.init() or use wp.oldEditor.initialize() after the DOM element is inserted. Also, each editor on the same page must have a unique ID string — duplicate IDs cause only the first editor to initialise.