WP Webhooks / Blog / WordPress internals
Article · WordPress internals

Adding Controls to the Publish Box with post_submitbox_misc_actions

post_submitbox_misc_actions documentation: the exact signature, why the hook never fires in the block editor, and the PluginPostStatusInfo equivalent that does.

8 min 2026-08-10
#hooks#wp-admin#gutenberg

TL;DR: post_submitbox_misc_actions still works exactly as documented — it just never fires on the editor most sites now use.

  • Signature: do_action( 'post_submitbox_misc_actions', $post ). Added in WordPress 2.9.0; the $post parameter arrived in 4.4.0.
  • It fires inside post_submit_meta_box(), after the post date setting, in wp-admin/includes/meta-boxes.php.
  • The Publish box is registered with __back_compat_meta_box => true, and the block editor skips every box carrying that flag — so the hook never runs there.
  • There is no error and no notice. Your callback simply never executes.
  • The block editor equivalent is a PluginPostStatusInfo slot registered in JavaScript.

/ Overview

When does post_submitbox_misc_actions fire?

When WordPress renders the classic Publish meta box, immediately after the post date row and before the box closes. It is the standard extension point for adding your own control — a checkbox, a select, a status line — into the panel that already holds Status, Visibility and Publish immediately.

The declaration in meta-boxes.php is short, and the version tags matter:

PHP — wp-admin/includes/meta-boxes.php

/**
 * Fires after the post time/date setting in the Publish meta box.
 *
 * @since 2.9.0
 * @since 4.4.0 Added the $post parameter.
 *
 * @param WP_Post $post WP_Post object for the current post.
 */
do_action( 'post_submitbox_misc_actions', $post );

One parameter, a WP_Post. Code written before WordPress 4.4 calls get_post() inside the callback instead, which still works and is why so much of the sample code you will find online ignores the argument entirely.

/ The trap

Why does it do nothing in the block editor?

Because the box it lives in is never rendered there, and the mechanism that removes it is deliberate rather than accidental. WordPress registers the Publish box like this:

PHP — how submitdiv is registered

$publish_callback_args = array( '__back_compat_meta_box' => true );

add_meta_box(
    'submitdiv', __( 'Publish' ), 'post_submit_meta_box',
    null, 'side', 'core', $publish_callback_args
);

And do_meta_boxes() reads that flag when it walks the registered boxes:

PHP — wp-admin/includes/template.php

// If a meta box is just here for back compat, don't show it in the block editor.
if ( $screen->is_block_editor() && isset( $box['args']['__back_compat_meta_box'] )
     && $box['args']['__back_compat_meta_box'] ) {
    continue;
}

That continue is the whole story. The box is skipped, so post_submit_meta_box() is never called, so do_action( 'post_submitbox_misc_actions', $post ) is never reached. Your add_action() registered successfully. The hook exists. It simply has no caller on that screen.

There is no deprecation notice, no admin warning and no PHP error. The only symptom is a control that used to be there and now is not — which is exactly the class of bug that survives a code review.
How the editor choice decides whether post_submitbox_misc_actions runsBoth editors register the Publish meta box under the id submitdiv, but it is registered with the __back_compat_meta_box argument set to true. When the screen is the block editor, do_meta_boxes skips every box carrying that flag, so post_submit_meta_box is never called and post_submitbox_misc_actions never fires. On the classic editor the same box renders normally and the hook fires after the post date setting. Attachments take a third path and fire attachment_submitbox_misc_actions instead. The block editor equivalent is a PluginPostStatusInfo slot rendered in JavaScript.

yes

no, classic editor

attachment screen

edit screen loads

add_meta_box('submitdiv', ...)
__back_compat_meta_box = true

screen is block editor?

do_meta_boxes() skips the box

post_submit_meta_box() never runs
hook never fires, no error

use a PluginPostStatusInfo slot
registered in JavaScript

post_submit_meta_box() renders

do_action('post_submitbox_misc_actions', $post)

field posts with the form
read it on save_post after a nonce check

attachment_submit_meta_box()
fires attachment_submitbox_misc_actions

FIG 01 — Why the same hook fires on one editor and not the other

/ Family

What are the sibling publish-box hooks?

Four, and picking the right one saves a lot of CSS. They all live in the same file and all share the block-editor fate above.

HookSinceRendersParameter
post_submitbox_start2.7.0Top of the publishing-actions row, next to Move to Trash$post — or null on the Edit Link screen
post_submitbox_minor_actions4.4.0After Save Draft and Preview$post
post_submitbox_misc_actions2.9.0After the date setting, inside the misc section$post (since 4.4.0)
attachment_submitbox_misc_actions3.5.0The attachment edit screen’s Save box$post

Two details in that table catch people out. post_submitbox_start is documented as WP_Post|null because the Edit Link screen fires it with null — a callback that types the parameter as WP_Post or calls a method on it will fatal there. And attachments never reach post_submitbox_misc_actions at all: they render attachment_submit_meta_box() instead, which fires its own attachment_submitbox_misc_actions.

/ Rendering

How do you render a field in the Publish box?

Echo the markup, wrapped in the class WordPress already styles, and include a nonce. The misc-pub-section class gives you the same padding and separator as the built-in rows, so the control does not look bolted on:

PHP — a control in the Publish box

add_action( 'post_submitbox_misc_actions', function ( $post ) {
    if ( ! in_array( $post->post_type, [ 'post', 'product' ], true ) ) {
        return;
    }
    if ( ! current_user_can( 'edit_post', $post->ID ) ) {
        return;
    }

    $notify = '1' === get_post_meta( $post->ID, '_yp_notify', true );

    wp_nonce_field( 'yp_notify_save', 'yp_notify_nonce' );
    ?>
    <div class="misc-pub-section yp-notify">
        <label>
            <input type="checkbox" name="yp_notify" value="1"
                   <?php checked( $notify ); ?> />
            <?php esc_html_e( 'Notify downstream systems', 'your-plugin' ); ?>
        </label>
    </div>
    <?php
} );

The capability check is not optional. The hook fires for anyone who can reach the edit screen, and contributors reach it for their own drafts.

/ Saving

How do you save what the field submits?

On save_post, guarded properly. The input is part of the normal post form, so it arrives in $_POST — but that same hook also runs for autosaves, revisions and REST requests that never included your field. Treating a missing key as "unchecked" without those guards will silently wipe the value every time an autosave fires.

PHP — the save handler

add_action( 'save_post', function ( $post_id ) {
    if ( wp_is_post_autosave( $post_id ) || wp_is_post_revision( $post_id ) ) {
        return;
    }
    if ( ! isset( $_POST['yp_notify_nonce'] ) ) {
        return;   // not our form — leave the meta alone
    }
    if ( ! wp_verify_nonce(
        sanitize_text_field( wp_unslash( $_POST['yp_notify_nonce'] ) ),
        'yp_notify_save'
    ) ) {
        return;
    }
    if ( ! current_user_can( 'edit_post', $post_id ) ) {
        return;
    }

    update_post_meta(
        $post_id, '_yp_notify',
        empty( $_POST['yp_notify'] ) ? '0' : '1'
    );
} );

The isset( $_POST['yp_notify_nonce'] ) check is the important one, and it is the guard most snippets omit. It is what distinguishes "the user unchecked the box" from "this save did not come from the classic edit form at all".

/ Block editor

What is the block editor equivalent?

A PluginPostStatusInfo slot, registered in JavaScript against the same post meta. There is no PHP hook that renders into the block editor's Summary panel, because that panel is React and is never built server-side.

JS — the same control, block editor side

import { registerPlugin } from '@wordpress/plugins';
import { PluginPostStatusInfo } from '@wordpress/editor';
import { CheckboxControl } from '@wordpress/components';
import { useEntityProp } from '@wordpress/core-data';

const NotifyToggle = () => {
  const [ meta, setMeta ] = useEntityProp( 'postType', 'post', 'meta' );

  return (
    <PluginPostStatusInfo>
      <CheckboxControl
        label={ 'Notify downstream systems' }
        checked={ !! meta?._yp_notify }
        onChange={ ( value ) => setMeta( { ...meta, _yp_notify: value } ) }
      />
    </PluginPostStatusInfo>
  );
};

registerPlugin( 'your-plugin-notify', { render: NotifyToggle } );

For useEntityProp to see the value, the meta key must be registered with show_in_rest — and a key beginning with an underscore is protected, so it also needs an auth_callback:

PHP — exposing the meta to both editors

register_post_meta( 'post', '_yp_notify', [
    'type'          => 'boolean',
    'single'        => true,
    'show_in_rest'  => true,
    'auth_callback' => function ( $allowed, $meta_key, $post_id ) {
        return current_user_can( 'edit_post', $post_id );
    },
] );

/ Both

How do you support both editors from one plugin?

Register both paths and let the screen decide, rather than trying to detect the editor and branch. The PHP hook is inert on block-editor screens and the JS plugin is never loaded on classic ones, so the two never collide.

  1. Register the meta once, with show_in_rest and an auth_callback, so both editors read and write the same key.
  2. Keep the PHP hook for the classic editor, the Quick Edit path, and any post type that opted out of the block editor via use_block_editor_for_post_type.
  3. Enqueue the JS on enqueue_block_editor_assets so it only loads where the slot exists.
  4. Do not duplicate the write. The block editor saves meta through the REST API, not through save_post's $_POST — the nonce guard above already makes that a no-op, which is the point of it.

Check before you assume classic is gone. A post type registered with 'show_in_rest' => false still uses the classic editor, as does any site running the Classic Editor plugin. Dropping the PHP hook because "everything is Gutenberg now" removes the control for exactly the installs most likely to depend on it.

/Footnotes
¹ Hook position, @since tags and the WP_Post|null signature of post_submitbox_start verified in wp-admin/includes/meta-boxes.php against WordPress 7.0.2.
² The __back_compat_meta_box skip is in do_meta_boxes() in wp-admin/includes/template.php.
³ Slot reference for the Summary panel: PluginPostStatusInfo.
Protected meta keys and auth_callback behaviour: register_post_meta().
FAQ

Things engineers always ask.

Don't see yours? Open an issue on GitHub or check the full reference in the API docs.

What is post_submitbox_misc_actions? +
A WordPress action that fires inside the classic Publish meta box, immediately after the post date setting. It was added in WordPress 2.9.0 and gained its WP_Post parameter in 4.4.0. It is the standard place to render an extra control alongside Status, Visibility and Publish immediately.
Why does post_submitbox_misc_actions not work in Gutenberg? +
The Publish meta box is registered with the __back_compat_meta_box argument set to true, and do_meta_boxes() skips every box carrying that flag when the screen is the block editor. post_submit_meta_box() is therefore never called and the action never fires. There is no error or deprecation notice.
What is the block editor equivalent of post_submitbox_misc_actions? +
The PluginPostStatusInfo slot from @wordpress/editor, registered with registerPlugin(). It renders into the Summary panel. The post meta it edits must be registered with show_in_rest, plus an auth_callback if the key begins with an underscore.
How do I save a field added with post_submitbox_misc_actions? +
On save_post, after checking wp_is_post_autosave() and wp_is_post_revision(), verifying a nonce, and confirming current_user_can("edit_post"). Check that your nonce field is present in $_POST before treating a missing value as unchecked, otherwise autosaves and REST saves will wipe the meta.
What is the difference between post_submitbox_start and post_submitbox_misc_actions? +
post_submitbox_start fires at the top of the publishing-actions row next to Move to Trash and dates from 2.7.0; post_submitbox_misc_actions fires lower down in the misc section after the date setting. post_submitbox_start can receive null instead of a WP_Post on the Edit Link screen, so callbacks must handle that.
Ready

Your next automation is
one sentence away.

$ wp plugin install flowsystems-webhook-actions --activate