wp_update_post()WP 1.0.0

Updates a post with new post data in WordPress Database. To work as expected, it is necessary to pass the ID of the post to be updated.

The function expects escaped data. That is, you cannot use wp_unslash() for received $_POST data. If you do, there will be double slash escaping! Conversely, if we pass normal data (not escaped) it should be escaped with wp_slash().

This function is a wrapper for wp_insert_post().

The difference between this function and wp_insert_post() is that there is no need to pass all the data, you can specify only those that need to be updated, the rest data will be copied from the database.

If post revisions are enabled the update does not delete previous data but instead, it becomes a revision. At the same time, all relations (with custom fields, categories, tags) will go to the main/new post.

The date does not have to be set for drafts. You can set the date and it will not be overridden.


Categories need to be passed as an array of integers (IDs of categories to which the post will be attached). This is the case even if only one category is assigned to the post: array(1).

Infinite loop

If you need to use wp_update_post() inside the save_post hook, make sure that post_type is not revision. The point is, when wp_update_post() is used inside a save_post hook (for example, for updating custom metabox), an infinite loop is usually occurs. This is because save_post is called twice if revisions are enabled: the first time when creating the revision, and the second time when updating the original post. As a result, we get a self-destructive loop and creation of an infinite number of revisions.

The same things happen when wp_update_post() uses inside the edit_attachment hook. When ID parameter refers to the attachment.

Here is an example showing what to do in the situation when wp_update_post() causes an infinite loop inside save_post hook:

add_action( 'save_post', 'my_function' );
function my_function( $post_id ){
	if ( ! wp_is_post_revision( $post_id ) ){
		// remove this hook so that it does not create an infinite loop
		remove_action( 'save_post', __FUNCTION__ );

		// update the post when the save_post hook is called again
		wp_update_post( $my_args );

		// return hook back
		add_action( 'save_post', __FUNCTION__ );
Future post-publication

If you plan to publish a draft in the future and use the wp_update_post() function to do this, the function will not work unless you specify edit_date = true. So, in other words, WordPress will ignore the post_date when updating drafts unless edit_date is true.

No Hooks.



  • Post ID - when the post has been inserted.
  • 0 - when there is an inserting error and $wp_error parameter is disabled.
  • WP_Error object - when there is an inserting error and the $wp_error parameter is enabled.


wp_update_post( $postarr, $wp_error );

Post data as an array. The array keys are identical to the wp_posts table fields in the WordPress Database.

For a list of available array keys, see the description of wp_insert_post().

$postarr['ID'] is required.

Arrays are expected to be escaped, objects are not.

Default: current post data

Allow return WP_Error on failure.
Default: false


Before using the function, you need to collect an array of data, which then passed as a parameter. The array must contain the data that we want to update.


#1 Update the content of the post 37

// Create an array of data
$my_post = array();
$my_post['ID'] = 37;
$my_post['post_content'] = 'Here\'s the new content';

// Update data in the database
wp_update_post( wp_slash($my_post) );

Fields that can be changed

This is an array of fields of any post in WordPress.

WP_Post Object(
	[ID]                     => 1
	[post_author]            => 1
	[post_date]              => 2010-03-26 09:27:40
	[post_date_gmt]          => 2010-03-26 05:27:40
	[post_content]           => My perfect content...
	[post_title]             => The title of the post
	[post_excerpt]           =>
	[post_status]            => publish
	[comment_status]         => open
	[ping_status]            => open
	[post_password]          =>
	[post_name]              => post_name
	[to_ping]                =>
	[pinged]                 => http://example.com/dopolnitelnyie-knopki
	[post_modified]          => 2014-02-10 10:31:17
	[post_modified_gmt]      => 2014-02-10 06:31:17
	[post_content_filtered]  =>
	[post_parent]            => 0
	[guid]                   => http://example.com/post_name
	[menu_order]             => 0
	[post_type]              => post
	[post_mime_type]         =>
	[comment_count]          => 41
	[filter]                 => raw

#2 Update the meta-fields at post 37

In this function as well as in wp_insert_post() you can specify an array of meta-fields to be added or updated.

// Create a data array
$my_post = [
	'ID' => 37,
	'meta_input' => [
		'meta_key_1' => 'Meta value 1',
		'meta_key_2' => 'Meta value 2',

// Update
wp_update_post( wp_slash($my_post) );


Since 1.0.0 Introduced.
Since 3.5.0 Added the $wp_error parameter to allow a WP_Error to be returned on failure.
Since 5.6.0 Added the $fire_after_hooks parameter.

wp_update_post() code WP 6.4.3

function wp_update_post( $postarr = array(), $wp_error = false, $fire_after_hooks = true ) {
	if ( is_object( $postarr ) ) {
		// Non-escaped post was passed.
		$postarr = get_object_vars( $postarr );
		$postarr = wp_slash( $postarr );

	// First, get all of the original fields.
	$post = get_post( $postarr['ID'], ARRAY_A );

	if ( is_null( $post ) ) {
		if ( $wp_error ) {
			return new WP_Error( 'invalid_post', __( 'Invalid post ID.' ) );
		return 0;

	// Escape data pulled from DB.
	$post = wp_slash( $post );

	// Passed post category list overwrites existing category list if not empty.
	if ( isset( $postarr['post_category'] ) && is_array( $postarr['post_category'] )
		&& count( $postarr['post_category'] ) > 0
	) {
		$post_cats = $postarr['post_category'];
	} else {
		$post_cats = $post['post_category'];

	// Drafts shouldn't be assigned a date unless explicitly done so by the user.
	if ( isset( $post['post_status'] )
		&& in_array( $post['post_status'], array( 'draft', 'pending', 'auto-draft' ), true )
		&& empty( $postarr['edit_date'] ) && ( '0000-00-00 00:00:00' === $post['post_date_gmt'] )
	) {
		$clear_date = true;
	} else {
		$clear_date = false;

	// Merge old and new fields with new fields overwriting old ones.
	$postarr                  = array_merge( $post, $postarr );
	$postarr['post_category'] = $post_cats;
	if ( $clear_date ) {
		$postarr['post_date']     = current_time( 'mysql' );
		$postarr['post_date_gmt'] = '';

	if ( 'attachment' === $postarr['post_type'] ) {
		return wp_insert_attachment( $postarr, false, 0, $wp_error );

	// Discard 'tags_input' parameter if it's the same as existing post tags.
	if ( isset( $postarr['tags_input'] ) && is_object_in_taxonomy( $postarr['post_type'], 'post_tag' ) ) {
		$tags      = get_the_terms( $postarr['ID'], 'post_tag' );
		$tag_names = array();

		if ( $tags && ! is_wp_error( $tags ) ) {
			$tag_names = wp_list_pluck( $tags, 'name' );

		if ( $postarr['tags_input'] === $tag_names ) {
			unset( $postarr['tags_input'] );

	return wp_insert_post( $postarr, $wp_error, $fire_after_hooks );