October DealsAmazon USOctober deal check: compare before you payAmazon US: current deals, useful picks and tech finds.Check DealsWindows FixRecommendedWindows errors stealing your time? Find the fix fastScan stability, cleanup and performance issues.Fix NowOctober DealsAmazon USDeal season is back - check today's better picksAmazon US: current deals, useful picks and tech finds.See Picks×
Skip to content
custom post status

How to Add a Custom Post Status for Blog Posts in WordPress

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

To add a custom status for blog posts, register it on the init hook, add your own editor control, and save the selected value through wp_insert_post(). Registration alone does not add a status selector to WordPress. A complete implementation also validates the save request and uses transition_post_status for notifications or other workflow actions.

1. Register the status on init

Use a short, stable key such as editor-review. WordPress documents register_post_status() as a registration function and warns: “Do not use before init.”

<?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' );

Put this in a plugin or a site-specific functionality plugin rather than a theme if the status must survive a theme change. The key is stored in the posts table; a user note on the reference documentation reports a 20-character limit for wp_posts.post_status, so keep it comfortably short.

2. Choose visibility and list behavior deliberately

The registration arguments determine how WordPress treats the status. They do not create an editor control.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Setting Value in the example Effect
public false The status is not treated as publicly viewable by default.
internal false It is not reserved solely for WordPress’s internal statuses.
protected true It is treated as a protected workflow state rather than a normal public state.
private false It is not automatically a private-post status.
exclude_from_search true Posts in this status are excluded from ordinary searches unless you intentionally query them.
show_in_admin_all_list true The posts can appear in the administrator’s “All” list.
show_in_admin_status_list true The status can appear among the status filters in the admin list.

Set these flags for the workflow you actually want. A non-public status should not be exposed through templates or custom queries unless your code deliberately handles its visibility.

3. Add a selector to the classic editor

WordPress explicitly notes that registering a status “does NOT add the registered post status to the Administration Screen.” The post_submitbox_misc_actions action runs in the Publish box after the date and time controls and provides the current post object.

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' );

The post-type check keeps this control on blog posts only. Use translation functions for labels in production, and escape any values that come from outside the code.

4. Save the selected status safely

Saving requires more than reading $_POST. Reject autosaves and revisions, verify the nonce, check the user’s capability, allow only the statuses your interface offers, and update the existing post by passing its ID to wp_insert_post().

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
function acme_save_status_field( $post_id ) {
    if ( ! isset( $_POST['acme_status_nonce'] ) ||
        ! wp_verify_nonce( $_POST['acme_status_nonce'], 'acme_status_save' ) ) {
        return;
    }

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

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

    if ( 'post' !== get_post_type( $post_id ) ) {
        return;
    }

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

    if ( ! isset( $_POST['acme_post_status'] ) ) {
        return;
    }

    $allowed = array( 'draft', 'editor-review', 'publish' );
    $status  = sanitize_key( wp_unslash( $_POST['acme_post_status'] ) );

    if ( ! in_array( $status, $allowed, true ) ) {
        return;
    }

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

Passing ID makes this an update instead of a new post. The API accepts post_status; if you use a future status, also provide a post date. The allow-list prevents a forged request from assigning an unintended status.

5. Trigger work when the status really changes

Use transition_post_status for actions such as notifying editors or enqueueing a review task. The hook supplies the new status, old status, and WP_Post object.

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 matters: status-related update hooks can also run when a post is saved without changing status. Without it, a notification could be sent repeatedly.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

6. Read or inspect the status in other code

Read the current value

$status = get_post_status( $post_id );

get_post_status() returns the current status string, or false on failure. It can return a custom status introduced by a plugin.

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.

Inspect the registered definition

$status_object = get_post_status_object( 'editor-review' );

This returns the registered status object, or null when that key has not been registered.

7. Production checklist

  • Register the status on init with a stable, short key.
  • Decide separately whether it is public, protected, searchable, and visible in administrator lists.
  • Add an explicit editor control; registration does not add one.
  • Limit the control to the post post type when appropriate.
  • Use translated labels and escape output.
  • Verify nonce and capability, and reject autosaves and revisions before updating.
  • Validate the submitted value against an allow-list.
  • Pass the existing post ID to wp_insert_post().
  • Compare old and new statuses before running transition side effects.
  • Keep non-public statuses out of front-end output unless your queries and templates intentionally support them.

Frequently Asked Questions

Why does my custom status not appear in the editor?

register_post_status() only registers the status. Add an editor field, such as the select rendered with post_submitbox_misc_actions, and save its value explicitly.

How can I run code only when a post enters the custom status?

Hook transition_post_status, restrict the post type and target status, and return when $new_status === $old_status.

How do I verify that a status is registered?

Call get_post_status_object( 'editor-review' ); it returns the status object or null.

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

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.

Leave a Reply

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

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

Read next

Recommended PC Tool
Recommended PC Tool
PC Slower Than It Used to Be?Free scan - under a minute
Outdated Drivers Are Slowing You DownFree scan - exact matches

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.