I'm saving my CPT in private status. So the transition would be from pending to private. Thing is, when the post was first submitted and is pending there's a date of that submission on the post_date field in db. But when the post got published, the date updated with the current date.

I want to keep the original date of the submission of the post even the post privately published later.

So I did something like below:

function mycpt_keep_pending_date_on_publishing( $new_status, $old_status, $post ) {
    if( 'mycpt' === $post->post_type && 'pending' === $old_status && 'private' === $new_status ) :
        $pending_datetime = get_post_field( 'post_date', $post->ID, 'raw' );

        // Update the post  
        $modified_post = array(  
            'ID'            => $post->ID,  
            'post_date'     => $pending_datetime,
            'post_date_gmt' => get_gmt_from_date( $pending_datetime )

        // Update the post into the database  
        wp_update_post( $modified_post );


add_action( 'transition_post_status', 'mycpt_keep_pending_date_on_publishing' );

But it's not working. What can be the reason?

  • Just FYI, there is {$old_status}_to_{$new_status} hook. If I were to make a wild guess, I would think that the post_date gets updated on publish before it actually gets set to private. – Howdy_McGee Apr 3 '17 at 17:19
  • Tried pending_to_publish and pending_to_private. Tried updating the post from front end with my code and from admin end with core code. No luck. :( – Mayeenul Islam Apr 3 '17 at 17:44

@Howdy_McGee is on the right track in his comment: by the time transition_post_status is fired, the post has already been updated (i.e., written to the db).

What you need to do is hook into wp_insert_post_data, instead of transition_post_status, as follows:

add_filter ('wp_insert_post_data', 'mycpt_keep_pending_date_on_publishing', 10, 2) ;

mycpt_keep_pending_date_on_publishing ($data, $postarr)
    if ($data['post_type'] != 'mycpt') {
        return ($data) ;

    // this check amounts to the same thing as transition_post_status(private, pending)
    if ('private' != $data['post_status'] || 'pending' != $postarr['original_post_status']) {
        return ($data) ;

    $pending_datetime = get_post_field ('post_date', $data['ID'], 'raw') ;

    $data['post_date'] = $pending_datetime ;
    $data['post_date_gmt'] = get_gmt_from_date ($pending_datetime) ;

    return ($data) ;

Note: I used the same function name as you did, but the body of that function is different.

| improve this answer | |
  • I would suggest to hook above filter callback inside the wp_insert_post_data hook, but remove it again during the transitioning hook to make sure it only runs once. There are plenty of example here on this site for this. – kaiser Apr 3 '17 at 20:57
  • @kaiser in case it wasn't clear, I'm suggesting the above code instead of, not in addition to, hooking into transition_post_status. I edited my answer to make that a little clearer. – Paul 'Sparrow Hawk' Biron Apr 3 '17 at 21:13
  • I know. And I suggested to remove the callback right inside the transition hook, so it does not interfere with any later code. There might be other things like plugins, that modify post data as well and one should be sure that you only do your task once. Take a look at this answer for an example. – kaiser Apr 3 '17 at 22:03
  • Added your code, it's working fine if the post is updated from the admin panel. But if it's updated from the front end, even changes like this isn't working. Could you please shed some light there? – Mayeenul Islam Apr 4 '17 at 18:48
  • I just did a quick scan of of your code on github. Adding 'original_post_status' => 'pending' to line 771 of includes/ns-functions.php should do the trick. That is passed to wp_update_post() when a post is updated on the backend and is used in the code in my answer to mimic transition_post_status. – Paul 'Sparrow Hawk' Biron Apr 4 '17 at 20:27

In addition to the other answers, here's a small plugin that makes sure the code is only executed once. In case the data is reset by a plugin running later, try using PHP_INT_MAX -1 as priority (not for publicly distributed plugins). In this case, you will have to set the same value for remove_filter(), else the callback will not get removed.

 * Plugin Name: (WPSE) Static post date
 * Description: Keep the post date as the original date for posts published as private
namespace WPSE;

add_filter( 'wp_insert_post_data', '\WPSE\save', 10, 2 );
function save( $post, $raw ) {
    if ( ! in_array( $post['post_status'], [ 'private', 'pending', ] ) ) {
        return $post;
    if ( 'your_post_type' !== $post['post_type'] ) {
        return $post;

    $date = get_post_field( 'post_date', $post['ID'], 'raw' );
    $post['post_date'] = $date;
    $post['post_date_gmt'] = get_gmt_from_date( $date );

    return $post;

add_action( 'transition_post_status', function() {
    # Make sure above callback is only triggered once
    remove_filter( 'wp_insert_post_data', '\WPSE\save' );
} );

IIRC your main problem with above code is, that the private_to_published filter was deprecated, so there is nothing specific enough aside from a post type status filter. Try the following plugin and see if it works (if your post type really is named mycpt):

/* Plugin Name: (WPSE) Test post type status actions */
add_action( 'private_mycpt', function( $ID, \WP_Post $post ) {
    var_dump( current_filter() );
}, 10, 2 );
| improve this answer | |
  • 1
    now I see what you were talking about. But I don't think your code will make sure that the code hooked into wp_insert_post_data is only executed once because there any number of hooks fired after the post is updated which could trigger another call to wp_insert_post(), and hence, another firing of wp_insert_post_data and you couldn't possibly enumerate them all (to hook into them and remove the func hooked to wp_insert_post_data). – Paul 'Sparrow Hawk' Biron Apr 4 '17 at 0:41
  • @Paul'SparrowHawk'Biron The code gets removed as soon as there is a transition. With the arguments (that I have taken from your answer), we can make quite sure that the modifications the callback triggers are limited to very few cases. Anyway, you are not completely off with your comment: While the callback gets removed and not added more than once, the case in which it gets removed probably is not the right one. It might trigger in some other cases as well. And the callback gets removed in other cases as well. – kaiser Apr 5 '17 at 7:18

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.