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.
#1 Best Overall
| 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().
Do these 3 things before closing this tab:
1Fix the driver behind crashes, sound loss and screen glitches2Clear out junk files and repair common Windows errors3Scan for outdated or missing drivers - takes under a minuteRank #3
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.
Rank #4
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.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.
Best Value
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
initwith 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
postpost 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.
Recommended Free Tools
Quick Recap
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.




