Save & CRUD

Creating, updating, and deleting posts fires more hooks than most developers expect — often more than once per save. These are the hooks that run when content changes, and the traps that catch people assuming save_post only fires once. 4 actions, 0 filters in this category.

save_post

save-crud

Fires after a post is saved or updated

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.

Signature

do_action( 'save_post', int $post_id, WP_Post $post, bool $update );

Example

add_action( 'save_post', function( $post_id, $post ) {
    if ( $post->post_type !== 'book' ) {
        return;
    }
    delete_transient( 'book_list_' . $post_id );
}, 10, 2 );

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
since 1.5.0wp-includes/post.php

wp_insert_post

save-crud

Fires immediately after a post is inserted or updated in the database

wp_insert_post looks like save_post but fires from every wp_insert_post() call, including programmatic inserts that never touch the block editor.

Signature

do_action( 'wp_insert_post', int $post_id, WP_Post $post, bool $update );

Example

add_action( 'wp_insert_post', function( $post_id, $post, $update ) {
    if ( 'book' !== $post->post_type || $update ) {
        return;
    }
    wp_mail( get_option( 'admin_email' ), 'New book added', get_the_title( $post_id ) );
}, 10, 3 );

Common Use Cases

  • Notify on new post creation (not updates)
  • React to programmatic/import inserts
  • Sync newly created posts to an external system
since 1.2.0wp-includes/post.php

transition_post_status

save-crud

Fires whenever a post's status changes

transition_post_status is the only hook that reliably tells you a status changed — including the old value — which save_post cannot do.

Signature

do_action( 'transition_post_status', string $new_status, string $old_status, WP_Post $post );

Example

add_action( 'transition_post_status', function( $new_status, $old_status, $post ) {
    if ( 'publish' === $new_status && 'publish' !== $old_status ) {
        wp_mail( get_option( 'admin_email' ), 'Post published', get_the_title( $post ) );
    }
}, 10, 3 );

Common Use Cases

  • Detect draft-to-publish transitions
  • Log status change history
  • Trigger workflows on unpublish/trash
  • Build scheduled-publish notifications
since 2.3.0wp-includes/post.php

before_delete_post

save-crud

Fires right before a post is permanently deleted

before_delete_post is your last chance to act while the post (and its data) still exists — right after this, wp_delete_post() removes the row for good.

Signature

do_action( 'before_delete_post', int $post_id, WP_Post $post );

Example

add_action( 'before_delete_post', function( $post_id, $post ) {
    if ( 'book' !== $post->post_type ) {
        return;
    }
    global $wpdb;
    $wpdb->delete( $wpdb->prefix . 'book_ratings', [ 'post_id' => $post_id ] );
}, 10, 2 );

Common Use Cases

  • Clean up custom database rows tied to a post
  • Delete associated generated/uploaded files
  • Sync the deletion to an external system
  • Log permanent deletions for an audit trail
since 1.2.0wp-includes/post.php