Wordpress - add_action hook for completely new post?

add_action('new_to_publish', 'your_function');
add_action('draft_to_publish', 'your_function');
add_action('pending_to_publish', 'your_function');

Precisely target creation of new post is actually more tricky than it seems. Technically there are multiple ways post can get created or updated and there are plenty not so obvious things that are technically posts as well (revisions for example).

WordPress provides dynamic hooks that track not only post creation, but what it was and what it became. See Post Status Transitions in Codex.


I have extensively read the WordPresss core and I have tried it all. wp_transition_post_status(), new_to_publish(), new_{post_type}(), wp_insert_post().

All of these are, after all, unreliable.

wp_transition_post_status() isn't reliable because the new status "publish" is the default status both for creating new posts and updating existing ones. Old status isn't reliable to define what's a new post and what's not because it can be draft, auto-draft, publish, etc.

new_to_publish() doesn't work for custom post types.

new_{post_type} only passes $post as parameter, and you can't know if it's new or updating existing one

wp_insert_post() have a $update parameter that should be TRUE if updating existing posts and FALSE if creating new posts, but it's unreliable because it returns TRUE for new posts because of auto-draft.

I ended up doing this:

/**
*   Do something when a new book is created
*/
function new_book($post_id, $post, $update) {
    if ($post->post_type == 'book' && $post->post_status == 'publish' && empty(get_post_meta( $post_id, 'check_if_run_once' ))) {
        # New Post

        # Do something here...

        # And update the meta so it won't run again
        update_post_meta( $post_id, 'check_if_run_once', true );
    }
}
add_action( 'wp_insert_post', 'new_book', 10, 3 );

Optionally, if you need to update your existing posts with the "check_if_run_once" meta, so it don't run the code above for existing posts created before you added that function, you could do:

/**
*   This is a temporary function to update existing posts with the "check_if_run_once" post meta
*   Access your website logged in as admin and add ?debug to the URL.
*/
function temporary_function() {
    if (current_user_can('manage_options')) {
        $posts = get_posts(array(
            'post_type' => 'book'
        ));
        foreach($posts as $post) {
            update_post_meta($post->ID, 'check_if_run_once', true);
        }
    }
}
if (isset($_GET['debug'])) {
    add_action('init', 'temporary_function');
}