register_post_type( string $post_type, array|string $args = array() ): WP_Post_Type|WP_Error

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.
  • label string
    Name of the post type shown in the menu. Usually plural.
    Default is value of $labels['name'].
  • labels string[]
    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.
  • description string
    A short descriptive summary of what the post type is.
  • public bool
    Whether 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.
  • hierarchical bool
    Whether the post type is hierarchical (e.g. page). Default false.
  • exclude_from_search bool
    Whether to exclude posts with this post type from front end search results. Default is the opposite value of $public.
  • publicly_queryable bool
    Whether 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_ui bool
    Whether to generate and allow a UI for managing this post type in the admin. Default is value of $public.
  • show_in_menu bool|string
    Where 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_menus bool
    Makes this post type available for selection in navigation menus.
    Default is value of $public.
  • show_in_admin_bar bool
    Makes this post type available via the admin bar. Default is value of $show_in_menu.
  • show_in_rest bool
    Whether 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_base string
    To change the base URL of REST API route. Default is $post_type.
  • rest_namespace string
    To change the namespace URL of REST API route. Default is wp/v2.
  • rest_controller_class string
    REST API controller class name. Default is ‘WP_REST_Posts_Controller‘.
  • autosave_rest_controller_class string|bool
    REST API controller class name. Default is ‘WP_REST_Autosaves_Controller‘.
  • revisions_rest_controller_class string|bool
    REST API controller class name. Default is ‘WP_REST_Revisions_Controller‘.
  • late_route_registration bool
    A flag to direct the REST API controllers for autosave / revisions should be registered before/after the post type controller.
  • menu_position int
    The position in the menu order the post type should appear. To work, $show_in_menu must be true. Default null (at the bottom).
  • menu_icon string
    The 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_type string|array
    The 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'.
  • capabilities string[]
    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_cap bool
    Whether to use the internal default meta capability handling.
    Default false.
  • supports array
    Core 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, and the 'comments' feature dictates whether the comments count will show on the edit screen. 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' ) ).
    Default is an array containing 'title' and 'editor'.
  • register_meta_box_cb callable
    Provide 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.
  • taxonomies string[]
    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_archive bool|string
    Whether 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.
  • rewrite bool|array
    Triggers 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:
    • slug string
      Customize the permastruct slug. Defaults to $post_type key.
    • with_front bool
      Whether the permastruct should be prepended with WP_Rewrite::$front.
      Default true.
    • feeds bool
      Whether the feed permastruct should be built for this post type.
      Default is value of $has_archive.
    • pages bool
      Whether the permastruct should provide for pagination. Default true.
    • ep_mask int
      Endpoint 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_var string|bool
    Sets 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_export bool
    Whether to allow this post type to be exported. Default true.
  • delete_with_user bool
    Whether 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.
  • template array
    Array 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_lock string|false
    Whether 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.
  • _builtin bool
    FOR INTERNAL USE ONLY! True if this post type is a native or "built-in" post_type. Default false.
  • _edit_link string
    FOR 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()

Return

WP_Post_Type|WP_Error The registered post type object on success, WP_Error object on failure.

More Information

You can use this function in themes and plugins. However, if you use it in a theme, your post type will disappear from the admin if a user switches away from your theme. See Must Use Plugins If you want to keep your changes e.g. post type, even if you switch between your themes.

Taxonomies

When registering a post type, always register your taxonomies using the taxonomies argument. If you do not, the taxonomies and post type will not be recognized as connected when using filters such as parse_query or pre_get_posts. This can lead to unexpected results and failures.

Even if you register a taxonomy while creating the post type, you must still explicitly register and define the taxonomy using register_taxonomy().

Reserved Post Types

The following post types are reserved and are already used by WordPress.

  • post
  • page
  • attachment
  • revision
  • nav_menu_item
  • custom_css
  • customize_changeset
  • oembed_cache
  • user_request
  • wp_block
  • wp_global_styles
  • wp_navigation
  • wp_template
  • wp_template_part

In addition, the following post types should not be used as they interfere with other WordPress functions.

  • action
  • author
  • order
  • theme

In general, you should always prefix your post types, or specify a custom `query_var`, to avoid conflicting with existing WordPress query variables.

More information: Post Types.

Parameter detail information

description

(string) (optional) A short descriptive summary of what the post type is.

Default: blank

The only way to read that field is using this code:

$obj = get_post_type_object( 'your_post_type_name' ); 
echo esc_html( $obj->description );

public

(boolean) (optional) Controls how the type is visible to authors (show_in_nav_menus, show_ui) and readers (exclude_from_search, publicly_queryable).

Default: false
  • true’ – Implies exclude_from_search: false, publicly_queryable: true, show_in_nav_menus: true, and show_ui:true. The built-in types attachment, page, and post are similar to this.
  • false’ – Implies exclude_from_search: true, publicly_queryable: false, show_in_nav_menus: false, and show_ui: false. The built-in types nav_menu_item and revision are similar to this. Best used if you’ll provide your own editing and viewing interfaces (or none at all).
If no value is specified for exclude_from_search, publicly_queryable, show_in_nav_menus, or show_ui, they inherit their values from public.

exclude_from_search

(boolean) (importance) Whether to exclude posts with this post type from front end search results.

Default: value of the opposite of public argument
  • ‘true’ – site/?s=search-term will not include posts of this post type.
  • ‘false’ – site/?s=search-term will include posts of this post type.
Note: If you want to show the posts’s list that are associated to taxonomy’s terms, you must set exclude_from_search to false (ie : for call site_domaine/?taxonomy_slug=term_slug or site_domaine/taxonomy_slug/term_slug). If you set to true, on the taxonomy page (ex: taxonomy.php) WordPress will not find your posts and/or pagination will make 404 error…

publicly_queryable

(boolean) (optional) Whether queries can be performed on the front end as part of parse_request().

Default: value of public argument
Note: The queries affected include the following (also initiated when rewrites are handled)

  •  ?post_type={post_type_key}
  •  ?{post_type_key}={single_post_slug}
  •  ?{post_type_query_var}={single_post_slug}
Note: If query_var is empty, null, or a boolean FALSE, WordPress will still attempt to interpret it (4.2.2) and previews/views of your custom post will return 404s.

show_ui

(boolean) (optional) Whether to generate a default UI for managing this post type in the admin.

Default: value of public argument
  • ‘false’ – do not display a user-interface for this post type
  • ‘true’ – display a user-interface (admin panel) for this post type
Note: _built-in post types, such as post and page, are intentionally set to false.

show_in_nav_menus

(boolean) (optional) Whether post_type is available for selection in navigation menus.

Default: value of public argument

show_in_menu

(boolean or string) (optional) Where to show the post type in the admin menu. show_ui must be true.

Default: value of show_ui argument
  • ‘false’ – do not display in the admin menu
  • ‘true’ – display as a top level menu
  • ‘some string’ – If an existing top level page such as ‘tools.php’ or ‘edit.php?post_type=page’, the post type will be placed as a sub menu of that.
Note: When using ‘some string’ to show as a submenu of a menu page created by a plugin, this item will become the first submenu item, and replace the location of the top-level link. If this isn’t desired, the plugin that creates the menu page needs to set the add_action priority for admin_menu to 9 or lower.
Note: As this one inherits its value from show_ui, which inherits its value from public, it seems to be the most reliable property to determine, if a post type is meant to be publicly useable. At least this works for _builtin post types and only gives back post and page.

show_in_admin_bar

(boolean) (optional) Whether to make this post type available in the WordPress admin bar.

Default: value of the show_in_menu argument

menu_position

(integer) (optional) The position in the menu order the post type should appear. show_in_menu must be true.

Default: null – defaults to below Comments
  • 5 – below Posts
  • 10 – below Media
  • 15 – below Links
  • 20 – below Pages
  • 25 – below comments
  • 60 – below first separator
  • 65 – below Plugins
  • 70 – below Users
  • 75 – below Tools
  • 80 – below Settings
  • 100 – below second separator

menu_icon

(string) (optional) The url to the icon to be used for this menu or the name of the icon from the iconfont [1]

Default: null – defaults to the posts icon
Examples

  • ‘dashicons-video-alt’ (Uses the video icon from Dashicons[2])
  • get_template_directory_uri() . “/images/cutom-posttype-icon.png”‘ (Use a image located in the current theme)
  • ‘data:image/svg+xml;base64,’ . base64_encode( “<svg version=”1.1″ xmlns=”http://www.w3.org/2000/svg” xmlns:xlink=”http://www.w3.org/1999/xlink” x=”0px” y=”0px” width=”20px” height=”20px” viewBox=”0 0 459 459″> <path fill=”black” d=”POINTS”/></svg>” )’ (directly embedding a svg with ‘fill=”black”‘ will allow correct colors. Also see [3])

capability_type

(string or array) (optional) The 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’) the first array element will be used for the singular capabilities and the second array element for the plural capabilities, this is instead of the auto generated version if no array is given which would be “storys”. The ‘capability_type’ parameter is used as a base to construct capabilities unless they are explicitly set with the ‘capabilities’ parameter. It seems that `map_meta_cap` needs to be set to false or null, to make this work (see note 2 below).

Default: “post”
Example with “book” or “array( ‘book’, ‘books’ )” value, it will generate the 7 capabilities equal to set capabilities parameter to this :
'capabilities' => array(
  'edit_post'          => 'edit_book', 
  'read_post'          => 'read_book', 
  'delete_post'        => 'delete_book', 
  'edit_posts'         => 'edit_books', 
  'edit_others_posts'  => 'edit_others_books', 
  'publish_posts'      => 'publish_books',       
  'read_private_posts' => 'read_private_books', 
  'create_posts'       => 'edit_books', 
),
Note 1: The “create_posts” capability correspond to “edit_books” so it become equal to “edit_posts”.
Note 2: See capabilities note 2 about meta capabilities mapping for custom post type.
You can take a look into the $GLOBALS['wp_post_types']['your_cpt_name'] array, then you’ll see the following:
[cap] => stdClass Object
	(
		// Meta capabilities
		[edit_post] => edit_book
		[read_post] => read_book
		[delete_post] => delete_book

		// Primitive capabilities used outside of map_meta_cap():
		[edit_posts] => edit_books
		[edit_others_posts] => edit_others_books
		[publish_posts] => publish_books
		[read_private_posts] => read_private_books

		// Primitive capabilities used within map_meta_cap():
		[create_posts] => edit_books
	)
Some of the capability types that can be used (probably not exhaustive list):

  • post (default)
  • page
These built-in types cannot be used:

  • attachment
  • mediapage
Note 3: If you use capabilities parameter, capability_type complete your capabilities.

capabilities

(array) (optional) An array of the capabilities for this post type.

Default: capability_type is used to construct
By default, eight keys are accepted as part of the capabilities array:

  • edit_post, read_post, and delete_post – These three are meta capabilities, which are then generally mapped to corresponding primitive capabilities depending on the context, for example the post being edited/read/deleted and the user or role being checked. Thus these capabilities would generally not be granted directly to users or roles.
  • edit_posts – Controls whether objects of this post type can be edited.
  • edit_others_posts – Controls whether objects of this type owned by other users can be edited. If the post type does not support an author, then this will behave like edit_posts.
  • delete_posts – Controls whether objects of this post type can be deleted.
  • publish_posts – Controls publishing objects of this post type.
  • read_private_posts – Controls whether private objects can be read.
Note: 1.) those last four primitive capabilities are checked in core in various locations. 2.) create_posts is automatically mapped to edit_posts by default.
There are also six other primitive capabilities which are not referenced directly in core, except in map_meta_cap() , which takes the three aforementioned meta capabilities and translates them into one or more primitive capabilities that must then be checked against the user or role, depending on the context. These additional capabilities are only used in map_meta_cap() . Thus, they are only assigned by default if the post type is registered with the ‘map_meta_cap‘ argument set to true (default is false).

  • read – Controls whether objects of this post type can be read.
  • delete_private_posts – Controls whether private objects can be deleted.
  • delete_published_posts – Controls whether published objects can be deleted.
  • delete_others_posts – Controls whether objects owned by other users can be can be deleted. If the post type does not support an author, then this will behave like delete_posts.
  • edit_private_posts – Controls whether private objects can be edited.
  • edit_published_posts – Controls whether published objects can be edited.

If you assign a 'capability_type' and then take a look into the $GLOBALS['wp_post_types']['your_cpt_name'] array, then you’ll see the following:

[cap] => stdClass Object
(
	// Meta capabilities

	[edit_post]		 => "edit_{$capability_type}"
	[read_post]		 => "read_{$capability_type}"
	[delete_post]		 => "delete_{$capability_type}"

	// Primitive capabilities used outside of map_meta_cap():

	[edit_posts]		 => "edit_{$capability_type}s"
	[edit_others_posts]	 => "edit_others_{$capability_type}s"
	[publish_posts]		 => "publish_{$capability_type}s"
	[read_private_posts]	 => "read_private_{$capability_type}s"

	// Primitive capabilities used within map_meta_cap():

	[read]                   => "read",
	[delete_posts]           => "delete_{$capability_type}s"
	[delete_private_posts]   => "delete_private_{$capability_type}s"
	[delete_published_posts] => "delete_published_{$capability_type}s"
	[delete_others_posts]    => "delete_others_{$capability_type}s"
	[edit_private_posts]     => "edit_private_{$capability_type}s"
	[edit_published_posts]   => "edit_published_{$capability_type}s"
	[create_posts]           => "edit_{$capability_type}s"
)

Note the “s” at the end of plural capabilities.

map_meta_cap

(boolean) (optional) Whether to use the internal default meta capability handling.

Default: null
Note: If set it to false then standard admin role can’t edit the posts types. Then the edit_post capability must be added to all roles to add or edit the posts types.

hierarchical

(boolean) (optional) Whether the post type is hierarchical (e.g. page). Allows Parent to be specified. The ‘supports’ parameter should contain ‘page-attributes’ to show the parent select box on the editor page.

Default: false
Note: this parameter was intended for Pages. Be careful when choosing it for your custom post type – if you are planning to have very many entries (say – over 2-3 thousand), you will run into load time issues. 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. Servers with limited memory resources may also be challenged by this parameter being set to true.

supports

(array/boolean) (optional) An alias for calling add_post_type_support() directly. As of 3.5, boolean false can be passed as value instead of an array to prevent default (title and editor) behavior.

Default: title and editor
  • ‘title’
  • ‘editor’ (content)
  • ‘author’
  • ‘thumbnail’ (featured image, current theme must also support post-thumbnails)
  • ‘excerpt’
  • ‘trackbacks’
  • ‘custom-fields’
  • ‘comments’ (also will see comment count balloon on edit screen)
  • ‘revisions’ (will store revisions)
  • ‘page-attributes’ (menu order, hierarchical must be true to show Parent option)
  • ‘post-formats’ add post formats, see Post Formats
Note: When you use custom post type that use thumbnails remember to check that the theme also supports thumbnails or use add_theme_support() function.

register_meta_box_cb

(callback ) (optional) Provide a callback function that will be called when setting up the meta boxes for the edit form. The callback function takes one argument $post, which contains the WP_Post object for the currently edited post. Do remove_meta_box() and add_meta_box() calls in the callback.

Default: None

taxonomies

(array) (optional) An array of registered taxonomies like category or post_tag that will be used with this post type. This can be used in lieu of calling register_taxonomy_for_object_type() directly. Custom taxonomies still need to be registered with register_taxonomy().

Default: no taxonomies

has_archive

(boolean or string) (optional) Enables post type archives. Will use $post_type as archive slug by default.

Default: false
Note: Will generate the proper rewrite rules if rewrite is enabled. Also use rewrite to change the slug used. If string, it should be translatable.

rewrite

(boolean or array) (optional) Triggers the handling of rewrites for this post type. To prevent rewrites, set to false.

Default: true and use $post_type as slug
$args array

  • 'slug' => string Customize the permalink structure slug. Defaults to the $post_type value. Should be translatable.
  • '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
  • 'feeds' => bool Should a feed permalink structure be built for this post type. Defaults to has_archive value.
  • 'pages' => bool Should the permalink structure provide for pagination. Defaults to true
  • 'ep_mask' => const As of 3.4 Assign an endpoint mask for this post type. For more info see Rewrite API/add_rewrite_endpoint, and Make WordPress Plugins summary of endpoints.
    • If not specified, then it inherits from permalink_epmask(if permalink_epmask is set), otherwise defaults to EP_PERMALINK.
Note: If registering a post type inside of a plugin, call flush_rewrite_rules() in your activation and deactivation hook (see Flushing Rewrite on Activation below). If flush_rewrite_rules() is not used, then you will have to manually go to Settings > Permalinks and refresh your permalink structure before your custom post type will show the correct structure.

permalink_epmask

(string) (optional) The default rewrite endpoint bitmasks. For more info see Trac Ticket 12605 and this – Make WordPress Plugins summary of endpoints.

Default: EP_PERMALINK
Note: In 3.4, this argument is effectively replaced by the 'ep_mask' argument under rewrite.

query_var

(boolean or string) (optional) Sets the query_var key for this post type.

Default: true – set to $post_type
  • ‘false’ – Disables query_var key use. A post type cannot be loaded at /?{query_var}={single_post_slug}
  • ‘string’ – /?{query_var_string}={single_post_slug} will work as intended.
Note: The query_var parameter has no effect if the ‘publicly_queryable’ parameter is set to false. query_var adds the custom post type’s query var to the built-in query_vars array so that WordPress will recognize it. WordPress removes any query var not included in that array.
If set to true it allows you to request a custom posts type (book) using this: example.com/?book=life-of-pi
If set to a string rather than true (for example ‘publication’), you can do: example.com/?publication=life-of-pi

can_export

(boolean) (optional) Can this post_type be exported.

Default: true

delete_with_user

(boolean) (optional) Whether 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 then 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('author'). Otherwise posts are not trashed or deleted.

Default: null

show_in_rest

(boolean) (optional) Whether to expose this post type in the REST API. Must be true to enable the Gutenberg editor.

Default: false

rest_base

(string) (optional) The base slug that this post type will use when accessed using the REST API.

Default: $post_type

rest_controller_class

(string) (optional) An optional custom controller to use instead of WP_REST_Posts_Controller. Must be a subclass of WP_REST_Controller.

Default: WP_REST_Posts_Controller

_builtin

(boolean) (not for general use) Whether this post type is a native or “built-in” post_type. Note: this entry is for documentation – core developers recommend you don’t use this when registering your own post type

Default: false
  • ‘false’ – default this is a custom post type
  • ‘true’ – this is a built-in native post type (post, page, attachment, revision, nav_menu_item)

_edit_link

(boolean) (not for general use) Link to edit an entry with this post type. Note: this entry is for documentation – core developers recommend you don’t use this when registering your own post type

Default:
  • ‘post.php?post=%d’

Flushing Rewrite on Activation

To get permalinks to work when you activate the plugin use the following example, paying attention to how my_cpt_init() is called in the register_activation_hook callback:

add_action( 'init', 'my_cpt_init' );
function my_cpt_init() {
register_post_type( ... );
}

register_activation_hook( __FILE__, 'my_rewrite_flush' );
function my_rewrite_flush() {
// First, we "add" the custom post type via the above written function.
// Note: "add" is written with quotes, as CPTs don't get added to the DB,
// They are only referenced in the post_type column with a post entry,
// when you add a post of this CPT.
my_cpt_init();

// ATTENTION: This is *only* done during plugin activation hook in this example!
// You should *NEVER EVER* do this on every page load!!
flush_rewrite_rules();
}

For themes, you’ll need to use the after_switch_theme hook instead. Like so:

add_action( 'init', 'my_cpt_init' );
function my_cpt_init() {
register_post_type( ... );
}

add_action( 'after_switch_theme', 'my_rewrite_flush' );
function my_rewrite_flush() {
my_cpt_init();
flush_rewrite_rules();
}

Note that although the $public attribute is optional, the inputs passed to the register_post_type() function are exactly what is queried by the get_post_types() function. So if you verbosely set the equivalent options for publicly_queriable, show_ui, show_in_nav_menus, and exclude_from_search, this will not be handled the same as if you had set the $public attribute. See bug 18950.

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

VersionDescription
5.9.0The rest_namespace argument was added.
5.3.0The supports argument will now accept an array of arguments for a feature.
5.0.0The template and template_lock arguments were added.
4.7.0Introduced show_in_rest, rest_base and rest_controller_class arguments to register the post type in REST API.
4.6.0Post type object returned is now an instance of WP_Post_Type.
4.4.0The show_ui argument is now enforced on the post type listing screen and post editing screen.
3.0.0The show_ui argument is now enforced on the new post screen.
2.9.0Introduced.
Show 3 moreShow less

User Contributed Notes

  1. Skip to note 31 content

    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' );
  2. Skip to note 32 content

    To add Gutenberg compatibility in your custom post type, it require two things
    1. supports must have editor in it
    2. show_in_rest set to true

    function 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' );
  3. Skip to note 34 content

    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

  4. Skip to note 35 content

    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 http://php.net/date
    			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' );
  5. Skip to note 36 content

    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' );
  6. Skip to note 37 content

    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.

  7. Skip to note 38 content

    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="http://www.w3.org/2000/svg"><path 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

  8. Skip to note 39 content

    The following guidelines ensure consistency with the default post types and help to avoid conflict with post types registered by other developers.

    • Set the value of $post_type to 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.
    • Separate words with underscores (e.g. sports_team, video_game).
    • Prefix your post type with 3-4 characters followed by an underscore to prevent conflict with other post types (e.g. kwh_testimonial).
  9. Skip to note 40 content

    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.

  10. Skip to note 41 content

    This function doesn’t check if

    $post_type

    key is unique. If the post type with existing key is registered it overwrites

    $wp_post_types

    global array element without calling

    unregister_post_type()

    function. It still resides in memory, rewrite rules, and hooks are not removed. Function

    unregister_post_type()

    doesn’t allow to unregister builtin post types, but

    register_post_type()

    allows to register them more than once.

  11. Skip to note 42 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 );
    
    }
  12. Skip to note 43 content

    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 );
  13. Skip to note 44 content

    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 );
    
    }
  14. Skip to note 46 content

    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_types global variable.
    Example:

    add_action( 'admin_notices', function () {
    	global $wp_post_types;
    	var_dump( $wp_post_types );
    });
  15. Skip to note 47 content

    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

  16. Skip to note 48 content

    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' );
  17. Skip to note 49 content

    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="https://codex.wordpress.org/Posts_Edit_SubPanel&quot; target="_blank">Edit Posts Documentation</a>', 'your_text_domain') . '</p>' .
    			'<p>' . __('<a href="https://wordpress.org/support/&quot; 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;
    }
  18. Skip to note 50 content

    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.

  19. Skip to note 51 content

    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.

  20. Skip to note 52 content

    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 call flush_rewrite_rules() because it’s expensive. Only run it on plugin activation. See the section above.

  21. Skip to note 53 content

    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' );
  22. Skip to note 54 content

    Note that although the $public attribute is optional, the inputs passed to the register_post_type() function are exactly what is queried by the get_post_types() function. So if you verbosely set the equivalent options for publicly_queriable, show_ui, show_in_nav_menus, and exclude_from_search, this will not be handled the same as if you had set the $public attribute. See bug https://core.trac.wordpress.org/ticket/18950

  23. Skip to note 55 content

    A note regarding the usage of show_in_menu with 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_type of your CPT (usually post or page) 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 in add_menu_page of my-custom-menu.

    Example:
    If your add_menu_page registers with a capability of manage_options and your CPT will use post as capability_type any user will see the top level and submenu items if they have the edit_posts cap. But if they don’t have the manage_options cap they cannot access the CPT edit view as the cap check resolves to manage_options of the top level menu page instead of edit_posts as one might expect.

    Refer to user_can_access_admin_page in /wp-admin/includes/plugin.php

    This 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] );
    		}
    	}
  24. Skip to note 58 content

    I was extending a post type when I ran into a problem related to the register_meta_box_cb argument. 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 );
  25. Skip to note 59 content

    To use a custom SVG in menu_icon find 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 fill attribute set in any path elements in the SVG file.

    Edit the SVG file with an editor and add fill attribute to any `path` elements before converting to base64.

    <path d="...

    should be

    <path fill="#9da3a8" d="...

    You can use any color you want, including using none instead of a color, as WordPress will automatically update it to match the color scheme.

    https://css-tricks.com/almanac/properties/f/fill/

  26. Skip to note 60 content

    When you register a new Custom Post Type I’d highly recommend setting with_front => true as 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

You must log in before being able to contribute a note or feedback.