wp_insert_post │ action-hook │ WP 2.0.0

Fires after a new post is saved or an existing post is updated.

It fires after the save_post_{$post_type} and save_post actions, but before wp_after_insert_post.

The action does not fire for attachments saved through wp_insert_post().

To handle only a specific post type, use the save_post_{$post_type} action, for example save_post_product.

Calling wp_update_post() or wp_insert_post() inside the callback can recursively trigger this same action.

wp_insert_post() cleans post_content, post_excerpt, and post_content_filtered with wp_filter_post_kses() if the current user does not have the unfiltered_html capability. The title is additionally cleaned with wp_filter_kses().

This matters when running through WP-CLI, WP-Cron, an unauthenticated REST request, or a separate script that includes wp-load.php. In these cases, get_current_user_id() usually returns 0, so potentially unsafe HTML, including <script>, <iframe>, and disallowed attributes, may be removed without warning. The post is still saved successfully, and the function does not return an error.

Before saving, set a user with the unfiltered_html capability, then restore the original user:

$original_user_id = get_current_user_id();

wp_set_current_user( 1 );

try {
	$post_id = wp_insert_post(
		[
			'post_title'   => 'Imported post',
			'post_content' => $html_content,
			'post_status'  => 'publish',
		],
		true
	);
} finally {
	wp_set_current_user( $original_user_id );
}
Multisite

On multisite, only super admins have the unfiltered_html capability. A regular site administrator does not. The DISALLOW_UNFILTERED_HTML constant, when set to true, denies this capability to all users, including administrators and super admins.

Usage

add_action( 'wp_insert_post', 'wp_kama_insert_post_action', 10, 3 );

/**
 * Function for `wp_insert_post` action-hook.
 * 
 * @param int     $post_id Post ID.
 * @param WP_Post $post    Post object.
 * @param bool    $update  Whether this is an existing post being updated.
 *
 * @return void
 */
function wp_kama_insert_post_action( $post_id, $post, $update ){
	// action...
}
$post_id(int)
ID of the saved post.
$post(WP_Post)
Object for the saved post.
$update(bool)
true if an existing post was updated. false if a new post was created.

Examples

#1 Perform an action after saving a post

Save the update time of a regular post in a meta-field. Revisions and autosaves are skipped.

add_action( 'wp_insert_post', 'wp_kama_after_insert_post', 10, 3 );

function wp_kama_after_insert_post( $post_id, $post, $update ) {
	if (
		'post' !== $post->post_type
		|| wp_is_post_revision( $post_id )
		|| wp_is_post_autosave( $post_id )
	) {
		return;
	}

	update_post_meta( $post_id, '_last_processed', time() );
}

#2 Update a post without recursively calling the callback

Temporarily disable the callback before calling wp_update_post().

add_action( 'wp_insert_post', 'wp_kama_update_post_slug', 10, 3 );

function wp_kama_update_post_slug( $post_id, $post, $update ) {
	if ( ! $update || 'product' !== $post->post_type ) {
		return;
	}

	remove_action( 'wp_insert_post', 'wp_kama_update_post_slug', 10 );

	wp_update_post(
		[
			'ID'        => $post_id,
			'post_name' => sanitize_title( $post->post_title ),
		]
	);

	add_action( 'wp_insert_post', 'wp_kama_update_post_slug', 10, 3 );
}

Changelog

Since 2.0.0 Introduced.

Where the hook is called

wp_insert_post()
wp_insert_post
WP_Customize_Manager::trash_changeset_post()
wp_insert_post
wp_publish_post()
wp_insert_post
wp-includes/post.php 5297
do_action( 'wp_insert_post', $post_id, $post, $update );
wp-includes/class-wp-customize-manager.php 3124
do_action( 'wp_insert_post', $post->ID, $post, true );
wp-includes/post.php 5467
do_action( 'wp_insert_post', $post->ID, $post, true );

Where the hook is used in WordPress

Usage not found.