wp-includes/post.php:4506Create 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.
$postarrarray$IDintdefault: 0
$post_authorintdefault: is the current user ID
$post_datestringdefault: is the current time
$post_date_gmtstringdefault: is the value of $post_date
$post_contentstringdefault: empty
$post_content_filteredstringdefault: empty
$post_titlestringdefault: empty
$post_excerptstringdefault: empty
$post_statusstringdefault: 'draft'
$post_typestringdefault: 'post'
$comment_statusstringdefault: is the value of 'default_comment_status' option
$ping_statusstringdefault: is the value of 'default_ping_status' option
$post_passwordstringdefault: empty
$post_namestringdefault: is the sanitized post title when creating a new post
$to_pingstringdefault: empty
$pingedstringdefault: empty
$post_parentintdefault: 0
$menu_orderintdefault: 0
$post_mime_typestringdefault: empty
$guidstringdefault: empty
$import_idintdefault: 0
$post_categoryint[]
$tags_inputarraydefault: empty
$tax_inputarraydefault: empty
$meta_inputarraydefault: empty
$page_templatestring
$wp_errorbooloptionalfalse$fire_after_hooksbooloptionaltrueint|WP_ErrorEvery 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.
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.
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.
16 hooks fire while wp_insert_post() runs, in this order:
Filters whether the post should be considered "empty".
Filters the post parent -- used to check for and prevent hierarchy loops.
Filters whether or not to add a `__trashed` suffix to trashed posts that match the name of the updated post.
Filters attachment post data before it is updated in or added to the database.
Filters slashed post data just before it is inserted into the database.
Fires immediately before an existing post is updated in the database.
Fires immediately before a new post is inserted in the database.
Fires once an existing attachment has been updated.
Fires once an existing attachment has been updated.
Fires once an attachment has been added.
Fires once an existing post has been updated.
Fires once an existing post has been updated.
Fires once an existing post has been updated.
Fires once a post has been saved.
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' );Introduced in 1.0.0. Unchanged from 6.7.7 through 7.1.0.
Signature, return type and hooks compared across 5 parsed releases.
src/wp-includes/post.php, and regenerated for each WordPress release so it tracks the code rather than a snapshot of it.