How to Register a Custom Post Status in WordPress

WordPress ships with built-in statuses: Draft, Pending Review, Published, Scheduled, Private, and Trash. Sometimes you need extra statuses for a custom workflow — for example, “Approved”, “In Review”, or “Archived”. WordPress provides register_post_status() for exactly this.

Problem: How do you register a custom post status in WordPress — for example, "Pending Review" or "Awaiting Approval" — and make it selectable in the admin?

Solution: Use register_post_status() in an init hook, then filter the post status dropdown with JavaScript and make it persist by hooking into save_post.

add_action( 'init', 'register_approved_status' );

function register_approved_status() {
    register_post_status( 'approved', [
        'label'                     => __( 'Approved', 'textdomain' ),
        'public'                    => true,
        'show_in_admin_all_list'    => true,
        'show_in_admin_status_list' => true,
        // translators: number of posts
        'label_count'               => _n_noop( 'Approved <span class="count">(%s)</span>',
                                                'Approved <span class="count">(%s)</span>' ),
    ] );
}

To make the status appear in the post edit screen dropdown, add it via JavaScript on the admin side:

add_action( 'admin_footer-post.php', 'append_approved_status_to_dropdown' );
add_action( 'admin_footer-post-new.php', 'append_approved_status_to_dropdown' );

function append_approved_status_to_dropdown() {
    global $post;
    if ( 'approved' !== $post->post_status ) return;
    ?>
    <script>
        jQuery( document ).ready( function( $ ) {
            $( '<option>' ).val( 'approved' )
                .text( '<?php esc_html_e( "Approved", "textdomain" ); ?>' )
                .appendTo( '#post_status' );
            $( '#post-status-display' ).text( '<?php esc_html_e( "Approved", "textdomain" ); ?>' );
        } );
    </script>
    <?php
}

To query posts with the custom status:

$query = new WP_Query( [
    'post_type'   => 'post',
    'post_status' => 'approved',
] );

NOTE: Custom statuses with public => true are visible on the front end. If you only need the status for internal workflow tracking, set public => false and exclude_from_search => true.