Table of Contents
- What
wp_insert_post()does - Key
$postarrfields - Safe draft-first example
- Insert versus update
- Common mistakes
- Testing checklist
- Conclusion
What wp_insert_post() does
wp_insert_post() is the WordPress core function that inserts a new post into the database or updates an existing one. If the $postarr array contains a non-zero ID, WordPress updates that post. If it does not, WordPress creates a new post from the supplied values.
The current WordPress wp_insert_post() reference documents this signature:
wp_insert_post( array $postarr, bool $wp_error = false, bool $fire_after_hooks = true );
On success it returns the post ID. On failure it returns 0, or a WP_Error object when the second argument is true.
This article is the function reference. For the older how-to that walks through creating a post or page in PHP, see Programmatically Create a WordPress Post or Page with PHP.
Key $postarr fields
You do not need every field. These are the ones that most custom code should set deliberately.
post_titleandpost_contentare the required content fields. WordPress can reject an empty title, content, and excerpt combination as empty content.post_statusdefaults todraft. That default is useful. Do not switch it topublishuntil the data is trusted and reviewed.post_typedefaults topost. Setpageor a registered custom post type when that is what you intend to create.post_authordefaults to the current user ID.post_namebecomes the slug. If you omit it, WordPress sanitizes the title when the status is not a draft or pending post.post_categorymust be an array of category IDs, even when you assign only one category.tags_inputaccepts tag names, slugs, or IDs.tax_inputassigns custom taxonomy terms. Hierarchical taxonomies need term IDs. The current user also needs theassign_termscapability for that taxonomy, otherwise WordPress ignores the list.meta_inputwrites post meta after the insert. It has been supported since WordPress 4.4.IDturns the call into an update of that existing post.
If the incoming title came from a form, strip tags first. WordPress sanitizes post data through sanitize_post(), but the official security note still recommends wp_strip_all_tags() on titles and similar fields.
Safe draft-first example
The following snippet creates a draft post, returns a WP_Error on failure, and only continues when a real post ID comes back. Add it through Code Snippets or a small site-specific plugin and test it on staging.
/**
* Create a draft WordPress post from trusted values.
*
* @param string $title Post title.
* @param string $content Post content.
* @return int|WP_Error
*/
function yoohoo_insert_draft_post( $title, $content ) {
$postarr = array(
'post_title' => wp_strip_all_tags( $title ),
'post_content' => $content,
'post_status' => 'draft',
'post_type' => 'post',
'post_author' => get_current_user_id(),
'meta_input' => array(
'_yoohoo_created_by' => 'wp_insert_post_example',
),
);
$result = wp_insert_post( $postarr, true );
if ( is_wp_error( $result ) ) {
return $result;
}
return (int) $result;
}
Keep the first save as a draft unless the workflow has already validated the content, author, and destination. Publishing immediately from untrusted request data is the most common production mistake.
Pro Tip: Pass
trueas the second argument so failures return aWP_Errorinstead of a silent0. Check that return value before you enqueue a webhook or send a notification.
Insert versus update
wp_insert_post() can update an existing post when you include ID. wp_update_post() is the clearer wrapper for that case: it loads the current post, merges your changes, and then calls wp_insert_post().
Use wp_update_post() when you are changing a known post. Use wp_insert_post() without an ID when you are creating one. Do not invent an ID and hope WordPress creates it.
Both functions fire save_post and wp_insert_post. If your own save_post callback calls either function again, remove the callback first or you can create a loop. That is the same verify-the-hook habit as our wpuf_profile_update guide.
Common mistakes
Publishing on the first save
The default status is draft. Keep it until the post has been checked. A scheduled future status also needs a valid post_date.
Ignoring the return value
A 0 is a failure when $wp_error is false. A WP_Error is a failure when $wp_error is true. Do not treat either as a post ID.
Passing category names instead of IDs
post_category expects an array of integers. Names and slugs belong in tags_input or, for custom taxonomies, in tax_input with the correct format.
Assuming tax_input always applies
If the current user cannot assign terms for that taxonomy, WordPress skips the list. In privileged or CLI code, set terms afterward with wp_set_object_terms().
Creating posts from raw $_POST data
Strip tags from titles and other plain-text fields. Confirm capabilities and a nonce before any front-end form reaches wp_insert_post().
Testing checklist
- Create a draft and confirm it appears in Posts with the expected title and author.
- Pass an empty title, content, and excerpt and confirm the function fails instead of inserting a blank post.
- Set
$wp_errortotrueand confirm aWP_Erroris returned on a forced failure. - Assign one category by ID and confirm it is attached.
- Include an
IDonly when you intend to update an existing post. - If a successful insert should notify another system, send that event through WP Zapier after you have a real post ID.
Conclusion
wp_insert_post() is a small API with a strict contract: build a complete $postarr, start as a draft unless the data is already trusted, check the return value, and keep updates on a known post ID.
That produces a post you can review, hook, or send onward without publishing incomplete or unsafe content.
What would you create first as a draft: a support note, a product announcement, or a membership update?