register_post_type()
概述
register_post_type() 是 WordPress 核心函数,用于注册自定义文章类型(CPT)。它允许开发者定义新的内容类型,并配置其在前端和后端的行为,包括界面显示、权限、REST API 集成等。注册应在 'init' 钩子之后进行,以确保正确加载。
关键要点
- 函数接受两个参数:$post_type(字符串,不超过20个字符,仅限小写字母、数字、短横线和下划线)和 $args(数组或字符串,用于配置CPT属性)。
- 重要参数包括:public(控制公开性)、labels(标签数组)、supports(支持的核心功能如标题、编辑器等)、taxonomies(关联的分类法)、rewrite(重写规则)、show_in_rest(启用REST API和块编辑器)。
- 注册时需注意:避免使用保留的文章类型(如 post、page),建议为 $post_type 添加前缀以防止冲突;分类法应通过 $taxonomies 参数注册以确保一致性。
- 函数返回 WP_Post_Type 对象(成功时)或 WP_Error 对象(失败时)。
- 在主题中使用时,切换主题可能导致CPT消失,建议在插件中注册以确保持久性。
代码示例
function wpdocs_codex_book_init() {
$labels = array(
'name' => _x('Books', 'Post type general name', 'textdomain'),
'singular_name' => _x('Book', 'Post type singular name', 'textdomain'),
// 更多标签...
);
$args = array(
'labels' => $labels,
'public' => true,
'has_archive' => true,
'supports' => array('title', 'editor', 'author', 'thumbnail'),
'show_in_rest' => true,
);
register_post_type('book', $args);
}
add_action('init', 'wpdocs_codex_book_init');注意事项
- 启用块编辑器(Gutenberg)需要设置 'supports' 包含 'editor' 且 'show_in_rest' 为 true。
- 重写规则:注册后可能需要调用 flush_rewrite_rules()(例如在插件激活时)以确保固定链接正常工作,但避免在每次页面加载时调用。
- 性能:对于 hierarchical 设置为 true 的CPT,如果条目过多(如超过2-3千),可能导致管理页面加载缓慢。
- 权限:capability_type 和 capabilities 参数用于定义自定义能力,注意 map_meta_cap 的设置可能影响角色访问。
Registers a post type.
Description
Note: Post type registrations should not be hooked before the ‘init’ action. Also, any taxonomy connections should be registered via the $taxonomies argument to ensure consistency when hooks such as ‘parse_query’ or ‘pre_get_posts’ are used.
Post types can support any number of built-in core features such as meta boxes, custom fields, post thumbnails, post statuses, comments, and more. See the $supports argument for a complete list of supported features.
Parameters
$post_typestringrequired-
Post type key. Must not exceed 20 characters and may only contain lowercase alphanumeric characters, dashes, and underscores. See sanitize_key() .
$argsarray|stringoptional-
Array or string of arguments for registering a post type.
labelstringName of the post type shown in the menu. Usually plural.
Default is value of $labels['name'].labelsstring[]An array of labels for this post type. If not set, post labels are inherited for non-hierarchical types and page labels for hierarchical ones. See get_post_type_labels() for a full list of supported labels.descriptionstringA short descriptive summary of what the post type is.publicboolWhether a post type is intended for use publicly either via the admin interface or by front-end users. While the default settings of $exclude_from_search, $publicly_queryable, $show_ui, and $show_in_nav_menus are inherited from $public, each does not rely on this relationship and controls a very specific intention.
Default false.hierarchicalboolWhether the post type is hierarchical (e.g. page). Default false.exclude_from_searchboolWhether to exclude posts with this post type from front end search results. Default is the opposite value of $public.publicly_queryableboolWhether queries can be performed on the front end for the post type as part of parse_request(). Endpoints would include: * ?post_type={post_type_key} * ?{post_type_key}={single_post_slug}- ?{post_type_query_var}={single_post_slug} If not set, the default is inherited from $public.
show_uiboolWhether to generate and allow a UI for managing this post type in the admin. Default is value of $public.show_in_menubool|stringWhere to show the post type in the admin menu. To work, $show_ui must be true. If true, the post type is shown in its own top level menu. If false, no menu is shown. If a string of an existing top level menu ('tools.php'or'edit.php?post_type=page', for example), the post type will be placed as a sub-menu of that.
Default is value of $show_ui.show_in_nav_menusboolMakes this post type available for selection in navigation menus.
Default is value of $public.show_in_admin_barboolMakes this post type available via the admin bar. Default is value of $show_in_menu.show_in_restboolWhether to include the post type in the REST API. Set this to true for the post type to be available in the block editor.rest_basestringTo change the base URL of REST API route. Default is $post_type.rest_namespacestringTo change the namespace URL of REST API route. Default is wp/v2.rest_controller_classstringREST API controller class name. Default is ‘WP_REST_Posts_Controller‘.autosave_rest_controller_classstring|boolREST API controller class name. Default is ‘WP_REST_Autosaves_Controller‘.revisions_rest_controller_classstring|boolREST API controller class name. Default is ‘WP_REST_Revisions_Controller‘.late_route_registrationboolA flag to direct the REST API controllers for autosave / revisions should be registered before/after the post type controller.menu_positionintThe position in the menu order the post type should appear. To work, $show_in_menu must be true. Default null (at the bottom).menu_iconstringThe URL to the icon to be used for this menu. Pass a base64-encoded SVG using a data URI, which will be colored to match the color scheme — this should begin with'data:image/svg+xml;base64,'. Pass the name of a Dashicons helper class to use a font icon, e.g.
'dashicons-chart-pie'. Pass'none'to leave div.wp-menu-image empty so an icon can be added via CSS. Defaults to use the posts icon.capability_typestring|arrayThe string to use to build the read, edit, and delete capabilities.
May be passed as an array to allow for alternative plurals when using this argument as a base to construct the capabilities, e.g.
array('story','stories'). Default'post'.capabilitiesstring[]Array of capabilities for this post type. $capability_type is used as a base to construct capabilities by default.
See get_post_type_capabilities() .map_meta_capboolWhether to use the internal default meta capability handling.
Default false.supportsarray|falseCore feature(s) the post type supports. Serves as an alias for calling add_post_type_support() directly. Core features include'title','editor','comments','revisions','trackbacks','author','excerpt','page-attributes','thumbnail','custom-fields', and'post-formats'.
Additionally, the'revisions'feature dictates whether the post type will store revisions, the'autosave'feature dictates whether the post type will be autosaved, and the'comments'feature dictates whether the comments count will show on the edit screen. For backward compatibility reasons, adding'editor'support implies'autosave'support too. A feature can also be specified as an array of arguments to provide additional information about supporting that feature.
Example:array( 'my_feature', array( 'field' => 'value' ) ).
If false, no features will be added.
Default is an array containing'title'and'editor'.register_meta_box_cbcallableProvide a callback function that sets up the meta boxes for the edit form. Do remove_meta_box() and add_meta_box() calls in the callback. Default null.taxonomiesstring[]An array of taxonomy identifiers that will be registered for the post type. Taxonomies can be registered later with register_taxonomy() or register_taxonomy_for_object_type() .has_archivebool|stringWhether there should be post type archives, or if a string, the archive slug to use. Will generate the proper rewrite rules if $rewrite is enabled. Default false.rewritebool|arrayTriggers the handling of rewrites for this post type. To prevent rewrite, set to false.
Defaults to true, using $post_type as slug. To specify rewrite rules, an array can be passed with any of these keys:slugstringCustomize the permastruct slug. Defaults to $post_type key.with_frontboolWhether the permastruct should be prepended with WP_Rewrite::$front.
Default true.feedsboolWhether the feed permastruct should be built for this post type.
Default is value of $has_archive.pagesboolWhether the permastruct should provide for pagination. Default true.ep_maskintEndpoint mask to assign. If not specified and permalink_epmask is set, inherits from $permalink_epmask. If not specified and permalink_epmask is not set, defaults to EP_PERMALINK.query_varstring|boolSets the query_var key for this post type. Defaults to $post_type key. If false, a post type cannot be loaded at ?{query_var}={post_slug}. If specified as a string, the query ?{query_var_string}={post_slug} will be valid.can_exportboolWhether to allow this post type to be exported. Default true.delete_with_userboolWhether to delete posts of this type when deleting a user.- If true, posts of this type belonging to the user will be moved to Trash when the user is deleted.
- If false, posts of this type belonging to the user will *not* be trashed or deleted.
- If not set (the default), posts are trashed if post type supports the
'author'feature. Otherwise posts are not trashed or deleted.
Default null.
templatearrayArray of blocks to use as the default initial state for an editor session. Each item should be an array containing block name and optional attributes.template_lockstring|falseWhether the block template should be locked if $template is set.- If set to
'all', the user is unable to insert new blocks, move existing blocks and delete blocks. - If set to
'insert', the user is able to move existing blocks but is unable to insert new blocks and delete blocks.
Default false.
- If set to
_builtinboolFOR INTERNAL USE ONLY! True if this post type is a native or “built-in” post_type. Default false._edit_linkstringFOR INTERNAL USE ONLY! URL segment to use for edit link of this post type. Default'post.php?post=%d'.
More Arguments from get_post_type_capabilities( … $args )
Post type registration arguments.
Default:
array()Source
function register_post_type( $post_type, $args = array() ) { global $wp_post_types; if ( ! is_array( $wp_post_types ) ) { $wp_post_types = array(); } // Sanitize post type name. $post_type = sanitize_key( $post_type ); if ( empty( $post_type ) || strlen( $post_type ) > 20 ) { _doing_it_wrong( __FUNCTION__, __( 'Post type names must be between 1 and 20 characters in length.' ), '4.2.0' ); return new WP_Error( 'post_type_length_invalid', __( 'Post type names must be between 1 and 20 characters in length.' ) ); } $post_type_object = new WP_Post_Type( $post_type, $args ); $post_type_object->add_supports(); $post_type_object->add_rewrite_rules(); $post_type_object->register_meta_boxes(); $wp_post_types[ $post_type ] = $post_type_object; $post_type_object->add_hooks(); $post_type_object->register_taxonomies(); /** * Fires after a post type is registered. * * @since 3.3.0 * @since 4.6.0 Converted the `$post_type` parameter to accept a `WP_Post_Type` object. * * @param string $post_type Post type. * @param WP_Post_Type $post_type_object Arguments used to register the post type. */ do_action( 'registered_post_type', $post_type, $post_type_object ); /** * Fires after a specific post type is registered. * * The dynamic portion of the filter name, `$post_type`, refers to the post type key. * * Possible hook names include: * * - `registered_post_type_post` * - `registered_post_type_page` * * @since 6.0.0 * * @param string $post_type Post type. * @param WP_Post_Type $post_type_object Arguments used to register the post type. */ do_action( "registered_post_type_{$post_type}", $post_type, $post_type_object ); return $post_type_object; }Hooks
- do_action( ‘registered_post_type’, string $post_type, WP_Post_Type $post_type_object )
-
Fires after a post type is registered.
- do_action( “registered_post_type_{$post_type}”, string $post_type, WP_Post_Type $post_type_object )
-
Fires after a specific post type is registered.
Changelog
Version Description 5.9.0 The rest_namespaceargument was added.5.3.0 The supportsargument will now accept an array of arguments for a feature.5.0.0 The templateandtemplate_lockarguments were added.4.7.0 Introduced show_in_rest,rest_baseandrest_controller_classarguments to register the post type in REST API.4.6.0 Post type object returned is now an instance of WP_Post_Type.4.4.0 The show_uiargument is now enforced on the post type listing screen and post editing screen.3.0.0 The show_uiargument is now enforced on the new post screen.2.9.0 Introduced. User Contributed Notes
You must log in before being able to contribute a note or feedback.
Skip to note 33 content
Codex
Register a ‘book’ post type, using new labels introduced in 4.3 and 4.4.
/** * Register a custom post type called "book". * * @see get_post_type_labels() for label keys. */ function wpdocs_codex_book_init() { $labels = array( 'name' => _x( 'Books', 'Post type general name', 'textdomain' ), 'singular_name' => _x( 'Book', 'Post type singular name', 'textdomain' ), 'menu_name' => _x( 'Books', 'Admin Menu text', 'textdomain' ), 'name_admin_bar' => _x( 'Book', 'Add New on Toolbar', 'textdomain' ), 'add_new' => __( 'Add New', 'textdomain' ), 'add_new_item' => __( 'Add New Book', 'textdomain' ), 'new_item' => __( 'New Book', 'textdomain' ), 'edit_item' => __( 'Edit Book', 'textdomain' ), 'view_item' => __( 'View Book', 'textdomain' ), 'all_items' => __( 'All Books', 'textdomain' ), 'search_items' => __( 'Search Books', 'textdomain' ), 'parent_item_colon' => __( 'Parent Books:', 'textdomain' ), 'not_found' => __( 'No books found.', 'textdomain' ), 'not_found_in_trash' => __( 'No books found in Trash.', 'textdomain' ), 'featured_image' => _x( 'Book Cover Image', 'Overrides the “Featured Image” phrase for this post type. Added in 4.3', 'textdomain' ), 'set_featured_image' => _x( 'Set cover image', 'Overrides the “Set featured image” phrase for this post type. Added in 4.3', 'textdomain' ), 'remove_featured_image' => _x( 'Remove cover image', 'Overrides the “Remove featured image” phrase for this post type. Added in 4.3', 'textdomain' ), 'use_featured_image' => _x( 'Use as cover image', 'Overrides the “Use as featured image” phrase for this post type. Added in 4.3', 'textdomain' ), 'archives' => _x( 'Book archives', 'The post type archive label used in nav menus. Default “Post Archives”. Added in 4.4', 'textdomain' ), 'insert_into_item' => _x( 'Insert into book', 'Overrides the “Insert into post”/”Insert into page” phrase (used when inserting media into a post). Added in 4.4', 'textdomain' ), 'uploaded_to_this_item' => _x( 'Uploaded to this book', 'Overrides the “Uploaded to this post”/”Uploaded to this page” phrase (used when viewing media attached to a post). Added in 4.4', 'textdomain' ), 'filter_items_list' => _x( 'Filter books list', 'Screen reader text for the filter links heading on the post type listing screen. Default “Filter posts list”/”Filter pages list”. Added in 4.4', 'textdomain' ), 'items_list_navigation' => _x( 'Books list navigation', 'Screen reader text for the pagination heading on the post type listing screen. Default “Posts list navigation”/”Pages list navigation”. Added in 4.4', 'textdomain' ), 'items_list' => _x( 'Books list', 'Screen reader text for the items list heading on the post type listing screen. Default “Posts list”/”Pages list”. Added in 4.4', 'textdomain' ), ); $args = array( 'labels' => $labels, 'public' => true, 'publicly_queryable' => true, 'show_ui' => true, 'show_in_menu' => true, 'query_var' => true, 'rewrite' => array( 'slug' => 'book' ), 'capability_type' => 'post', 'has_archive' => true, 'hierarchical' => false, 'menu_position' => null, 'supports' => array( 'title', 'editor', 'author', 'thumbnail', 'excerpt', 'comments' ), ); register_post_type( 'book', $args ); } add_action( 'init', 'wpdocs_codex_book_init' );Skip to note 34 content
Shashikant Yadav
To add Gutenberg compatibility in your custom post type, it require two things
1.
supportsmust haveeditorin it2.
show_in_restset to truefunction wpdocs_kantbtrue_init() { $labels = array( 'name' => _x( 'Recipes', 'Post type general name', 'recipe' ), 'singular_name' => _x( 'Recipe', 'Post type singular name', 'recipe' ), 'menu_name' => _x( 'Recipes', 'Admin Menu text', 'recipe' ), 'name_admin_bar' => _x( 'Recipe', 'Add New on Toolbar', 'recipe' ), 'add_new' => __( 'Add New', 'recipe' ), 'add_new_item' => __( 'Add New recipe', 'recipe' ), 'new_item' => __( 'New recipe', 'recipe' ), 'edit_item' => __( 'Edit recipe', 'recipe' ), 'view_item' => __( 'View recipe', 'recipe' ), 'all_items' => __( 'All recipes', 'recipe' ), 'search_items' => __( 'Search recipes', 'recipe' ), 'parent_item_colon' => __( 'Parent recipes:', 'recipe' ), 'not_found' => __( 'No recipes found.', 'recipe' ), 'not_found_in_trash' => __( 'No recipes found in Trash.', 'recipe' ), 'featured_image' => _x( 'Recipe Cover Image', 'Overrides the “Featured Image” phrase for this post type. Added in 4.3', 'recipe' ), 'set_featured_image' => _x( 'Set cover image', 'Overrides the “Set featured image” phrase for this post type. Added in 4.3', 'recipe' ), 'remove_featured_image' => _x( 'Remove cover image', 'Overrides the “Remove featured image” phrase for this post type. Added in 4.3', 'recipe' ), 'use_featured_image' => _x( 'Use as cover image', 'Overrides the “Use as featured image” phrase for this post type. Added in 4.3', 'recipe' ), 'archives' => _x( 'Recipe archives', 'The post type archive label used in nav menus. Default “Post Archives”. Added in 4.4', 'recipe' ), 'insert_into_item' => _x( 'Insert into recipe', 'Overrides the “Insert into post”/”Insert into page” phrase (used when inserting media into a post). Added in 4.4', 'recipe' ), 'uploaded_to_this_item' => _x( 'Uploaded to this recipe', 'Overrides the “Uploaded to this post”/”Uploaded to this page” phrase (used when viewing media attached to a post). Added in 4.4', 'recipe' ), 'filter_items_list' => _x( 'Filter recipes list', 'Screen reader text for the filter links heading on the post type listing screen. Default “Filter posts list”/”Filter pages list”. Added in 4.4', 'recipe' ), 'items_list_navigation' => _x( 'Recipes list navigation', 'Screen reader text for the pagination heading on the post type listing screen. Default “Posts list navigation”/”Pages list navigation”. Added in 4.4', 'recipe' ), 'items_list' => _x( 'Recipes list', 'Screen reader text for the items list heading on the post type listing screen. Default “Posts list”/”Pages list”. Added in 4.4', 'recipe' ), ); $args = array( 'labels' => $labels, 'description' => 'Recipe custom post type.', 'public' => true, 'publicly_queryable' => true, 'show_ui' => true, 'show_in_menu' => true, 'query_var' => true, 'rewrite' => array( 'slug' => 'recipe' ), 'capability_type' => 'post', 'has_archive' => true, 'hierarchical' => false, 'menu_position' => 20, 'supports' => array( 'title', 'editor', 'author', 'thumbnail' ), 'taxonomies' => array( 'category', 'post_tag' ), 'show_in_rest' => true ); register_post_type( 'Recipe', $args ); } add_action( 'init', 'wpdocs_kantbtrue_init' );Skip to note 35 content
John Antonacci
use
'show_in_rest' => true,to enable blocks in your custom post type.
Skip to note 36 content
eatbuildplay
This documentation is currently missing the “template” and “template_lock” arguments which I’ve seen used in some projects. The purpose of which is to automatically load a certain block or block template into the Gutenberg editor, or to lock the usage of blocks.
The documentation for these arguments can be found in this doc section about Gutenberg blocks at Block Editor Handbook > Templates
textorcontentattributes to set default content in a block template.Skip to note 37 content
Codex
Customize the post update messages of the ‘book’ custom post type:
/** * Book-specific update messages. * * @see /wp-admin/edit-form-advanced.php * * @param array $messages Existing post update messages. * @return array Amended post update messages with new CPT update messages. */ function wpdocs_codex_book_updated_messages( $messages ) { $post = get_post(); $post_type = get_post_type( $post ); $post_type_object = get_post_type_object( $post_type ); $messages['book'] = array( 0 => '', // Unused. Messages start at index 1. 1 => __( 'Book updated.', 'textdomain' ), 2 => __( 'Custom field updated.', 'textdomain' ), 3 => __( 'Custom field deleted.', 'textdomain' ), 4 => __( 'Book updated.', 'textdomain' ), /* translators: %s: date and time of the revision */ 5 => isset( $_GET['revision'] ) ? sprintf( __( 'Book restored to revision from %s', 'textdomain' ), wp_post_revision_title( (int) $_GET['revision'], false ) ) : false, 6 => __( 'Book published.', 'textdomain' ), 7 => __( 'Book saved.', 'textdomain' ), 8 => __( 'Book submitted.', 'textdomain' ), 9 => sprintf( __( 'Book scheduled for: <strong>%1$s</strong>.', 'textdomain' ), // translators: Publish box date format, see <a href="http://php.net/date" rel="nofollow ugc">http://php.net/date</a> date_i18n( __( 'M j, Y @ G:i', 'textdomain' ), strtotime( $post->post_date ) ) ), 10 => __( 'Book draft updated.', 'textdomain' ), ); if ( $post_type_object->publicly_queryable ) { $permalink = get_permalink( $post->ID ); $view_link = sprintf( ' <a href="%s">%s</a>', esc_url( $permalink ), __( 'View book', 'textdomain' ) ); $messages['book'][1] .= $view_link; $messages['book'][6] .= $view_link; $messages['book'][9] .= $view_link; $preview_permalink = add_query_arg( 'preview', 'true', $permalink ); $preview_link = sprintf( '<a target="_blank" href="%s">%s</a>', esc_url( $preview_permalink ), __( 'Preview book', 'textdomain' ) ); $messages[ $post_type ][8] .= $preview_link; $messages[ $post_type ][10] .= $preview_link; } return $messages; } add_filter( 'post_updated_messages', 'wpdocs_codex_book_updated_messages' );Skip to note 38 content
Anonymous User
As of v6.1 – this gives more of a full comprehensive boilerplate to copy and adjust to your needs (and in order from documentation) – which includes labels – so that I can then easily do a find and replace to match the wording that I need when registering a new CPT.
add_action( 'init', 'wpdocs_register_announcement_cpt' ); function wpdocs_register_announcement_cpt() { $labels = array( 'name' => __( 'Announcements', 'TEXTDOMAINHERE' ), 'singular_name' => __( 'Announcement', 'TEXTDOMAINHERE' ), 'add_new' => __( 'Add New', 'TEXTDOMAINHERE' ), 'add_new_item' => __( 'Add New Announcement', 'TEXTDOMAINHERE' ), 'edit_item' => __( 'Edit Announcement', 'TEXTDOMAINHERE' ), 'new_item' => __( 'New Announcement', 'TEXTDOMAINHERE' ), 'view_item' => __( 'View Announcement', 'TEXTDOMAINHERE' ), 'view_items' => __( 'View Announcements', 'TEXTDOMAINHERE' ), 'search_items' => __( 'Search Announcements', 'TEXTDOMAINHERE' ), 'not_found' => __( 'No Announcements found.', 'TEXTDOMAINHERE' ), 'not_found_in_trash' => __( 'No Announcements found in Trash.', 'TEXTDOMAINHERE' ), 'parent_item_colon' => __( 'Parent Announcements:', 'TEXTDOMAINHERE' ), 'all_items' => __( 'All Announcements', 'TEXTDOMAINHERE' ), 'archives' => __( 'Announcement Archives', 'TEXTDOMAINHERE' ), 'attributes' => __( 'Announcement Attributes', 'TEXTDOMAINHERE' ), 'insert_into_item' => __( 'Insert into Announcement', 'TEXTDOMAINHERE' ), 'uploaded_to_this_item' => __( 'Uploaded to this Announcement', 'TEXTDOMAINHERE' ), 'featured_image' => __( 'Featured Image', 'TEXTDOMAINHERE' ), 'set_featured_image' => __( 'Set featured image', 'TEXTDOMAINHERE' ), 'remove_featured_image' => __( 'Remove featured image', 'TEXTDOMAINHERE' ), 'use_featured_image' => __( 'Use as featured image', 'TEXTDOMAINHERE' ), 'menu_name' => __( 'Announcements', 'TEXTDOMAINHERE' ), 'filter_items_list' => __( 'Filter Announcement list', 'TEXTDOMAINHERE' ), 'filter_by_date' => __( 'Filter by date', 'TEXTDOMAINHERE' ), 'items_list_navigation' => __( 'Announcements list navigation', 'TEXTDOMAINHERE' ), 'items_list' => __( 'Announcements list', 'TEXTDOMAINHERE' ), 'item_published' => __( 'Announcement published.', 'TEXTDOMAINHERE' ), 'item_published_privately' => __( 'Announcement published privately.', 'TEXTDOMAINHERE' ), 'item_reverted_to_draft' => __( 'Announcement reverted to draft.', 'TEXTDOMAINHERE' ), 'item_scheduled' => __( 'Announcement scheduled.', 'TEXTDOMAINHERE' ), 'item_updated' => __( 'Announcement updated.', 'TEXTDOMAINHERE' ), 'item_link' => __( 'Announcement Link', 'TEXTDOMAINHERE' ), 'item_link_description' => __( 'A link to an announcement.', 'TEXTDOMAINHERE' ), ); $args = array( 'labels' => $labels, 'description' => __( 'organize and manage company announcements', 'TEXTDOMAINHERE' ), 'public' => false, 'hierarchical' => false, 'exclude_from_search' => true, 'publicly_queryable' => false, 'show_ui' => true, 'show_in_menu' => true, 'show_in_nav_menus' => false, 'show_in_admin_bar' => false, 'show_in_rest' => true, 'menu_position' => null, 'menu_icon' => 'dashicons-megaphone', 'capability_type' => 'post', 'capabilities' => array(), 'supports' => array( 'title', 'editor', 'revisions' ), 'taxonomies' => array(), 'has_archive' => false, 'rewrite' => array( 'slug' => 'cpar_announcement' ), 'query_var' => true, 'can_export' => true, 'delete_with_user' => false, 'template' => array(), 'template_lock' => false, ); register_post_type( 'wpdocs_register_announcement_cpt', $args ); }register_post_type(“wpdocs_register_announcement_cpt”) must not exceed 20 characters.Skip to note 39 content
Christina Blust
Using Dashicons for a custom menu icon
To use one of the existing Dashicons for your custom post type in the menu (instead of the push-pin default), go to Developer Resources: Dashicons and click on your favorite icon. The class name will show up top — just copy and use. So for, instance, for your custom post type “Book,” you might use
dashicons-book.function book_setup_post_type() { $args = array( 'public' => true, 'label' => __( 'Books', 'textdomain' ), 'menu_icon' => 'dashicons-book', ); register_post_type( 'book', $args ); } add_action( 'init', 'book_setup_post_type' );Skip to note 40 content
Braad
As of WordPress 4.3 and 4.4, a number of new labels have been added.
Introduced in 4.3:
featured_image– defaults to “Featured Image”set_featured_image– defaults to “Set featured image”remove_featured_image– defaults to “Remove featured image”use_featured_image– defaults to “Use as featured image”Introduced in 4.4:
archives– defaults to “Post Archives” or “Page Archives”insert_into_item– defaults to “Insert into post” or “Insert into page”uploaded_to_this_item– defaults to “Uploaded to this post” or “Uploaded to this page”filter_items_list– defaults to “Filter posts list” or “Filter pages list”items_list_navigation– defaults to “Posts list navigation” or “Pages list navigation”items_list– defaults to “Posts list” or “Pages list”See https://make.wordpress.org/core/2015/12/11/additional-labels-for-custom-post-types-and-custom-taxonomies/ for more information.
Skip to note 41 content
Kevin Hoffman
The following guidelines ensure consistency with the default post types and help to avoid conflict with post types registered by other developers.
$post_typeto a singular noun (e.g.testimonial,portfolio,event). Remember you can always use the plural form for other parameters that affect the labels and rewrite slug.sports_team,video_game).kwh_testimonial).Skip to note 42 content
Jan-Willem
To use an svg which colors are always correct embed it inline like:
register_post_type('labs', [ // ect 'menu_icon' => 'data:image/svg+xml;base64,' . base64_encode('<svg width="20" height="20" viewBox="0 0 1792 1792" xmlns="<a href="http://www.w3.org/2000/svg"><path" rel="nofollow ugc">http://www.w3.org/2000/svg"><path</a> fill="black" d="M1591 1448q56 89 21.5 152.5t-140.5 63.5h-1152q-106 0-140.5-63.5t21.5-152.5l503-793v-399h-64q-26 0-45-19t-19-45 19-45 45-19h512q26 0 45 19t19 45-19 45-45 19h-64v399zm-779-725l-272 429h712l-272-429-20-31v-436h-128v436z"/></svg>') ]);The
fill="black"is important.Source: https://stackoverflow.com/a/42265057/933065
Skip to note 43 content
marale
This function doesn’t check if
key is unique. If the post type with existing key is registered it overwrites
global array element without calling
function. It still resides in memory, rewrite rules, and hooks are not removed. Function
doesn’t allow to unregister builtin post types, but
allows to register them more than once.
Skip to note 44 content
souri
Post type as submenu is possible.
You can put a custom post type in the menu of another CPT!
The documentation isn’t that obvious!
But it is perfectly possible to make a post type a submenu of another post type!
The option “show_in_menu” is the key to accomplish that. (Other than the suggested menu_position parameter)
You only have to give “show_in_menu” this parameter: “edit.php?post_type=page”
Where “page” could be a built in post type or any post type you create!
Here is an example code snippet
$books_args = array( 'label' => __( 'Books', 'textdomain' ), 'show_in_menu' => 'edit.php?post_type=page', ); register_post_type( 'book', $books_args );Skip to note 45 content
Joseph Paul
Never create a custom post type “author”
The archive page and posts pages will conflict with wordpress’ builtin
/author/{username}routes.Even if you
'rewrite' => ['slug' => 'writer'], there will be some confusion and your custom author posts will not be accessible.Skip to note 46 content
Codex
This example adds WordPress 3.3+ Help Tab to the ‘book’ post type.
add_action( 'admin_head', 'wpdocs_codex_custom_help_tab' ); /** * Add Help Tab to Book post type. */ function wpdocs_codex_custom_help_tab() { $screen = get_current_screen(); // Return early if we're not on the book post type. if ( 'book' != $screen->post_type ) { return; } // Setup help tab args. $args = array( 'id' => 'your_custom_id', // Unique id for the tab. 'title' => __( 'Custom Help', 'textdomain' ), // Unique visible title for the tab. 'content' => '<h3>Help Title</h3><p>Help content</p>', // Actual help text. ); // Add the help tab. $screen->add_help_tab( $args ); }Skip to note 47 content
NateWr
When using a base64-encoded SVG for the
menu_iconargument, your SVG must have afillattribute. Without thefillattribute, WordPress won’t be able to match the admin color scheme.Skip to note 48 content
Andrei Surdu
These are the built-in post types:
'post', 'page', 'attachment', 'revision', 'nav_menu_item', 'custom_css', 'customize_changeset', 'oembed_cache', 'user_request',If you want to view the info for all built-in and or custom post types, you can get it from
$wp_post_typesglobal variable.Example:
add_action( 'admin_notices', function () { global $wp_post_types; var_dump( $wp_post_types ); });wp_blockSkip to note 49 content
西門 正 Code Guy
The argument ‘with_front’ in old Codex is more clearly explained with example.
‘with_front’ => bool Should the permalink structure be prepended with the front base.
(example: if your permalink structure is /blog/, then your links will be: false->/news/, true->/blog/news/). Defaults to true
Skip to note 50 content
Drew Jaynes
This basic example registers a ‘book’ post type.
/** * Register a book post type. */ function wpdocs_codex_custom_init() { $args = array( 'public' => true, 'label' => __( 'Books', 'textdomain' ), ); register_post_type( 'book', $args ); } add_action( 'init', 'wpdocs_codex_custom_init' );Skip to note 51 content
Codex
This is example adds contextual help to the ‘book’ post type.
add_action( 'contextual_help', 'wpdocs_codex_add_help_text', 10, 3 ); /** * Display contextual help for the Book post type */ function wpdocs_codex_add_help_text( $contextual_help, $screen_id, $screen ) { // $contextual_help .= var_dump( $screen ); // use this to help determine $screen->id if ( 'book' == $screen->id ) { $contextual_help = '<p>' . __('Things to remember when adding or editing a book:', 'your_text_domain') . '</p>' . '<ul>' . '<li>' . __('Specify the correct genre such as Mystery, or Historic.', 'your_text_domain') . '</li>' . '<li>' . __('Specify the correct writer of the book. Remember that the Author module refers to you, the author of this book review.', 'your_text_domain') . '</li>' . '</ul>' . '<p>' . __('If you want to schedule the book review to be published in the future:', 'your_text_domain') . '</p>' . '<ul>' . '<li>' . __('Under the Publish module, click on the Edit link next to Publish.', 'your_text_domain') . '</li>' . '<li>' . __('Change the date to the date to actual publish this article, then click on Ok.', 'your_text_domain') . '</li>' . '</ul>' . '<p><strong>' . __('For more information:', 'your_text_domain') . '</strong></p>' . '<p>' . __('<a href="<a href="https://codex.wordpress.org/Posts_Edit_SubPanel"" rel="nofollow ugc">https://codex.wordpress.org/Posts_Edit_SubPanel"</a>; target="_blank">Edit Posts Documentation</a>', 'your_text_domain') . '</p>' . '<p>' . __('<a href="<a href="https://wordpress.org/support/"" rel="nofollow ugc">https://wordpress.org/support/"</a>; target="_blank">Support Forums</a>', 'your_text_domain') . '</p>' ; } elseif ( 'edit-book' == $screen->id ) { $contextual_help = '<p>' . __('This is the help screen displaying the table of books blah blah blah.', 'your_text_domain') . '</p>'; } return $contextual_help; }Skip to note 52 content
eddr
about hierarchical post type performance issues note : ” With this parameter set to true WordPress will fetch all IDs of that particular post type on each administration page load for your post type. ”
The phrasing is a little bit confusing. WP will fetch all the child IDs of this particular post of this post type and not “all IDs” of “the post type” which is a term describing the type of a post and not a specific post.
Skip to note 53 content
mumbomedia
There is a mistake in this document.
The param supports also accept the boolean value false.
When supports is set to false the post type don’t use any of the build in feature like editor.
Only the submit metabox is automatically registered.
So I would really appreticate if someone could update the info so other developers could benefit from my research.
Skip to note 54 content
Hay
If for some reason your pages with a custom post type give a 404 on the frontend, you might need to flush your rewrite rules. You can do this manually by going to Settings –> Permalinks and hitting ‘Save Changes’ or by calling
flush_rewrite_rules(). Note that you shouldn’t callflush_rewrite_rules()because it’s expensive. Only run it on plugin activation. See the section above.Skip to note 55 content
Jilani Ahmed
A specific issue I would like to address here for beginners, about the Custom Post Type (CPT) parent menu and immediate child menu label.
For example –
Your CPT main menu label is “Videos” As a default the immediate child menu label will show also “Videos” but you don’t want it. You just want, the parent menu label “Videos” and the immediate child menu label “All Videos”. For that case, you should use ‘all_items’ => ‘All Videos’
Complete code below –
function wpdocs_video_init() { register_post_type( 'videos', array( 'public' => true, 'labels' => array( 'name' => esc_html__( 'Videos', 'textdomain' ), 'all_items' => esc_html__( 'All Videos', 'textdomain' ), 'singular_name' => esc_html__( 'Video', 'textdomain' ) ), ) ); } add_action( 'init', 'wpdocs_video_init' );Skip to note 56 content
Julio Potier
Note that although the
$publicattribute is optional, the inputs passed to theregister_post_type()function are exactly what is queried by theget_post_types()function. So if you verbosely set the equivalent options forpublicly_queriable,show_ui,show_in_nav_menus, andexclude_from_search, this will not be handled the same as if you had set the$publicattribute. See bug https://core.trac.wordpress.org/ticket/18950Skip to note 57 content
janthiel
A note regarding the usage of
show_in_menuwith a string value: As described in the docs this will add your Custom Post Type (CPT) menu items as submenu items to any other existing top menu item.show_in_menu => 'my-custom-menu' ... capability_type => 'post'Independent of the given
capability_typeof your CPT (usuallypostorpage) the access to the edit page (/wp-admin/edit.php?post_type=my-custom-post-type) of your CPT will be as well determined by the capability set inadd_menu_pageofmy-custom-menu.Example:
If your
add_menu_pageregisters with a capability ofmanage_optionsand your CPT will usepostascapability_typeany user will see the top level and submenu items if they have theedit_postscap. But if they don’t have themanage_optionscap they cannot access the CPT edit view as the cap check resolves tomanage_optionsof the top level menu page instead ofedit_postsas one might expect.Refer to
user_can_access_admin_pagein /wp-admin/includes/plugin.phpThis scenario will trigger this code block in there:
foreach ( $menu as $menu_array ) { if ( $menu_array[2] === $parent ) { return current_user_can( $menu_array[1] ); } }Skip to note 58 content
webaware
Custom post types with
publicly_queryableset to false don’t expose the post slug for editing. However,supportshas a hidden attribute ofslugwhich does expose the post slug field on the editor (but not for quick edit).Skip to note 59 content
Riccardo
To have the Permalink and the Permalink Edit button displayed under the Post Title in the Edit Post screen, you must set the
publicparameter totrueand, if specified, thepublicly_queryableandrewriteparameters must not be set tofalse.Skip to note 60 content
cyclonecode
I was extending a post type when I ran into a problem related to the
register_meta_box_cbargument. I needed to add my own callback, but also make sure so that the original (if any) callback was still fired. I think this is pretty undocumented but you can actually add an array with callbacks for this argument e.g:$post_type = (array) get_post_type_object( 'POST_TYPE_TO_EXTEND' ); $args = array( // Assume this code is held inside a class. 'register_meta_box_cb' => array( $this, 'addMetaBoxes' ), ); if ( isset( $post_type['register_meta_box_cb'] ) ) { $post_type['register_meta_box_cb'] = array_merge( array( $post_type['register_meta_box_cb'] ), array( $args['register_meta_box_cb'] ) ); } $args = array_merge( $post_type, $args ); // Now both the original posts meta box callback and our custom one will be called. register_post_type( 'custom_type', $args );Skip to note 61 content
filatovdanyl
Note:
show_in_menuisn’t required. If you want to add the CPT “posts list” page as a custom submenu item to some top-level menu item in wp admin, you can just use theadd_submenu_page(), with$menu_slugparameter as the post list url, and'show_in_menu' => false`:function wporg_cp_popups_list_submenu() {
add_submenu_page( ‘convert-pro’, ‘Popups Posts’, ‘Popups Posts’, ‘access_cp_pro’, ‘edit.php?post_type=cp_popups’ );
}
add_action( ‘admin_menu’, ‘wporg_cp_popups_list_submenu’, 100 );
Credit: ApsaraAruna / https://wordpress.stackexchange.com/a/411326/166795
Skip to note 62 content
Akira Tachibana
Note: I am posting this on behalf of @rollybueno. See https://github.com/WordPress/Documentation-Issue-Tracker/issues/1989.
To customize permalinks for custom post types, you need to define the rewrite argument when registering the post type using
register_post_type(). This allows you to change the slug of your CPT. For example, if you’re registering a custom post type called book, you can set'rewrite' => array('slug' => 'library', 'with_front' => false)to make it likehttps://yoursite.com/library/book-title.function register_book_post_type() { $labels = array( 'name' => 'Books', 'singular_name' => 'Book', 'add_new_item' => 'Add New Book', 'edit_item' => 'Edit Book', 'new_item' => 'New Book', 'all_items' => 'All Books', 'menu_name' => 'Books' ); $args = array( 'labels' => $labels, 'public' => true, 'has_archive' => true, 'rewrite' => array( 'slug' => 'library', 'with_front' => false ), 'supports' => array( 'title', 'editor', 'author' ), 'show_in_rest' => true, ); register_post_type( 'book', $args ); } add_action( 'init', 'register_book_post_type' );After updating this, it’s essential to flush rewrite rules either by visiting Settings → Permalinks and clicking “Save Changes” or programmatically using
flush_rewrite_rules()on plugin activation.Skip to note 63 content
tripflex
To use a custom SVG in
menu_iconfind or create the SVG you want to use, and then you can use a site like https://www.base64-image.de/ to convert it to base64 code.In order for the SVG icon to match the admin color scheme, it must have the
fillattribute set in anypathelements in the SVG file.Edit the SVG file with an editor and add fill attribute to any `path` elements before converting to base64.
should be
<path fill="#9da3a8" d="...You can use any color you want, including using
noneinstead of a color, as WordPress will automatically update it to match the color scheme.https://css-tricks.com/almanac/properties/f/fill/
Skip to note 64 content
Bronson Quick
When you register a new Custom Post Type I’d highly recommend setting
with_front => trueas it’s false by default in core. This change to your CPT registration will be helpful if a user/client ever changes their permalinks to/blog/%category%/%postname%/because all your registered CPTs will have/blog/your-cpt