If you're an experienced developer, you may wish to skip to the code.

If you’ve ever done any work with building a plugin or building a feature of a theme that includes a custom meta box or that includes functionality that is fired on the save_post action, then you’ve likely seen WordPress save_post called twice.

The thing is, this is expected behavior, but because of how it’s used, you may not always want your code to execute until the user actually clicks on the save button.

So here’s a quick tip on how to properly manage the case when save_post is called twice.

Understanding Why It Happens

Before you actually begin to programmatically handle when save_post is called, it’s worth understanding what’s going on behind the scenes.

Generally speaking, here are some general tips about save_post:

  • Check out the docblock for the function. In the WordPress source, you’ll notice: “@uses do_action() Calls ‘save_post’ and ‘wp_insert_post’ on post id and post data just before returning.”
  • Posts go through an autosave process whenever it’s created and whenever it’s being drafted. As such, save_post is actually fired multiple times while a user is drafting a post.
  • When the user clicks “Save” or “Publish,” the function fires thus kicking off yet-another-round of the function being called.

Finally, it’s worth noting the the edit_post function fires once, but only when the user has actually edited an existing post.

As such, we’re left with having to resolve the problem of the save functionality being called twice, and not doing so until the user has clicked ‘Save’ or ‘Publish.’

Combat Saving Posts Twice

The resolution is simple: we need to make sure that we check the post_type at the beginning of each time the function is fired. To be complete, we can also check to see if the post is being automatically saved.

To do this, we can leverage two functions that are available the WordPress API:

  1. wp_is_post_revision
  2. wp_is_post_autosave

Both functions accept the current post ID as an argument which allows us to determine if it’s the time to save our custom data or not by making sure that it’s not a post revision and that it’s not an autosave:

if( ! ( wp_is_post_revision( $post_id) || wp_is_post_autosave( $post_id ) ) ) {
   // Save your work
} // end if

Lastly, this should all be done at the beginning of the function.

If I’ve forgotten anything or there’s a better way to manage this, please feel free to share in the comments.

Category:
Tips
Tags:

Join the conversation! 19 Comments

  1. What about the DOING_AUTOSAVE and DOING_AJAX constants? Should work fine too. I’d be interested in pros/cons of this approach, though.

  2. Good stuff! When I first read the post title, I figured this was more of a post about how to use save_post when you have functions inside your hooked function that also call save_post. Things like creating a new post when a post is saved (or a new CPT, for example) – that’s fun stuff.

    But that’s really just a matter of something like remove_action( ‘save_post’, __FUNCTION__ ) — execute code — add_action( ‘save_post’, __FUNCTION__ ).

    Great post man!

  3. Hmm, I’m having a little trouble – when I do something like this on save_post:

    if(wp_is_post_revision($post_id)) {
    //here is an existing post being edited, and this is called twice for some reason
    } else {
    //here is a new post, this is fired once on new post submission
    }

    What if we’re adding new custom fields to an existing post though?

    • If you’re adding custom fields – be it through true custom fields or a meta box – then the above code should still work as it’s all sent as part of the $_POST collection.

      The save_post is called twice because, remember, it can be an autosave, a revision, or a new post. You may be interested in using publish_post, or simply place your code in the else clause of your conditional.

      Beyond that, I’m not sure what else to recommend since I don’t know the specifics of your project.

  4. I love the brevity and clarity of your post. It explained more about the confusing nature of the save_post hook than anything else I could find in the past hour and did it in far fewer words. THanks!

  5. I’m working on a project where I’m trying to validate custom meta fields before new post is being “inserted”. If required data is not provided, I simply can not create the post, so I chose to use “wp_insert_post_empty_content” filter, to return TRUE if posted data is invalid, which skips inserting the post. During this hook, I create “admin notice” and render it upon page refresh. The problem is that I get 2 notices, because “wp_insert_post_empty_content” executes twice just like the “save_post”, only my hook fires up much earlier than save_post. Note that both hooks fire up within the same method – insert post fires up before SQL query is executed, while save post is at the end of the procedure after the post is created.

    I tried your suggestion to check for revision and autosave, but both methods return FALSE when I hit publish, and it still runs twice.

    Any other ideas?

    • Without having more time to actually sit down and work through this, I’m afraid I don’t have a quick answer for you; however, given what you’ve provided here, I’d say to set some type of flag somewhere in the system.

      Then, if the value doesn’t exist add the notice; otherwise, skip adding it (so it effectively only adds it once).

      • Thanks for your response, Tom! Yeah, I don’t see any other, cleaner, ways for handling this. Since there are can be numerous admin notices, it’s probably best to index each notice with a code that references specific validation item and simply not override one if it already exists.

        Thanks again.

  6. Hey Tom, thanks for all of the great blog posts that you write.

    I wonder about the condition inside the if() statement here. Since we are “making sure that it’s not a post revision and that it’s not an autosave”…shouldn’t we have an OR instead of an AND inside the negation part of the if statement?

    By DeMorgan’s Law, we have the condition being true when the post is not an autosave OR not a revision. This would return true, for example, when we have a post that is an autosave but not a revision. I think what we want is “not an autosave AND not a revision”. So the condition in my estimation would look like:

    ! ( wp_is_post_revision( $post_id ) || wp_is_post_autosave( $post_id ) )

    which is equivalent to

    ! wp_is_post_revision( $post_id ) && ! wp_is_post_autosave( $post_id )

    • By DeMorgan’s Law, we have the condition being true when the post is not an autosave OR not a revision.

      You’re right and the post has been updated :).

      This is an old post, too. Nice find and thanks for helping me keep it current!

  7. This post isn’t ancient history yet so I’ll add my 2¢

    I never realized multiple save_posts actions being an issue until I was debugging some critical errors in my plugin, and had to resort to literally checking each single line in succession with output to db to “see” what was happening… I finally realized that save_post was being called 2 or 3 times, and it seemed that it was being called twice during an actual post update.

    While some people will claim that this is an unnecessary code loop or error, and not typical WP function- perhaps, but it depends on the set of plugins you have and what your own plugin does. Let’s not second guess my usage…

    Here is my trick that I hope can help others, and get a few of you questioning how it works… If you’re going to use this you should make sure you aren’t responding to a needless loop introduced by errant code.

    I implemented a static variable and set my code that needed to make absolute sure to only run once in a protected conditional. Because of complex threading and timing issues, I actually needed to bust this out 3 different times during my save_post action… I just increment the static var as the last line of the function et voila!

    function save_post_actions(){

    static $tc = 0;

    if($tc < 1){

    //run once

    }
    //whatever

    if($tc < 1){

    //more run once

    }
    //whatever

    $tc++;

    }

    • Using a static variable to tackle this problem seems like an interesting approach though especially given “complex threading” (I mean, WordPress usually fires in a single-thread); however, I don’t know all of the gory details so I appreciate your sharing this here in the comments!

  8. Thanks for this! I have the unfortunate task of adding custom business rules to a WordPress instance, and I’m frequently faced with issues due to WordPress having such a bizarre spaghetti architecture… kind of reminds me of first year PHP code (“Require this here! No here! Then require this there every request even when it’s not needed, why not! What does inheritance mean? CONSTANTS_R_EZ_2”).

    Okay this is more of a rant than a comment, but base-line : thanks!

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.