wppaste
WordPress

wp_insert_post( array $postarr, bool $wp_error = false, bool $fire_after_hooks = true ): int|WP_Error

Since
1.0.0, 2.6.0, 4.2.0, 4.4.0, 5.6.0
Source
wp-includes/post.php:4506

Create or update a post programmatically with wp_insert_post(), passing fields like post_title, post_status, post_type, meta_input, and tax_input. It returns the new post ID on success and 0 on failure, or a WP_Error when $wp_error is true. Supplying an ID key updates that post instead of inserting a new one; new posts default to a draft of type post.

Inserts or update a post.

Description

If the $postarr parameter has 'ID' set to a value, then post will be updated. You can set the post date manually, by setting the values for 'post_date' and 'post_date_gmt' keys. You can close the comments or open the comments by setting the value for 'comment_status' key.

Parameters

$postarrarray
An array of elements that make up a post to update or insert.
  • $IDintdefault: 0

    The post ID. If equal to something other than 0, the post with that ID will be updated.
  • $post_authorintdefault: is the current user ID

    The ID of the user who added the post.
  • $post_datestringdefault: is the current time

    The date of the post.
  • $post_date_gmtstringdefault: is the value of $post_date

    The date of the post in the GMT timezone.
  • $post_contentstringdefault: empty

    The post content.
  • $post_content_filteredstringdefault: empty

    The filtered post content.
  • $post_titlestringdefault: empty

    The post title.
  • $post_excerptstringdefault: empty

    The post excerpt.
  • $post_statusstringdefault: 'draft'

    The post status.
  • $post_typestringdefault: 'post'

    The post type.
  • $comment_statusstringdefault: is the value of 'default_comment_status' option

    Whether the post can accept comments. Accepts 'open' or 'closed'.
  • $ping_statusstringdefault: is the value of 'default_ping_status' option

    Whether the post can accept pings. Accepts 'open' or 'closed'.
  • $post_passwordstringdefault: empty

    The password to access the post.
  • $post_namestringdefault: is the sanitized post title when creating a new post

    The post name.
  • $to_pingstringdefault: empty

    Space or carriage return-separated list of URLs to ping.
  • $pingedstringdefault: empty

    Space or carriage return-separated list of URLs that have been pinged.
  • $post_parentintdefault: 0

    Set this for the post it belongs to, if any.
  • $menu_orderintdefault: 0

    The order the post should be displayed in.
  • $post_mime_typestringdefault: empty

    The mime type of the post.
  • $guidstringdefault: empty

    Global Unique ID for referencing the post.
  • $import_idintdefault: 0

    The post ID to be used when inserting a new post. If specified, must not match any existing post ID.
  • $post_categoryint[]

    Array of category IDs. Defaults to value of the 'default_category' option.
  • $tags_inputarraydefault: empty

    Array of tag names, slugs, or IDs.
  • $tax_inputarraydefault: empty

    An array of taxonomy terms keyed by their taxonomy name. If the taxonomy is hierarchical, the term list needs to be either an array of term IDs or a comma-separated string of IDs. If the taxonomy is non-hierarchical, the term list can be an array that contains term names or slugs, or a comma-separated string of names or slugs. This is because, in hierarchical taxonomy, child terms can have the same names with different parent terms, so the only way to connect them is using ID.
  • $meta_inputarraydefault: empty

    Array of post meta values keyed by their post meta key.
  • $page_templatestring

    Page template to use.
$wp_errorbooloptional
Whether to return a WP_Error on failure. Default false.Default: false
$fire_after_hooksbooloptional
Whether to fire the after insert hooks. Default true.Default: true

Return

int|WP_Error
The post ID on success. The value 0 or WP_Error on failure.

Examples

Every example is editable and runs in a real WordPress booted in your browser by WordPress Playground. Press Run, then edit the code: clicking away re-runs it. Nothing is sent anywhere until you do.

Create a draft post with meta and tags

meta_input and tags_input save related data in the same call, so no follow-up writes are needed.

$post_id = wp_insert_post( array(
	'post_title'   => 'Imported release notes',
	'post_content' => 'What changed in this release.',
	'post_status'  => 'draft',
	'post_author'  => 1,
	'tags_input'   => array( 'featured' ),
	'meta_input'   => array( 'price' => '0.00' ),
), true );

if ( is_wp_error( $post_id ) ) {
	echo 'Failed: ', $post_id->get_error_message();
} else {
	echo "created post {$post_id}\n";
	echo 'status:     ', get_post_status( $post_id ), "\n";
	echo 'price meta: ', get_post_meta( $post_id, 'price', true ), "\n";
	echo 'tags:       ', implode( ', ', wp_get_post_tags( $post_id, array( 'fields' => 'names' ) ) );
}

Pass true as the second argument or failures come back as 0 instead of a WP_Error.

Publish a page once, without creating duplicates

wp_insert_post() always inserts, so an importer that runs twice creates two posts unless it checks first.

$slug     = 'pricing';
$existing = get_page_by_path( $slug );

if ( $existing ) {
	echo "already exists as page {$existing->ID}, nothing to do";
} else {
	$id = wp_insert_post( array(
		'post_title'  => 'Pricing',
		'post_name'   => $slug,
		'post_type'   => 'page',
		'post_status' => 'publish',
		'post_parent' => 6,
		'post_author' => 1,
	), true );

	echo is_wp_error( $id ) ? $id->get_error_message() : "created page {$id} under page 6";
}

Press Run twice: the second run takes the guard branch.

Hooks fired · 16

16 hooks fire while wp_insert_post() runs, in this order:

  1. apply_filters( wp_insert_post_empty_content )filterline 4603 (+97 into the body)

    Filters whether the post should be considered "empty".

  2. apply_filters( wp_insert_post_parent )filterline 4776 (+270 into the body)

    Filters the post parent -- used to check for and prevent hierarchy loops.

  3. apply_filters( add_trashed_suffix_to_trashed_posts )filterline 4802 (+296 into the body)

    Filters whether or not to add a `__trashed` suffix to trashed posts that match the name of the updated post.

  4. apply_filters( wp_insert_attachment_data )filterline 4871 (+365 into the body)

    Filters attachment post data before it is updated in or added to the database.

  5. apply_filters( wp_insert_post_data )filterline 4886 (+380 into the body)

    Filters slashed post data just before it is inserted into the database.

  6. do_action( pre_post_update )actionline 4901 (+395 into the body)

    Fires immediately before an existing post is updated in the database.

  7. do_action( pre_post_insert )actionline 4933 (+427 into the body)

    Fires immediately before a new post is inserted in the database.

  8. do_action( edit_attachment )actionline 5094 (+588 into the body)

    Fires once an existing attachment has been updated.

  9. do_action( attachment_updated )actionline 5107 (+601 into the body)

    Fires once an existing attachment has been updated.

  10. do_action( add_attachment )actionline 5117 (+611 into the body)

    Fires once an attachment has been added.

  11. do_action( edit_post_{$post->post_type} )actionline 5140 (+634 into the body)

    Fires once an existing post has been updated.

  12. do_action( edit_post )actionline 5150 (+644 into the body)

    Fires once an existing post has been updated.

  13. do_action( post_updated )actionline 5163 (+657 into the body)

    Fires once an existing post has been updated.

  14. do_action( save_post_{$post->post_type} )actionline 5183 (+677 into the body)

    Fires once a post has been saved.

  15. do_action( save_post )actionline 5194 (+688 into the body)

    Fires once a post has been saved.

  16. do_action( wp_insert_post )actionline 5205 (+699 into the body)

    Fires once a post has been saved.

Uses · 48

Show all 48

Used by · 21

Show all 21

Source

function wp_insert_post( $postarr, $wp_error = false, $fire_after_hooks = true ) {	global $wpdb; 	// Capture original pre-sanitized array for passing into filters.	$unsanitized_postarr = $postarr; 	$user_id = get_current_user_id(); 	$defaults = array(		'post_author'           => $user_id,		'post_content'          => '',		'post_content_filtered' => '',		'post_title'            => '',		'post_excerpt'          => '',		'post_status'           => 'draft',		'post_type'             => 'post',		'comment_status'        => '',		'ping_status'           => '',		'post_password'         => '',		'to_ping'               => '',		'pinged'                => '',		'post_parent'           => 0,		'menu_order'            => 0,		'guid'                  => '',		'import_id'             => 0,		'context'               => '',		'post_date'             => '',		'post_date_gmt'         => '',	); 	$postarr = wp_parse_args( $postarr, $defaults ); 	unset( $postarr['filter'] ); 	$postarr = sanitize_post( $postarr, 'db' ); 	// Are we updating or creating?	$post_id = 0;	$update  = false;	$guid    = $postarr['guid']; 	if ( ! empty( $postarr['ID'] ) ) {		$update = true; 		// Get the post ID and GUID.		$post_id     = $postarr['ID'];		$post_before = get_post( $post_id ); 		if ( is_null( $post_before ) ) {			if ( $wp_error ) {				return new WP_Error( 'invalid_post', __( 'Invalid post ID.' ) );			}			return 0;		} 		$guid            = get_post_field( 'guid', $post_id );		$previous_status = get_post_field( 'post_status', $post_id );	} else {		$previous_status = 'new';		$post_before     = null;	} 	$post_type = empty( $postarr['post_type'] ) ? 'post' : $postarr['post_type']; 	$post_title   = $postarr['post_title'];	$post_content = $postarr['post_content'];	$post_excerpt = $postarr['post_excerpt']; 	if ( isset( $postarr['post_name'] ) ) {		$post_name = $postarr['post_name'];	} elseif ( $update ) {		// For an update, don't modify the post_name if it wasn't supplied as an argument.		$post_name = $post_before->post_name;	} 	$maybe_empty = 'attachment' !== $post_type		&& ! $post_content && ! $post_title && ! $post_excerpt		&& post_type_supports( $post_type, 'editor' )		&& post_type_supports( $post_type, 'title' )		&& post_type_supports( $post_type, 'excerpt' );

History

Introduced in 1.0.0. Unchanged from 6.7.7 through 7.1.0.

  1. 6.7.7
  2. 6.8.8
  3. 6.9.7
  4. 7.0.4
  5. 7.1.0

Signature, return type and hooks compared across 5 parsed releases.

5.6.0
Added the $fire_after_hooks parameter.from the docblock
4.4.0
A 'meta_input' array can now be passed to $postarr to add post meta data.from the docblock
4.2.0
Support was added for encoding emoji in the post title, content, and excerpt.from the docblock
2.6.0
Added the $wp_error parameter to allow a WP_Error to be returned on failure.from the docblock
1.0.0
Introduced.from the docblock

About this page

Parsed data
Generated from the wordpress-develop 6.9.7 tag, from src/wp-includes/post.php, and regenerated for each WordPress release so it tracks the code rather than a snapshot of it.
Corrections
Something wrong on this page? Report it and it gets fixed in the next regeneration.