wp_insert_post()
概述
wp_insert_post() 是 WordPress 核心函数,用于插入新文章或更新现有文章。它接受一个包含文章数据的数组作为参数,并根据是否提供 ID 来决定执行插入或更新操作。函数内部会处理数据验证、清理和关联分类、标签、自定义分类法及元数据。
关键要点
- 函数根据 $postarr 参数中是否包含 'ID' 键值来决定更新或插入文章:若 ID 存在且非零,则更新对应文章;否则插入新文章。
- 必须提供 post_title 和 post_content,否则函数可能因内容为空而失败,可通过 wp_insert_post_empty_content 过滤器自定义空内容检查。
- 支持多种参数,包括 post_status、post_type、post_author、post_date、post_date_gmt、comment_status、ping_status、post_password、post_name、post_parent、menu_order、post_mime_type、guid、import_id、post_category、tags_input、tax_input、meta_input、page_template 等。
- 分类需以整数数组形式传递(即使只有一个分类),标签和自定义分类法可通过 tags_input 和 tax_input 设置,但 tax_input 要求用户有 assign_terms 权限。
- 函数返回文章 ID(成功时)或 0/WP_Error(失败时),可通过 $wp_error 参数控制是否返回 WP_Error。
- 数据通过 sanitize_post() 自动清理,但建议对 post_title 等字段使用 wp_strip_all_tags() 移除 HTML 标签,特别是在前端表单中。
- 更新文章时需注意,若不指定 post_status,可能被重置为默认值 'draft',建议使用 wp_update_post() 进行部分更新。
- 可通过 $fire_after_hooks 参数控制是否触发插入后的钩子,默认启用。
代码示例
// 创建文章对象
$my_post = array(
'post_title' => wp_strip_all_tags( $_POST['post_title'] ),
'post_content' => $_POST['post_content'],
'post_status' => 'publish',
'post_author' => 1,
'post_category' => array( 8,39 )
);
// 插入文章到数据库
wp_insert_post( $my_post );注意事项
- 使用 tax_input 时,需确保当前用户有 assign_terms 权限,否则应改用 wp_set_object_terms()。
- 设置未来发布时间(post_status 为 'future')时,必须同时指定 post_date。
- page_template 参数仅对 post_type 为 'page' 的文章有效,非页面文章需通过 update_post_meta() 设置 '_wp_page_template'。
- 文章数据中的反斜杠会被自动移除,若需保留,应使用 wp_slash() 处理。
- 通过 import_id 可指定新文章的自定义 ID,但需确保其唯一性。
- 函数不会自动设置 post_modified 和 post_modified_gmt,如需自定义,可通过 wp_insert_post_data 过滤器实现。
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.
See also
Parameters
$postarrarrayrequired-
An array of elements that make up a post to update or insert.
IDintThe post ID. If equal to something other than 0, the post with that ID will be updated. Default 0.post_authorintThe ID of the user who added the post. Default is the current user ID.post_datestringThe date of the post. Default is the current time.post_date_gmtstringThe date of the post in the GMT timezone. Default is the value of$post_date.post_contentstringThe post content. Default empty.post_content_filteredstringThe filtered post content. Default empty.post_titlestringThe post title. Default empty.post_excerptstringThe post excerpt. Default empty.post_statusstringThe post status. Default'draft'.post_typestringThe post type. Default'post'.comment_statusstringWhether the post can accept comments. Accepts'open'or'closed'.
Default is the value of'default_comment_status'option.ping_statusstringWhether the post can accept pings. Accepts'open'or'closed'.
Default is the value of'default_ping_status'option.post_passwordstringThe password to access the post. Default empty.post_namestringThe post name. Default is the sanitized post title when creating a new post.to_pingstringSpace or carriage return-separated list of URLs to ping.
Default empty.pingedstringSpace or carriage return-separated list of URLs that have been pinged. Default empty.post_parentintSet this for the post it belongs to, if any. Default 0.menu_orderintThe order the post should be displayed in. Default 0.post_mime_typestringThe mime type of the post. Default empty.guidstringGlobal Unique ID for referencing the post. Default empty.import_idintThe post ID to be used when inserting a new post.
If specified, must not match any existing post ID. Default 0.post_categoryint[]Array of category IDs.
Defaults to value of the'default_category'option.tags_inputarrayArray of tag names, slugs, or IDs. Default empty.tax_inputarrayAn 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. Default empty.meta_inputarrayArray of post meta values keyed by their post meta key. Default empty.page_templatestringPage template to use.
$wp_errorbooloptional-
Whether to return a WP_Error on failure.
Default:
false $fire_after_hooksbooloptional-
Whether to fire the after insert hooks.
Default:
true
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' );
/**
* Filters whether the post should be considered "empty".
*
* The post is considered "empty" if both:
* 1. The post type supports the title, editor, and excerpt fields
* 2. The title, editor, and excerpt fields are all empty
*
* Returning a truthy value from the filter will effectively short-circuit
* the new post being inserted and return 0. If $wp_error is true, a WP_Error
* will be returned instead.
*
* @since 3.3.0
*
* @param bool $maybe_empty Whether the post should be considered "empty".
* @param array $postarr Array of post data.
*/
if ( apply_filters( 'wp_insert_post_empty_content', $maybe_empty, $postarr ) ) {
if ( $wp_error ) {
return new WP_Error( 'empty_content', __( 'Content, title, and excerpt are empty.' ) );
} else {
return 0;
}
}
$post_status = empty( $postarr['post_status'] ) ? 'draft' : $postarr['post_status'];
if ( 'attachment' === $post_type && ! in_array( $post_status, array( 'inherit', 'private', 'trash', 'auto-draft' ), true ) ) {
$post_status = 'inherit';
}
if ( ! empty( $postarr['post_category'] ) ) {
// Filter out empty terms.
$post_category = array_filter( $postarr['post_category'] );
} elseif ( $update && ! isset( $postarr['post_category'] ) ) {
$post_category = $post_before->post_category;
}
// Make sure we set a valid category.
if ( empty( $post_category ) || 0 === count( $post_category ) || ! is_array( $post_category ) ) {
// 'post' requires at least one category.
if ( 'post' === $post_type && 'auto-draft' !== $post_status ) {
$post_category = array( get_option( 'default_category' ) );
} else {
$post_category = array();
}
}
/*
* Don't allow contributors to set the post slug for pending review posts.
*
* For new posts check the primitive capability, for updates check the meta capability.
*/
if ( 'pending' === $post_status ) {
$post_type_object = get_post_type_object( $post_type );
if ( ! $update && $post_type_object && ! current_user_can( $post_type_object->cap->publish_posts ) ) {
$post_name = '';
} elseif ( $update && ! current_user_can( 'publish_post', $post_id ) ) {
$post_name = '';
}
}
/*
* Create a valid post name. Drafts and pending posts are allowed to have
* an empty post name.
*/
if ( empty( $post_name ) ) {
if ( ! in_array( $post_status, array( 'draft', 'pending', 'auto-draft' ), true ) ) {
$post_name = sanitize_title( $post_title );
} else {
$post_name = '';
}
} else {
// On updates, we need to check to see if it's using the old, fixed sanitization context.
$check_name = sanitize_title( $post_name, '', 'old-save' );
if ( $update
&& strtolower( urlencode( $post_name ) ) === $check_name
&& get_post_field( 'post_name', $post_id ) === $check_name
) {
$post_name = $check_name;
} else { // New post, or slug has changed.
$post_name = sanitize_title( $post_name );
}
}
/*
* Resolve the post date from any provided post date or post date GMT strings;
* if none are provided, the date will be set to now.
*/
$post_date = wp_resolve_post_date( $postarr['post_date'], $postarr['post_date_gmt'] );
if ( ! $post_date ) {
if ( $wp_error ) {
return new WP_Error( 'invalid_date', __( 'Invalid date.' ) );
} else {
return 0;
}
}
if ( empty( $postarr['post_date_gmt'] ) || '0000-00-00 00:00:00' === $postarr['post_date_gmt'] ) {
if ( ! in_array( $post_status, get_post_stati( array( 'date_floating' => true ) ), true ) ) {
$post_date_gmt = get_gmt_from_date( $post_date );
} else {
$post_date_gmt = '0000-00-00 00:00:00';
}
} else {
$post_date_gmt = $postarr['post_date_gmt'];
}
if ( $update || '0000-00-00 00:00:00' === $post_date ) {
$post_modified = current_time( 'mysql' );
$post_modified_gmt = current_time( 'mysql', true );
} else {
$post_modified = $post_date;
$post_modified_gmt = $post_date_gmt;
}
if ( 'attachment' !== $post_type ) {
$now = gmdate( 'Y-m-d H:i:s' );
if ( 'publish' === $post_status ) {
if ( strtotime( $post_date_gmt ) - strtotime( $now ) >= MINUTE_IN_SECONDS ) {
$post_status = 'future';
}
} elseif ( 'future' === $post_status ) {
if ( strtotime( $post_date_gmt ) - strtotime( $now ) < MINUTE_IN_SECONDS ) {
$post_status = 'publish';
}
}
}
// Comment status.
if ( empty( $postarr['comment_status'] ) ) {
if ( $update ) {
$comment_status = 'closed';
} else {
$comment_status = get_default_comment_status( $post_type );
}
} else {
$comment_status = $postarr['comment_status'];
}
// These variables are needed by compact() later.
$post_content_filtered = $postarr['post_content_filtered'];
$post_author = isset( $postarr['post_author'] ) ? $postarr['post_author'] : $user_id;
$ping_status = empty( $postarr['ping_status'] ) ? get_default_comment_status( $post_type, 'pingback' ) : $postarr['ping_status'];
$to_ping = isset( $postarr['to_ping'] ) ? sanitize_trackback_urls( $postarr['to_ping'] ) : '';
$pinged = isset( $postarr['pinged'] ) ? $postarr['pinged'] : '';
$import_id = isset( $postarr['import_id'] ) ? $postarr['import_id'] : 0;
/*
* The 'wp_insert_post_parent' filter expects all variables to be present.
* Previously, these variables would have already been extracted
*/
if ( isset( $postarr['menu_order'] ) ) {
$menu_order = (int) $postarr['menu_order'];
} else {
$menu_order = 0;
}
$post_password = isset( $postarr['post_password'] ) ? $postarr['post_password'] : '';
if ( 'private' === $post_status ) {
$post_password = '';
}
if ( isset( $postarr['post_parent'] ) ) {
$post_parent = (int) $postarr['post_parent'];
} else {
$post_parent = 0;
}
$new_postarr = array_merge(
array(
'ID' => $post_id,
),
compact( array_diff( array_keys( $defaults ), array( 'context', 'filter' ) ) )
);
/**
* Filters the post parent -- used to check for and prevent hierarchy loops.
*
* @since 3.1.0
*
* @param int $post_parent Post parent ID.
* @param int $post_id Post ID.
* @param array $new_postarr Array of parsed post data.
* @param array $postarr Array of sanitized, but otherwise unmodified post data.
*/
$post_parent = apply_filters( 'wp_insert_post_parent', $post_parent, $post_id, $new_postarr, $postarr );
/*
* If the post is being untrashed and it has a desired slug stored in post meta,
* reassign it.
*/
if ( 'trash' === $previous_status && 'trash' !== $post_status ) {
$desired_post_slug = get_post_meta( $post_id, '_wp_desired_post_slug', true );
if ( $desired_post_slug ) {
delete_post_meta( $post_id, '_wp_desired_post_slug' );
$post_name = $desired_post_slug;
}
}
// If a trashed post has the desired slug, change it and let this post have it.
if ( 'trash' !== $post_status && $post_name ) {
/**
* Filters whether or not to add a `__trashed` suffix to trashed posts that match the name of the updated post.
*
* @since 5.4.0
*
* @param bool $add_trashed_suffix Whether to attempt to add the suffix.
* @param string $post_name The name of the post being updated.
* @param int $post_id Post ID.
*/
$add_trashed_suffix = apply_filters( 'add_trashed_suffix_to_trashed_posts', true, $post_name, $post_id );
if ( $add_trashed_suffix ) {
wp_add_trashed_suffix_to_post_name_for_trashed_posts( $post_name, $post_id );
}
}
// When trashing an existing post, change its slug to allow non-trashed posts to use it.
if ( 'trash' === $post_status && 'trash' !== $previous_status && 'new' !== $previous_status ) {
$post_name = wp_add_trashed_suffix_to_post_name_for_post( $post_id );
}
$post_name = wp_unique_post_slug( $post_name, $post_id, $post_status, $post_type, $post_parent );
// Don't unslash.
$post_mime_type = isset( $postarr['post_mime_type'] ) ? $postarr['post_mime_type'] : '';
// Expected_slashed (everything!).
$data = compact(
'post_author',
'post_date',
'post_date_gmt',
'post_content',
'post_content_filtered',
'post_title',
'post_excerpt',
'post_status',
'post_type',
'comment_status',
'ping_status',
'post_password',
'post_name',
'to_ping',
'pinged',
'post_modified',
'post_modified_gmt',
'post_parent',
'menu_order',
'post_mime_type',
'guid'
);
$emoji_fields = array( 'post_title', 'post_content', 'post_excerpt' );
foreach ( $emoji_fields as $emoji_field ) {
if ( isset( $data[ $emoji_field ] ) ) {
$charset = $wpdb->get_col_charset( $wpdb->posts, $emoji_field );
// The 'utf8' character set is a deprecated alias of 'utf8mb3'. See <https://dev.mysql.com/doc/refman/8.4/en/charset-unicode-utf8.html>.
if ( 'utf8' === $charset || 'utf8mb3' === $charset ) {
$data[ $emoji_field ] = wp_encode_emoji( $data[ $emoji_field ] );
}
}
}
if ( 'attachment' === $post_type ) {
/**
* Filters attachment post data before it is updated in or added to the database.
*
* @since 3.9.0
* @since 5.4.1 The `$unsanitized_postarr` parameter was added.
* @since 6.0.0 The `$update` parameter was added.
*
* @param array $data An array of slashed, sanitized, and processed attachment post data.
* @param array $postarr An array of slashed and sanitized attachment post data, but not processed.
* @param array $unsanitized_postarr An array of slashed yet *unsanitized* and unprocessed attachment post data
* as originally passed to wp_insert_post().
* @param bool $update Whether this is an existing attachment post being updated.
*/
$data = apply_filters( 'wp_insert_attachment_data', $data, $postarr, $unsanitized_postarr, $update );
} else {
/**
* Filters slashed post data just before it is inserted into the database.
*
* @since 2.7.0
* @since 5.4.1 The `$unsanitized_postarr` parameter was added.
* @since 6.0.0 The `$update` parameter was added.
*
* @param array $data An array of slashed, sanitized, and processed post data.
* @param array $postarr An array of sanitized (and slashed) but otherwise unmodified post data.
* @param array $unsanitized_postarr An array of slashed yet *unsanitized* and unprocessed post data as
* originally passed to wp_insert_post().
* @param bool $update Whether this is an existing post being updated.
*/
$data = apply_filters( 'wp_insert_post_data', $data, $postarr, $unsanitized_postarr, $update );
}
$data = wp_unslash( $data );
$where = array( 'ID' => $post_id );
if ( $update ) {
/**
* Fires immediately before an existing post is updated in the database.
*
* @since 2.5.0
*
* @param int $post_id Post ID.
* @param array $data Array of unslashed post data.
*/
do_action( 'pre_post_update', $post_id, $data );
if ( false === $wpdb->update( $wpdb->posts, $data, $where ) ) {
if ( $wp_error ) {
if ( 'attachment' === $post_type ) {
$message = __( 'Could not update attachment in the database.' );
} else {
$message = __( 'Could not update post in the database.' );
}
return new WP_Error( 'db_update_error', $message, $wpdb->last_error );
} else {
return 0;
}
}
} else {
// If there is a suggested ID, use it if not already present.
if ( ! empty( $import_id ) ) {
$import_id = (int) $import_id;
if ( ! $wpdb->get_var( $wpdb->prepare( "SELECT ID FROM $wpdb->posts WHERE ID = %d", $import_id ) ) ) {
$data['ID'] = $import_id;
}
}
/**
* Fires immediately before a new post is inserted in the database.
*
* @since 6.9.0
*
* @param array $data Array of unslashed post data.
*/
do_action( 'pre_post_insert', $data );
if ( false === $wpdb->insert( $wpdb->posts, $data ) ) {
if ( $wp_error ) {
if ( 'attachment' === $post_type ) {
$message = __( 'Could not insert attachment into the database.' );
} else {
$message = __( 'Could not insert post into the database.' );
}
return new WP_Error( 'db_insert_error', $message, $wpdb->last_error );
} else {
return 0;
}
}
$post_id = (int) $wpdb->insert_id;
// Use the newly generated $post_id.
$where = array( 'ID' => $post_id );
}
if ( empty( $data['post_name'] ) && ! in_array( $data['post_status'], array( 'draft', 'pending', 'auto-draft' ), true ) ) {
$data['post_name'] = wp_unique_post_slug( sanitize_title( $data['post_title'], $post_id ), $post_id, $data['post_status'], $post_type, $post_parent );
$wpdb->update( $wpdb->posts, array( 'post_name' => $data['post_name'] ), $where );
clean_post_cache( $post_id );
}
if ( is_object_in_taxonomy( $post_type, 'category' ) ) {
wp_set_post_categories( $post_id, $post_category );
}
if ( isset( $postarr['tags_input'] ) && is_object_in_taxonomy( $post_type, 'post_tag' ) ) {
wp_set_post_tags( $post_id, $postarr['tags_input'] );
}
// Add default term for all associated custom taxonomies.
if ( 'auto-draft' !== $post_status ) {
foreach ( get_object_taxonomies( $post_type, 'object' ) as $taxonomy => $tax_object ) {
if ( ! empty( $tax_object->default_term ) ) {
// Filter out empty terms.
if ( isset( $postarr['tax_input'][ $taxonomy ] ) && is_array( $postarr['tax_input'][ $taxonomy ] ) ) {
$postarr['tax_input'][ $taxonomy ] = array_filter( $postarr['tax_input'][ $taxonomy ] );
}
// Passed custom taxonomy list overwrites the existing list if not empty.
$terms = wp_get_object_terms( $post_id, $taxonomy, array( 'fields' => 'ids' ) );
if ( ! empty( $terms ) && empty( $postarr['tax_input'][ $taxonomy ] ) ) {
$postarr['tax_input'][ $taxonomy ] = $terms;
}
if ( empty( $postarr['tax_input'][ $taxonomy ] ) ) {
$default_term_id = get_option( 'default_term_' . $taxonomy );
if ( ! empty( $default_term_id ) ) {
$postarr['tax_input'][ $taxonomy ] = array( (int) $default_term_id );
}
}
}
}
}
// New-style support for all custom taxonomies.
if ( ! empty( $postarr['tax_input'] ) ) {
foreach ( $postarr['tax_input'] as $taxonomy => $tags ) {
$taxonomy_obj = get_taxonomy( $taxonomy );
if ( ! $taxonomy_obj ) {
/* translators: %s: Taxonomy name. */
_doing_it_wrong( __FUNCTION__, sprintf( __( 'Invalid taxonomy: %s.' ), $taxonomy ), '4.4.0' );
continue;
}
// array = hierarchical, string = non-hierarchical.
if ( is_array( $tags ) ) {
$tags = array_filter( $tags );
}
if ( current_user_can( $taxonomy_obj->cap->assign_terms ) ) {
wp_set_post_terms( $post_id, $tags, $taxonomy );
}
}
}
if ( ! empty( $postarr['meta_input'] ) ) {
foreach ( $postarr['meta_input'] as $field => $value ) {
update_post_meta( $post_id, $field, $value );
}
}
$current_guid = get_post_field( 'guid', $post_id );
// Set GUID.
if ( ! $update && '' === $current_guid ) {
$wpdb->update( $wpdb->posts, array( 'guid' => get_permalink( $post_id ) ), $where );
}
if ( 'attachment' === $postarr['post_type'] ) {
if ( ! empty( $postarr['file'] ) ) {
update_attached_file( $post_id, $postarr['file'] );
}
if ( ! empty( $postarr['context'] ) ) {
add_post_meta( $post_id, '_wp_attachment_context', $postarr['context'], true );
}
}
// Set or remove featured image.
if ( isset( $postarr['_thumbnail_id'] ) ) {
$thumbnail_support = current_theme_supports( 'post-thumbnails', $post_type ) && post_type_supports( $post_type, 'thumbnail' ) || 'revision' === $post_type;
if ( ! $thumbnail_support && 'attachment' === $post_type && $post_mime_type ) {
if ( wp_attachment_is( 'audio', $post_id ) ) {
$thumbnail_support = post_type_supports( 'attachment:audio', 'thumbnail' ) || current_theme_supports( 'post-thumbnails', 'attachment:audio' );
} elseif ( wp_attachment_is( 'video', $post_id ) ) {
$thumbnail_support = post_type_supports( 'attachment:video', 'thumbnail' ) || current_theme_supports( 'post-thumbnails', 'attachment:video' );
}
}
if ( $thumbnail_support ) {
$thumbnail_id = (int) $postarr['_thumbnail_id'];
if ( -1 === $thumbnail_id ) {
delete_post_thumbnail( $post_id );
} else {
set_post_thumbnail( $post_id, $thumbnail_id );
}
}
}
clean_post_cache( $post_id );
$post = get_post( $post_id );
if ( ! empty( $postarr['page_template'] ) ) {
$post->page_template = $postarr['page_template'];
$page_templates = wp_get_theme()->get_page_templates( $post );
if ( 'default' !== $postarr['page_template'] && ! isset( $page_templates[ $postarr['page_template'] ] ) ) {
if ( $wp_error ) {
return new WP_Error( 'invalid_page_template', __( 'Invalid page template.' ) );
}
update_post_meta( $post_id, '_wp_page_template', 'default' );
} else {
update_post_meta( $post_id, '_wp_page_template', $postarr['page_template'] );
}
}
if ( 'attachment' !== $postarr['post_type'] ) {
wp_transition_post_status( $data['post_status'], $previous_status, $post );
} else {
if ( $update ) {
/**
* Fires once an existing attachment has been updated.
*
* @since 2.0.0
*
* @param int $post_id Attachment ID.
*/
do_action( 'edit_attachment', $post_id );
$post_after = get_post( $post_id );
/**
* Fires once an existing attachment has been updated.
*
* @since 4.4.0
*
* @param int $post_id Post ID.
* @param WP_Post $post_after Post object following the update.
* @param WP_Post $post_before Post object before the update.
*/
do_action( 'attachment_updated', $post_id, $post_after, $post_before );
} else {
/**
* Fires once an attachment has been added.
*
* @since 2.0.0
*
* @param int $post_id Attachment ID.
*/
do_action( 'add_attachment', $post_id );
}
return $post_id;
}
if ( $update ) {
/**
* Fires once an existing post has been updated.
*
* The dynamic portion of the hook name, `$post->post_type`, refers to
* the post type slug.
*
* Possible hook names include:
*
* - `edit_post_post`
* - `edit_post_page`
*
* @since 5.1.0
*
* @param int $post_id Post ID.
* @param WP_Post $post Post object.
*/
do_action( "edit_post_{$post->post_type}", $post_id, $post );
/**
* Fires once an existing post has been updated.
*
* @since 1.2.0
*
* @param int $post_id Post ID.
* @param WP_Post $post Post object.
*/
do_action( 'edit_post', $post_id, $post );
$post_after = get_post( $post_id );
/**
* Fires once an existing post has been updated.
*
* @since 3.0.0
*
* @param int $post_id Post ID.
* @param WP_Post $post_after Post object following the update.
* @param WP_Post $post_before Post object before the update.
*/
do_action( 'post_updated', $post_id, $post_after, $post_before );
}
/**
* Fires once a post has been saved.
*
* The dynamic portion of the hook name, `$post->post_type`, refers to
* the post type slug.
*
* Possible hook names include:
*
* - `save_post_post`
* - `save_post_page`
*
* @since 3.7.0
*
* @param int $post_id Post ID.
* @param WP_Post $post Post object.
* @param bool $update Whether this is an existing post being updated.
*/
do_action( "save_post_{$post->post_type}", $post_id, $post, $update );
/**
* Fires once a post has been saved.
*
* @since 1.5.0
*
* @param int $post_id Post ID.
* @param WP_Post $post Post object.
* @param bool $update Whether this is an existing post being updated.
*/
do_action( 'save_post', $post_id, $post, $update );
/**
* Fires once a post has been saved.
*
* @since 2.0.0
*
* @param int $post_id Post ID.
* @param WP_Post $post Post object.
* @param bool $update Whether this is an existing post being updated.
*/
do_action( 'wp_insert_post', $post_id, $post, $update );
if ( $fire_after_hooks ) {
wp_after_insert_post( $post, $update, $post_before );
}
return $post_id;
}
Hooks
- do_action( ‘add_attachment’, int $post_id )
-
Fires once an attachment has been added.
- apply_filters( ‘add_trashed_suffix_to_trashed_posts’, bool $add_trashed_suffix, string $post_name, int $post_id )
-
Filters whether or not to add a
__trashedsuffix to trashed posts that match the name of the updated post. - do_action( ‘attachment_updated’, int $post_id, WP_Post $post_after, WP_Post $post_before )
-
Fires once an existing attachment has been updated.
- do_action( ‘edit_attachment’, int $post_id )
-
Fires once an existing attachment has been updated.
- do_action( ‘edit_post’, int $post_id, WP_Post $post )
-
Fires once an existing post has been updated.
- do_action( “edit_post_{$post->post_type}”, int $post_id, WP_Post $post )
-
Fires once an existing post has been updated.
- do_action( ‘post_updated’, int $post_id, WP_Post $post_after, WP_Post $post_before )
-
Fires once an existing post has been updated.
- do_action( ‘pre_post_insert’, array $data )
-
Fires immediately before a new post is inserted in the database.
- do_action( ‘pre_post_update’, int $post_id, array $data )
-
Fires immediately before an existing post is updated in the database.
- do_action( ‘save_post’, int $post_id, WP_Post $post, bool $update )
-
Fires once a post has been saved.
- do_action( “save_post_{$post->post_type}”, int $post_id, WP_Post $post, bool $update )
-
Fires once a post has been saved.
- apply_filters( ‘wp_insert_attachment_data’, array $data, array $postarr, array $unsanitized_postarr, bool $update )
-
Filters attachment post data before it is updated in or added to the database.
- do_action( ‘wp_insert_post’, int $post_id, WP_Post $post, bool $update )
-
Fires once a post has been saved.
- apply_filters( ‘wp_insert_post_data’, array $data, array $postarr, array $unsanitized_postarr, bool $update )
-
Filters slashed post data just before it is inserted into the database.
- apply_filters( ‘wp_insert_post_empty_content’, bool $maybe_empty, array $postarr )
-
Filters whether the post should be considered “empty”.
- apply_filters( ‘wp_insert_post_parent’, int $post_parent, int $post_id, array $new_postarr, array $postarr )
-
Filters the post parent — used to check for and prevent hierarchy loops.
Changelog
| Version | Description |
|---|---|
| 5.6.0 | Added the $fire_after_hooks parameter. |
| 4.4.0 | A 'meta_input' array can now be passed to $postarr to add post meta data. |
| 4.2.0 | Support was added for encoding emoji in the post title, content, and excerpt. |
| 2.6.0 | Added the $wp_error parameter to allow a WP_Error to be returned on failure. |
| 1.0.0 | Introduced. |
Skip to note 21 content
truongwp
Insert post with custom taxonomy and post meta data (since 4.4.0):
$hierarchical_tax = array( 13, 10 ); // Array of tax ids. $non_hierarchical_terms = 'tax name 1, tax name 2'; // Can use array of ids or string of tax names separated by commas $post_arr = array( 'post_title' => 'Test post', 'post_content' => 'Test post content', 'post_status' => 'publish', 'post_author' => get_current_user_id(), 'tax_input' => array( 'hierarchical_tax' => $hierarchical_tax, 'non_hierarchical_tax' => $non_hierarchical_terms, ), 'meta_input' => array( 'test_meta_key' => 'value of test_meta_key', ), );Skip to note 22 content
Kiera Howe
‘tax_input’ in the arguments only works on wp_insert_post if the function is being called by a user with “assign_terms” access
https://core.trac.wordpress.org/browser/tags/4.7/src/wp-includes/post.php#L3352
Skip to note 23 content
Codex
Inserting a Post
Before calling
wp_insert_post()it is necessary to create an array to pass the necessary elements that make up a post. Thewp_insert_post()will fill out a default list of these but the user is required to provide the title and content otherwise the database write will fail.The next example shows the post title, content, status, author, and post categories being set. You can add further key-value pairs, making sure the keys match the names of the columns in the
wp_poststable in the database.// Gather post data. $my_post = array( 'post_title' => 'My post', 'post_content' => 'This is my post.', 'post_status' => 'publish', 'post_author' => 1, 'post_category' => array( 8,39 ) ); // Insert the post into the database. wp_insert_post( $my_post );You can also get the new post ID after inserting a new post:
Skip to note 24 content
Aurovrata Venet
Its important that you validate your post ID before you use by checking if the returned values is not a WP_error,
$args = array( 'post_type' => 'my_custom_post', /*other default parameters you want to set*/ ); $post_id = wp_insert_post($args); if(!is_wp_error($post_id)){ //the post is valid }else{ //there was an error in the post insertion, echo $post_id->get_error_message(); }echo esc_html( $post_id->get_error_message() );if(!is_wp_error($post_id) && $post_id !== 0){Skip to note 25 content
Pablo Pacheco
As @Kiera Howe brilliantly noticed, ‘tax_input’ requires ‘assign_terms’ access.
Depending on the case it can make its use impracticable.
A quick solution could be replacing it by:
wp_set_object_terms( $post_id, array( $term_id1, $term_id2 ), 'tax_slug' );Reference:
https://ryansechrest.com/2012/07/wordpress-taxonomy-terms-dont-insert-when-cron-job-executes-wp_insert_post/
wp_set_current_user( 1 );beforehand, assuming the ID ‘1’ is the super admin.Skip to note 26 content
kanggheekeong
The post_template parameter should be described as well.
It should be set with a full file name, such as ‘templatefilename.php’.
This clarify will help developers trying to tie a template to a page.
'page_template' => 'templates/tpl_default.php'Skip to note 27 content
NateWr
By default,
wp_insert_postwill not insert an empty post. This can cause unexpected problems if you’re passing anIDand expecting it to fall back towp_update_post. For instance, the following will not work:wp_insert_post( array( 'ID' => 2, 'post_parent' => 1 ) );You will need to call
wp_update_postdirectly.wp_update_post( array( 'ID' => 2, 'post_parent' => 1 ) );Skip to note 28 content
flimm
The format for
post_dateandpost_date_gmtinput isY-m-d H:i:s, for instance2019-01-01 01:01:01. You can use PHP’sdatefunction, like this:wp_insert_post( array( 'post_date' => date( 'Y-m-d H:i:s', time() ), // etc ) );Skip to note 29 content
Compute
When saving data containing slashes remember to add extra slashes due to
wp_insert_postunslashing these values.$regex = '(b16-d{3}b)'; $post_data = array( 'post_title' => 'Test regex', 'post_content' => $regex, 'post_type' => 'post', ); $post_id = wp_insert_post( $post_data );Will store
(b16-d{3}b). To fix this usewp_slash():$regex = '(b16-d{3}b)'; $post_data = array( 'post_title' => 'Test regex', 'post_content' => wp_slash( $regex ), 'post_type' => 'post', ); $post_id = wp_insert_post( $post_data );Skip to note 30 content
西門 正 Code Guy
About taxonomy parameter
Parameters:
'tax_input' (array) Array of taxonomy terms keyed by their taxonomy name. Default empty.The description for the parameter ‘tax_input’ is not clearly stated and no example is given so far, so I would like to add a supplementary note here for the exact meaning and examples.
According to the source code of ‘wp_insert_post() ’, the ‘tax_input’ is being added by function ‘wp_set_post_terms() ’. So I refer to its description that Here is the example for using this:
'tax_input' => array( $taxomony => $tags )eg.
'something1, something2, something3'or
array( 30, 40, 50 )or
array( 'something1', 'something2', 'something3' )array( 30, 40, 50 )Tested: I have personally tested it in a custom post type with custom category(taxonomy) name with wp_update_post()
Skip to note 31 content
albertohrtzt
Directly insert existing attachment with id 32 to post thumbnail.
$thumbnail_id = 32; $args = array( 'post_type' => 'post', 'post_title' => 'Some title', 'post_content' => 'Some content', '_thumbnail_id' => $thumbnail_id, ); wp_insert_post( $args );Skip to note 32 content
Ateeq Rafeeq
To add Taxonomy and Meta field values with custom post types.
For Taxonomy:
Make sure Taxonomy name is key of tax_input array. And the array of Taxonomy ID’s is value of that key like in example below.
For Meta Values:
You can pass meta value name as key and meta value as that keys value.
$post_arr = array( 'post_title' => esc_html($video_title), 'post_content' => $video_description, 'post_status' => 'draft', 'post_author' => $user_id, 'post_type' => 'videos', 'tax_input' => array( "video_category" => $taxnomy_ids //Video Cateogry is Taxnmony Name and being used as key of array. ), 'meta_input' => array( 'wc_video_url' => $video_url, 'wc_product' => $video_product ), );Skip to note 33 content
Benjamin Kostenbader
If you’re creating a post and the post ID doesn’t yet exist, using
'ID' => your_numberwill not create a new post. It will return blank, because it searched for the ID and it didn’t exist.Instead, use
'import_id'like this:$args = array( 'import_id' => your_custom_integer, // this designates the custom post ID you want to use! make sure it's unique 'post_type' => 'your_post_type', 'post_status' => 'publish', 'content' => 'your_content', 'meta_input' => array( 'product_data' => 'if_you_want_this' ) ); $id = wp_insert_post($args);Skip to note 34 content
cvladan
By default,
wp_insert_postdoes not allow you to setpost_modified.If you want to change the modified date of the post, you will find that this is not possible. In fact, the
$postarrparameter does not contain thepost_modifiedorpost_modified_gmtfield. This is intentional, and in a way makes sense, because the modified post date is automatically set to the time the post was inserted.However, if you are doing an import or similar, you may want to set the
post_modifiedfield explicitly. Of course you can use two separate commands and two SQL statements – first insert the post withwp_insert_postand then update the modified date withwp_update_post. For instance like thiswp_update_post( array( 'ID' => $post_id, 'post_modified' => $date, 'post_modified_gmt' => get_gmt_from_date( $date ), ) );However, a much better and more performant solution that requires only one SQL statement is as follows. You need to hook into
wp_insert_post_dataand insert the desired value just beforewp_insert_post, and then remove this filter so that all other insert operations in WordPress continue to work as expected. It would look roughly as follows:function wpdocs_with_post_modified( $data, $postarr ) { $data['post_modified'] = $postarr['post_modified'] ?? null; $data['post_modified_gmt'] = $postarr['post_modified_gmt'] ?? get_gmt_from_date( $data['post_modified'] ); $data['post_modified'] = $data['post_modified'] ?? get_date_from_gmt( $data['post_modified_gmt'] ); return $data; } add_filter( 'wp_insert_post_data', 'wpdocs_with_post_modified', PHP_INT_MAX, 2 ); $newpost = wp_insert_post( $data ); remove_filter( 'wp_insert_post_data', 'wpdocs_with_post_modified', PHP_INT_MAX );Skip to note 35 content
Akhtarujjaman Shuvo
Prevent duplicate post
// Product Title $post_title = 'Test Product'; // Add Product $new_post = array( 'post_title' => $post_title, 'post_type' => 'product', 'post_staus' => 'draft', 'post_content' => 'Lorem Ipsum is simply dummy text of the printing and typesetting industry.', 'post_excerpt' => 'Lorem Ipsum is simply dummy text' ); // Catch post ID $post_id = post_exists( $post_title ) or wp_insert_post( $new_post );Skip to note 36 content
Daniyal Ahmed (a11n)
Create a post for custom post type :
$id = wp_insert_post(array('post_title'=>'random', 'post_type'=>'custom_post', 'post_content'=>'demo text'));Skip to note 37 content
Roberto
Watch out when updating a post using wp_insert_post() ; if you don’t explicitly specify the post_status, it will change it back to ‘draft’.
I ended up using wp_update_post() instead.
Skip to note 38 content
asmitta
The second parameter of this function is now set to
falseif empty. So if you want an WP_Error on failure you need to specify the second parameter. E.g:<br />
// Before: wp_insert_post( $args )<br />
wp_insert_post( $args, true );<br />
Skip to note 39 content
Khoi Pro
To work with this function, you should add condition to each of
$post_title = $_POST['title']; $post_description = $_POST['content']; $post_posted_id = $_POST['user_id']; $post_categories = $_POST['term']; $post_options = array(); if( !empty($post_description) ) { $post_options['post_content'] = wp_strip_all_tags(post_description); } if( !empty($post_title) ) { $post_options['post_title'] = wp_strip_all_tags($post_title); // Then start to add new post $post_id = wp_insert_post($post_options); // Then return JSON in case you are using AJAX if( !is_wp_error($post_id) ) { wp_send_json_success(array('post_id' => $post_id), 200); } else { wp_send_json_error($post_id->get_error_message()); } }The second thing, in case you push multiple params like multiple checkbox checked, you should know how to push to array:
The example markup:
$language) : ?> <div class="form-check"> <input class="form-check-input" type="checkbox" value="<?php echo $language['id']; ?>" id="term[language][<?php echo $key; ?>]" name="term[language][]"> <label class="form-check-label" for="term[language][<?php echo $key; ?>]"></label> </div>We should check and using this function to keep it working:
$category) { if( $post_options['tax_input'][$key] !== '' ) { $post_options['tax_input'][$key] = array($category); } else { array_push($post_options['tax_input'][$key], $category); } } } ?>Skip to note 40 content
Oscar Abad Folgueira
This snippet creates a post with code checking if the post already exists before creating it.
function create_wordpress_post_with_code() { // Set the post ID to -1. This sets to no action at moment $post_id = -1; // Set the Author, Slug, title and content of the new post $author_id = 1; $slug = 'wordpress-post-created-with-code'; $title = 'WordPress post created whith code'; $content = 'This is the content of the post that we are creating right now with code. More text: I motsetning til hva mange tror, er ikke Lorem Ipsum bare tilfeldig tekst. Dets røtter springer helt tilbake til et stykke klassisk latinsk litteratur fra 45 år f.kr., hvilket gjør det over 2000 år gammelt. Richard McClintock - professor i latin ved Hampden-Sydney College i Virginia, USA - slo opp flere av de mer obskure latinske ordene, consectetur, fra en del av Lorem Ipsum, og fant dets utvilsomme opprinnelse gjennom å studere bruken av disse ordene i klassisk litteratur. Lorem Ipsum kommer fra seksjon 1.10.32 og 1.10.33 i "de Finibus Bonorum et Malorum" (The Extremes of Good and Evil) av Cicero, skrevet i år 45 f.kr. Boken er en avhandling om teorier rundt etikk, og var veldig populær under renessansen. Den første linjen av Lorem Ipsum, "Lorem Ipsum dolor sit amet...", er hentet fra en linje i seksjon 1.10.32.'; // Cheks if doen't exists a post with slug "wordpress-post-created-with-code". if( !post_exists_by_slug( $slug ) ) { // Set the post ID $post_id = wp_insert_post( array( 'comment_status' => 'closed', 'ping_status' => 'closed', 'post_author' => $author_id, 'post_name' => $slug, 'post_title' => $title, 'post_content' => $content, 'post_status' => 'publish', 'post_type' => 'post' ) ); } else { // Set pos_id to -2 becouse there is a post with this slug. $post_id = -2; } // end if } // end oaf_create_post_with_code add_filter( 'after_setup_theme', 'create_wordpress_post_with_code' ); /** * post_exists_by_slug. * * @return mixed boolean false if no post exists; post ID otherwise. */ function post_exists_by_slug( $post_slug ) { $args_posts = array( 'post_type' => 'post', 'post_status' => 'any', 'name' => $post_slug, 'posts_per_page' => 1, ); $loop_posts = new WP_Query( $args_posts ); if ( ! $loop_posts->have_posts() ) { return false; } else { $loop_posts->the_post(); return $loop_posts->post->ID; } }