Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Register a custom status with register_post_status() on the init hook, then build a separate editor control if users need to select it. Registration alone does not add a status to the WordPress Administration Screen. The example below adds an “Editor review” status for blog posts, saves it safely, and shows how to run code when a post genuinely changes status.

Register the custom status

Add the registration code to a site-specific plugin or your theme’s functions.php. A plugin is usually easier to maintain because the status remains available if you change themes. WordPress documents register_post_status() as a registration function and says, “Do not use before init.” WordPress Developer Resources: register_post_status()

<?php
function acme_register_review_status() {
    register_post_status( 'editor-review', array(
        'label'                     => _x( 'Editor review', 'post status', 'acme' ),
        'label_count'               => _n_noop(
            'Editor review <span class="count">(%s)</span>',
            'Editor reviews <span class="count">(%s)</span>',
            'acme'
        ),
        'public'                    => false,
        'internal'                  => false,
        'protected'                 => true,
        'private'                   => false,
        'exclude_from_search'       => true,
        'show_in_admin_all_list'    => true,
        'show_in_admin_status_list' => true,
    ) );
}
add_action( 'init', 'acme_register_review_status' );

Use a stable, short status key such as editor-review; a user note on the reference page reports a 20-character storage limit for wp_posts.post_status. The status arguments are deliberate: this example makes the status non-public and protected, excludes it from search, and includes it in the WordPress admin’s all-post and status lists. The labels use translation functions so they can be localized.

Choose visibility and admin-list behavior

A registered status can have different effects depending on its flags. Decide how the status should behave before exposing it to editors; registration does not automatically make a non-public post visible on the front end or include it in search.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
  • Visibility: public, internal, protected, and private describe the status’s visibility and intended use. The example sets public to false, protected to true, and internal and private to false.
  • Search: exclude_from_search controls whether posts with this status are excluded from search. The example sets it to true.
  • Admin lists: show_in_admin_all_list and show_in_admin_status_list control whether posts with the status appear in the admin’s all-post list and status list. The example enables both.

These flags are not a substitute for intentionally handling visibility in your templates and queries. If a status is non-public, do not assume a theme or custom query will show its posts to visitors.

Add a selector to the classic editor

Registering a status does not add a control to the editor. WordPress.org’s post-status documentation explicitly warns: “NOTICE: This function does NOT add the registered post status to the Administration Screen.” WordPress.org: Post Status

For the classic editor’s Publish box, use post_submitbox_misc_actions. It runs after the time and date setting in that box and receives the current post object. WordPress Developer Resources: post_submitbox_misc_actions This example limits the field to blog posts and offers only the statuses intended for this workflow:

function acme_render_status_field( $post ) {
    if ( 'post' !== $post->post_type ) {
        return;
    }
    wp_nonce_field( 'acme_status_save', 'acme_status_nonce' );
    $current = get_post_status( $post );
    ?>
    <div class="misc-pub-section">
        <label for="acme_post_status">Editorial status</label>
        <select id="acme_post_status" name="acme_post_status">
            <option value="draft" <?php selected( $current, 'draft' ); ?>>Draft</option>
            <option value="editor-review" <?php selected( $current, 'editor-review' ); ?>>Editor review</option>
            <option value="publish" <?php selected( $current, 'publish' ); ?>>Published</option>
        </select>
    </div>
    <?php
}
add_action( 'post_submitbox_misc_actions', 'acme_render_status_field' );

This is a classic-editor control, not a Gutenberg status selector. If your editors use the block editor, provide an appropriate editor interface rather than assuming this hook adds a control there.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Save the selected status safely

When WordPress saves the post, verify the nonce, reject autosaves and revisions, check the user’s capability, and accept only statuses in an explicit allow-list. Then pass the post ID and selected status to wp_insert_post(). It can insert or update a post and accepts post_status; include the existing post’s ID when updating it. WordPress Developer Resources: wp_insert_post()

A minimal save handler illustrating the required checks is:

function acme_save_editorial_status( $post_id ) {
    if ( ! isset( $_POST['acme_status_nonce'] ) ||
        ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['acme_status_nonce'] ) ), 'acme_status_save' ) ) {
        return;
    }

    if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
        return;
    }

    if ( wp_is_post_revision( $post_id ) ) {
        return;
    }

    if ( ! current_user_can( 'edit_post', $post_id ) ) {
        return;
    }

    if ( 'post' !== get_post_type( $post_id ) || ! isset( $_POST['acme_post_status'] ) ) {
        return;
    }

    $allowed_statuses = array( 'draft', 'editor-review', 'publish' );
    $status = sanitize_key( wp_unslash( $_POST['acme_post_status'] ) );
    if ( ! in_array( $status, $allowed_statuses, true ) ) {
        return;
    }

    wp_insert_post( array(
        'ID'          => $post_id,
        'post_status' => $status,
    ) );
}
add_action( 'save_post', 'acme_save_editorial_status' );

The allow-list is important: a request value should not be treated as an approved status just because it came from your own select element. The nonce, capability, autosave, revision, and post-type checks constrain when the handler acts and who can request the change. If you add other selectable statuses, add them deliberately to both the control and the allow-list.

WordPress also documents that assigning a future status requires a post date. Do not use a future status through this minimal handler without supplying an appropriate date in the update data.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Run code when a post enters the status

For notifications or review-task automation, use the transition_post_status action. It passes the new status, old status, and the post object. WordPress Developer Resources: transition_post_status

function acme_status_transition( $new_status, $old_status, $post ) {
    if ( 'post' !== $post->post_type || $new_status === $old_status ) {
        return;
    }

    if ( 'editor-review' === $new_status ) {
        // Notify the editorial team or enqueue a review task.
    }
}
add_action( 'transition_post_status', 'acme_status_transition', 10, 3 );

The equality check prevents work from running when the status did not actually change. That distinction matters because status/post-type-specific dynamic hooks may also run on later updates where the status remains unchanged. Use the transition hook for work that should happen because a post entered a status, and make the workflow safe to retry if your notification or queueing system can run more than once.

Read or inspect a status in code

Use get_post_status( $post_id_or_object ) to retrieve the current status string; it returns false if it cannot retrieve a status. A custom status introduced by a plugin may be returned. WordPress Developer Resources: get_post_status()

Use get_post_status_object( 'editor-review' ) when you need the registered status object and its settings. It returns null if that status is not registered. WordPress Developer Resources: get_post_status_object()

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Implementation checklist

  • Register the status on init with a stable, short key.
  • Set its visibility, search behavior, and admin-list flags to match the editorial workflow.
  • Add an editor control separately; registration alone does not create one.
  • On save, verify the nonce, autosave and revision state, editing capability, post type, and status allow-list.
  • Update with wp_insert_post() and the post ID.
  • For transition-only side effects, compare old and new status values.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.