Is it possible within the save_post action to determine whether it's a new post being created or an existing post being update?

  • I don't think this is possible. See my comment below @moraleida's answer. Why do you need to know if it's a new post or being updated? There may be a work-around or an alternative approach. Commented Apr 12, 2012 at 18:28

11 Answers 11


Since WordPress version 3.7. - IIRC - the save_post hook - more information about the hook and its usage at Code Reference: save_post and Codex: save_post - has a third parameter $update which can be used to determine just that.

@param     int               $post_ID     Post ID.
@param     WP_Post     $post          Post object.
@param     bool            $update     Whether this is an existing post being updated or not.


$update is not always true – you can see and test it yourself with below code. It is not well documented though, possibly far from optimally named, and hence creates misleading expectations. Below code can be used for some debugging, play around with when to intercept code execution, because otherwise you won't see the information/messages. I think, the culprit in deceptive behavior is the handling of revisions and auto saves – which could be disabled, but I don't recommend it, and haven't tested it. Not sure if this warrants a Trac Ticket, so I didn't open one, if you think so, please follow the link and do it yourself. Aside from that, as stated in the comments, if you have a specific problem, post a new question.

add_action( 'save_post', 'debug_save_post_update', 10, 3 );
function debug_save_post_update( $ID, $post, $update ) {

  echo '<pre>';
  print_r( $post ); echo '<br>';
  echo '$update == ';
  echo $update ? 'true' : 'false';

  if( ! $update && $post->post_status == "auto-draft" ) {
    // applies to new post
    echo ' && $post->post_status == "auto-draft"';
  } else if ( ! $update ) {
    // applies basically to the (auto saved) revision 
  } else {
    // applies to updating a published post
    // when there is a revision, which is normally the case, 
    // standard behavior of WordPress, then it is considered 
    // an update, which is where the confusion sets in
    // there are other methods, like checking time or post status
    // depending on your use case it might be more appropriate 
    // to use one of those alternatives 

  echo '</pre>';
  • 8
    The $update parameter is ALWAYS true even when it is a new post. So this parameter is useless. Not sure if it ever worked at all, but it sure as hell isn't working the way it's documented in the latest version of wordpress 4.8. Commented Jul 20, 2017 at 20:32
  • @SolomonClosson If you take a look at wp_publish_post, then yes. But that isn't true for its usage in wp_insert_post. I've written a debug function, I add it to the answer. Commented Jul 21, 2017 at 0:11
  • @SolomonClosson If you have a actual concrete problem, please ask a new question. Take a look at the revisions for the debug function an explanation. Commented Jul 21, 2017 at 0:34
  • 1
    The save_post hook has a 3rd parameter that is always set to TRUE, so not sure what this has to do with other hooks, not speaking about other hooks. I'm talking about the hook in your answer. This is incorrect. Commented Jul 23, 2017 at 23:42
  • 1
    Guys, just an info. The hook is fired on restoring and deleting a post.
    – melvin
    Commented May 9, 2019 at 11:39

The way I perform this check (within a hooked function) is to compare the post date and modified date (in GMT for standardisation)

function check_new_vs_update( $post_id ){
    $myPost        = get_post($post_id);
    $post_created  = new DateTime( $myPost->post_date_gmt );
    $post_modified = new DateTime( $myPost->post_modified_gmt );
    $diff          = $created->diff( $modified );
    $seconds_difference = ((($diff->y * 365.25 + $diff->m * 30 + $diff->d) * 24 + $diff->h) * 60 + $diff->i)*60 + $diff->s;

    if( $seconds_difference <= 1 ){
        // New post
        // Updated post
add_action('save_post', 'check_new_vs_update' );

This works because even at creation the post has a 'modified' date attached to it, which is exactly the same as the 'created' date, but we allow a variance of 1 second either way in case a second ticks over during the creation of the post.

  • 2
    Sometimes the post_date_gmt is 2019-03-12 01:31:30 and the post_modified_gmt is 2019-03-12 01:31:31. :( Commented Mar 12, 2019 at 4:06
  • 2
    @HeYifei何一非 good point, if the processing begins at the end of a given second, this could happen. I've updated my answer, thanks Commented Mar 12, 2019 at 13:04
  • 3
    Guys, just an info. The hook is fired on restoring and deleting a post.
    – melvin
    Commented May 9, 2019 at 11:39
  • Good to know @melvin, thanks Commented Aug 17, 2020 at 17:43
  • I can't believe that it as to be so complicated. Wordpress never cease to amaze me. Commented Jan 4, 2021 at 13:29

I ended up just checking for the existence of a custom value prior to setting it. That way, if it's a newly created post the custom value would not yet exist.

function attributes_save_postdata($post_id) {
  if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) return;
  if (!wp_verify_nonce($_POST['_attributes_noncename'], plugin_basename(__FILE__))) return;
  if ('page' == $_POST['post_type']) {
    if (!current_user_can('edit_page', $post_id)) return;
  } else {
    if (!current_user_can('edit_post', $post_id)) return;
  $termid = get_post_meta($post_id, '_termid', true);
  if ($termid != '') {
    // it's a new record
    $termid = 'update';
  } else {
    // it's an existing record
  update_post_meta($post_id, '_termid', $termid);
add_action('save_post', 'attributes_save_postdata');
  • For this to work, do you have to first create the custom field using add_post_meta?
    – MF1
    Commented Apr 9, 2013 at 2:03
  • Per the Codex: [update_post_meta] may be used in place of add_post_meta() function. codex.wordpress.org/Function_Reference/update_post_meta Commented Jan 20, 2014 at 21:46
  • This might fail, if posts were created before the code hook gets enabled via a plugin activation. Older posts dont have the meta set, hence the first update for them will be considered as new. Commented May 5, 2018 at 19:13
  • @VasuChawla so in that case maybe its more reliable to check the existence isset() (for example) of that specific meta term instead of whether its true or false. As a third condition. (if, elseif, ...else) Commented Aug 1, 2020 at 17:03

Example to ialocin answer with "update" paremeter:

function save_func($ID, $post,$update) {

   if($update == false) {
     // do something if its first time publish
   } else {
     // Do something if its update

add_action( 'save_post', 'save_func', 10, 3 );
  • 2
    A better way to structure this would be either putting the update block first, allowing to just do if($update) or keeping the new block first but using if( ! $update ). The latter will get OP into better practice and is preferred over your method by WordPress coding standards in cases like the ternary operator Commented Nov 17, 2016 at 11:38

I have just encountered the save_post about new and update. After reading the source code to understand the flow. I found that the following method might be useful. Since it is not yet mentioned before. See if it is useful to anyone. (The test is Core 5.3.3)

The Post creation flow is approximately:

  1. After pressing Add New (Post)
  2. $post = get_default_post_to_edit( $post_type, true ); will be called where
  3. get_default_post_to_edit() receive argument $create_in_db = true
  4. so wp_insert_post() is immediately being called, auto-draft post is being created, even it is not saved, every time clicking Add New, an auto-draft is created
  5. $update is always true for Publish. So when Publish a new post, it is true.

By comparing the $_POST object for new post and update post or republish post, the prominent difference is value _wp_http_referer, the new post is /wp-admin/post-new.php

Assumption: assumed the post is published/added from UI. If it is done by other mechanism, custom code, the checking is needed to adjust.

add_action( 'save_post', 'test_save_post_check', 0, 3 );
function test_save_post_check( $post_ID, $post, $update ) {
    // other tests + this
    // checking the 'post-new' position from the '_wp_http_referer'
    if( strpos( wp_get_raw_referer(), 'post-new' ) > 0 ) {
        // new
    } else {
        // update

here is a functional code that worked and tested by me i used in my website also it solve the two following problem associated with save_post action :

  1. problem to check between ubdate or insert

  2. problem of twice insert by save_post action

    function save_annonces_callback($post_ID, $post, $update){
            $post_type = get_post_type($post_ID);
            if ( $post_type === 'annonces' ){
                //this to preventtwice insert by save_post action :)
                if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE ) {
                } else {
                    //check if new post so insert
                    if( strpos( wp_get_raw_referer(), 'post-new' ) > 0 ){
                        //perform insert
                        //perform update
        }add_action('save_post','save_annonces_callback', 10, 3);

You can use pre_post_update action hook for the update code and save_post for the new post code. It works before a post is updated.

  • 5
    save_post hook is fired both when a post is created and updated (after WordPress has saved it to the database). pre_post_update is fired when a post is updated, but prior to the post being updated - this can be important. Commented Aug 12, 2012 at 18:59

As Darshan Thanki hinted (and Stephen Harris further elaborated), you can use pre_post_update to your advantage.

global $___new_post;
$___new_post = true;

  function() {
    global $___new_post;
    $___new_post = false;

function is_new_post() {
  global $___new_post;
  return $___new_post;

The reason why I used globals is because function is_new_post() use ( &$new_post ) is not valid in PHP (shocking...) so pulling in that variable into the function scope doesn't work -- hence the global.

Note that this can really only reliably be used within/after the save_post event (which is usually sufficient, at least for what we're doing with it).


When save_post is triggered, all information about that post is already available, so in theory you could use

function f4553265_check_post() {

    if (!get_posts($post_id)) {
    // if this is a new post get_posts($post_id) should return null
    } else {
    // $post_id already exists on the database

this is untested, though. =)

  • 3
    By the time you get to save_post the post itself would have already been saved to database - so get_posts would return the current post. Commented Apr 12, 2012 at 18:25
  • True, just checked it in the Codex. Thanks for the heads-up.
    – moraleida
    Commented Apr 12, 2012 at 18:59
  • This does not work with get_posts but it works with get_post.
    – Gavin
    Commented Aug 18, 2020 at 2:28

Another approach that uses a built-in function and no addition to the database would involve get_post_status().

$post_status = get_post_status();
if ( $post_status != 'draft' ) {
} else { 
    //not a draft: can be published, pending, etc. 

Note however that it might not be appropriate if you plan to later set the status back to "draft" – your instructions would be repeated the next time you will update the post. Depending on the context, you might want to consider the various strings that can be returned by get_post_status() to build a more appropriate scenario.

See Codex for get_post_status() and Post Status

Possible values are:

  • 'publish' - A published post or page
  • 'pending' - post is pending review
  • 'draft' - a post in draft status
  • 'auto-draft' - a newly created post, with no content
  • 'future' - a post to publish in the future
  • 'private' - not visible to users who are not logged in
  • 'inherit' - a revision. see get_children.
  • 'trash' - post is in trashbin. added with Version 2.9.
  • I don't think this does what was asked for. If I create a new post and then hit 'Publish', save_post() is executed for the first time, but during that execution get_post_status() already returns 'publish' and not 'draft', even though it is only in the process of being published.
    – cgogolin
    Commented Nov 25, 2017 at 18:39

Since the $update param is useless, this is the fastest way I tested:

function wpse48678_check_is_post_new($post_id, $post, $update)
    if (false !== strpos($_POST['_wp_http_referer'], 'post-new.php')) {
        return true; // Or do something else.
    } else {
        return false; // Or do something else.
add_action('save_post_{$post_type}', 'wpse48678_check_is_post_new', 10, 3);

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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