October DealsAmazon USOctober deal check: compare before you payAmazon US: current deals, useful picks and tech finds.Check DealsWindows FixRecommendedWindows errors stealing your time? Find the fix fastScan stability, cleanup and performance issues.Fix NowOctober DealsAmazon USDeal season is back - check today's better picksAmazon US: current deals, useful picks and tech finds.See Picks×
Skip to content
World desk3 min

Why WordPress’s save_post Hook Fires Twice—and How to Prevent an Infinite Loop

A callback that calls wp_update_post() inside save_post can trigger itself again. Learn the unhook-and-restore fix, how revisions affect repeated saves, and when a post-type-specific hook helps.
Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

If your WordPress save_post callback fires twice, first check whether the callback—or something it calls—runs wp_update_post(). That function saves the post and triggers the save hooks again, re-entering the callback. The standard fix is to remove that exact callback before the nested update and restore it afterward. Revisions can also account for a second invocation, so inspect the post ID and revision status rather than assuming every repeat has the same cause.

Why does the WordPress save_post hook fire twice?

The save_post action runs after a post or page is created or updated. Its $update argument indicates whether the post already existed. A callback attached to this action that calls wp_update_post() causes another save, which fires the hook again. If the callback makes the same update on every invocation, it can keep re-entering itself indefinitely. WordPress describes this behavior in its save_post hook reference and wp_update_post() reference.

As an Amazon Associate I earn from qualifying purchases.

A second invocation does not always mean recursion. When revisions are enabled, WordPress warns that save_post may run for a revision and then for the original post. Check the ID passed to the callback, the post type, $update, and whether the ID belongs to a revision. wp_is_post_revision() identifies a revision and returns its parent post ID when applicable.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

How do you prevent an infinite loop when updating a post in save_post?

Before updating, confirm a change is genuinely needed. If it is, remove your callback from the hook, perform the update, then add the callback back. This prevents that callback from re-entering itself during the nested save while leaving other callbacks on the hook alone.

function my_save_post_callback( $post_id, $post, $update ) {
    // Ignore revisions so the callback operates on the parent post.
    if ( wp_is_post_revision( $post_id ) ) {
        return;
    }

    // Determine whether a change is actually needed before updating.
    if ( ! needs_my_update( $post_id ) ) {
        return;
    }

    remove_action( 'save_post', 'my_save_post_callback', 10 );
    wp_update_post(
        array(
            'ID'          => $post_id,
            'post_status' => 'private',
        )
    );
    add_action( 'save_post', 'my_save_post_callback', 10, 3 );
}
add_action( 'save_post', 'my_save_post_callback', 10, 3 );

This illustrates the pattern in the official WordPress guidance; it is not a tested, drop-in implementation. Replace needs_my_update() with an application-specific comparison. The example registers three accepted arguments because the callback uses the post ID, post object, and update status. When removing and restoring it, the callback identity and priority must match the original registration; otherwise remove_action() may fail. See the remove_action() reference and add_action() reference.

Should you use save_post_{post_type} instead?

Use save_post_{post_type} when the callback only applies to one post type. It prevents the callback from running for unrelated types, but it does not prevent recursion: wp_update_post() still triggers the post-type-specific save action for that post. Keep the unhook-and-restore protection if your callback updates the same post. WordPress introduced this specific hook in version 3.7.0; see its reference.

Hook order matters when other callbacks or plugins also update the post. During insertion, save_post_{post_type} runs before generic save_post, followed by wp_insert_post, according to the wp_insert_post() reference.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Debugging checklist for a repeated save_post callback

  1. Log each invocation. Record the post ID, post type, $update, and the result of wp_is_post_revision( $post_id ). The ID may be a revision rather than the original post.
  2. Find the nested save. Search the callback and the functions it calls for wp_update_post() or another operation that saves the same post.
  3. Compare before writing. Check the existing and proposed values, and return if there is no real change to make.
  4. Prevent self-reentry. If the update is necessary, remove the exact callback at its registered priority around the nested update, then restore it.
  5. Scope the hook if appropriate. Use save_post_{post_type} for a callback limited to one type, but retain recursion protection.
  6. Use action counts as a clue, not a diagnosis. did_action( 'save_post' ) reports how many times the action has run. The Plugin Handbook’s hook guidance shows a once-only guard using that count. It can help diagnose or deliberately limit repeated execution, but it does not reveal which nested operation caused it.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

What not to remove

Do not remove every callback on save_post as a routine fix. That can suppress unrelated plugin or theme behavior during the nested save. The documented approach is to unhook only the callback that would re-enter, using the same callback identity and priority with which it was registered.

Rank #4
Teacher Record Book
  • Keep track of everything from attendance to test scores
  • Spiral bound
  • Measures 8-1/2" x 11"

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.

Leave a Reply

Your email address will not be published. Required fields are marked *

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

More from the Wire

  1. World desk4 min
    How to Spot an AI Voice Scam Before Sending MoneyDon’t rely on how a caller sounds. Pause, call back through a known number, and verify the emergency with another trusted person before sending money.
  2. Mountain View desk4 min
    Google’s SynthID Detector: How to Check AI-Generated Images, Video and AudioGoogle’s SynthID Detector looks for an embedded watermark in supported images, video and audio. Here is what its results do—and do not—show.
  3. Redmond desk20 min
    How to create a link to File or Folder in Windows 11Windows 11 gives you several ways to point to a file or folder without moving or duplicating it. You can create a desktop shortcut,…
Recommended PC Tool
Recommended PC Tool
Windows Errors? Fix Them Before They SpreadFree repair scan
Outdated Drivers Are Slowing You DownFree scan - exact matches

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.