Action Hook since 1.5.0
save_post
Fires after a post is saved or updated
Description
save_post runs after WordPress writes the post row. Use it for caches, sync, and derived data — and always guard against autosave, revisions, and infinite loops.
When it runs
After wp_insert_post() commits. Fires for autosaves and revisions unless you skip them.
Signature
do_action( 'save_post', int $post_id, WP_Post $post, bool $update );Parameters
$post_idint — Saved post ID.$postWP_Post — Post object after save.$updatebool — True when updating an existing post.
Examples
Basic
add_action( 'save_post', function( $post_id, $post ) {
if ( $post->post_type !== 'book' ) {
return;
}
delete_transient( 'book_list_' . $post_id );
}, 10, 2 );Flush a per-post transient when a book is saved.
Real case
add_action( 'save_post', function( $post_id, $post, $update ) {
if ( wp_is_post_autosave( $post_id ) || wp_is_post_revision( $post_id ) ) {
return;
}
if ( $update ) {
do_action( 'my_plugin_post_updated', $post_id );
}
}, 10, 3 );Skip autosaves/revisions and fire a custom event only on real updates.
Common Use Cases
- Clear caches after post update
- Send admin email notifications
- Sync data to external APIs
- Auto-generate related records
- Queue heavy sync/recalculation work instead of running it inline on large tables
Common mistakes
- Calling wp_update_post() inside save_post without removing the callback first — infinite loop
- Trusting save_post as proof of a legitimate, authorized edit — it also fires from the REST API, importers, and programmatic wp_insert_post() calls, so sensitive actions still need their own current_user_can()/nonce checks.
Related hooks
FAQ
Why does save_post fire twice?
Once for the autosave/revision and once for the real post. Guard with wp_is_post_autosave() and wp_is_post_revision().
Is it safe to run heavy logic directly inside save_post on a large site?
Not if it's synchronous — save_post runs inside the save request itself, so slow logic on a large database can lock tables and delay the editor's save response. Offload it to a queue or scheduled event (e.g. Action Scheduler or wp_schedule_single_event) instead.
Source: wp-includes/post.php