D
P
0

WordPress

Built a Custom Gutenberg Block but It Never Shows Up in the Inserter? The Site Runs Classic Editor

July 1, 2026·4 min read
Built a Custom Gutenberg Block but It Never Shows Up in the Inserter? The Site Runs Classic Editor

A client asked for something that sounded simple: an embeddable content module their editors could drop into the middle of an article. A card of dynamic data that renders consistently wherever it lands. So I did what I considered the "right modern thing": I built a custom Gutenberg block. A tidy block.json, an edit component with a live preview, a server-side render callback so the markup always comes from fresh data. I registered the block, tested it on my dev install, and it was lovely: type the block name into the inserter, fill in the parameters, done. I shipped it to production feeling proud of myself.

A few hours later the client replied: "Where is the block? I cannot find it anywhere in the editor."

The investigation

My first instinct: something went wrong in the deploy. I checked that the plugin was active in production, that register_block_type ran without errors, that error_log was clean, that the cache was purged. All green. Yet the client still could not find the block, and this was not someone who gives up after one glance.

Eventually I asked for a screenshot of their post edit screen. That one image answered everything: there was no block inserter. No add-block button, no Gutenberg sidebar. Just the classic TinyMCE toolbar — bold, italic, the format dropdown. The production site was running the Classic Editor plugin, site-wide, locked down with no per-user switch.

The root cause

Classic Editor does not hide the block editor; it replaces it entirely. The block inserter is not tucked away in some menu — it simply never exists on that screen. Meanwhile register_block_type still succeeds server-side without a single warning, because registering a block is a perfectly legal operation. My block sat neatly registered in a registry nobody would ever open.

So the bug was not in the code. The bug was in my assumption: I never checked which editor the site actually used before I started building. I built UI for the environment I imagined, not the environment the client had.

The fix: a shortcode plus a TinyMCE button

The good news: from the start I had written the render logic as a standalone PHP function rather than welding it into the block definition. So the core feature needed no rewrite at all. The same function just gets registered as a shortcode:

// The same render function that backed the block's render_callback.
add_shortcode('content_module', 'render_content_module');
 
function render_content_module($atts) {
    $atts = shortcode_atts([
        'id'     => 0,
        'layout' => 'default',
    ], $atts, 'content_module');
 
    ob_start();
    get_template_part('template-parts/content-module', null, $atts);
    return ob_get_clean();
}

But asking editors to hand-type a shortcode complete with parameters is a recipe for typos. So I added a small button to the TinyMCE toolbar via two filters:

add_filter('mce_buttons', function ($buttons) {
    $buttons[] = 'content_module_button';
    return $buttons;
});
 
add_filter('mce_external_plugins', function ($plugins) {
    $plugins['content_module_button'] = get_template_directory_uri()
        . '/js/content-module-button.js';
    return $plugins;
});

The JS plugin is deliberately as small as possible: one button, one prompt, insert the shortcode:

tinymce.PluginManager.add('content_module_button', function (editor) {
    editor.addButton('content_module_button', {
        text: 'Content Module',
        onclick: function () {
            var moduleId = window.prompt('Module ID:');
            if (moduleId) {
                editor.insertContent(
                    '[content_module id="' + moduleId + '"]'
                );
            }
        }
    });
});

The end result: exactly the same feature, exactly the same front-end markup, but inserted from a toolbar that actually exists in front of the editors. The client was happy, and they never knew the first version of this feature lived in an editor they did not have.

The checklist before building editor UI

  • Verify which editor the site actually uses before writing any editor UI. Fastest way: open the post edit screen yourself. Programmatic way:
include_once ABSPATH . 'wp-admin/includes/plugin.php';
 
if (is_plugin_active('classic-editor/classic-editor.php')) {
    // No block inserter here. Ship a shortcode instead.
}
  • Write the render logic as a standalone function. The same function can back a block's render_callback and an add_shortcode callback. This is what saved me from rewriting the feature.
  • Remember that "modern" is a property of the environment, not of the code. A Gutenberg block is the right answer on a site running the block editor. On a site locked to Classic Editor, it is just dead code, neatly registered.

Since this incident, my first question before building anything that touches the writing experience is no longer "block or shortcode?" but "which editor is it?".