register_post_type( string $post_type, array|string $args = array() )

Registers a post type.


Description 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 Parameters

$post_type

(string) (Required) Post type key. Must not exceed 20 characters and may only contain lowercase alphanumeric characters, dashes, and underscores. See sanitize_key().

$args

(array|string) (Optional) 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'
    (array) 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) 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 (eg. 'tools.php' or 'edit.php?post_type=page'), 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 $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 add the post type route in the REST API 'wp/v2' namespace.
  • 'rest_base'
    (string) To change the base url of REST API route. Default is $post_type.
  • 'rest_controller_class'
    (string) REST API Controller class name. Default is 'WP_REST_Posts_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) 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'
    (array) 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. Defaults 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'
    (array) 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'
      (const) 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 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.
  • '_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'.

Default value: array()


Top ↑

Return Return

(WP_Post_Type|WP_Error) The registered post type object, or an error object.


Top ↑

Source Source

File: wp-includes/post.php

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 );

	return $post_type_object;
}

Top ↑

Changelog Changelog

Changelog
Version Description
4.7.0 Introduced show_in_rest, 'rest_base' and 'rest_controller_class' arguments 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_ui argument is now enforced on the post type listing screen and post editing screen.
3.0.0 The show_ui argument is now enforced on the new post screen.
2.9.0 Introduced.


Top ↑

User Contributed Notes User Contributed Notes

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

  3. Skip to note content
    Contributed by 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.', 'your-plugin-textdomain' ),
    		2 => __( 'Custom field updated.', 'your-plugin-textdomain' ),
    		3 => __( 'Custom field deleted.', 'your-plugin-textdomain' ),
    		4 => __( 'Book updated.', 'your-plugin-textdomain' ),
    		/* translators: %s: date and time of the revision */
    		5 => isset( $_GET['revision'] ) ? sprintf( __( 'Book restored to revision from %s', 'your-plugin-textdomain' ), wp_post_revision_title( (int) $_GET['revision'], false ) ) : false,
    		6 => __( 'Book published.', 'your-plugin-textdomain' ),
    		7 => __( 'Book saved.', 'your-plugin-textdomain' ),
    		8 => __( 'Book submitted.', 'your-plugin-textdomain' ),
    		9 => sprintf(
    			__( 'Book scheduled for: <strong>%1$s</strong>.', 'your-plugin-textdomain' ),
    			// translators: Publish box date format, see http://php.net/date
    			date_i18n( __( 'M j, Y @ G:i', 'your-plugin-textdomain' ), strtotime( $post->post_date ) )
    		),
    		10 => __( 'Book draft updated.', 'your-plugin-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', 'your-plugin-textdomain' ) );
    		$messages[ $post_type ][1] .= $view_link;
    		$messages[ $post_type ][6] .= $view_link;
    		$messages[ $post_type ][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', 'your-plugin-textdomain' ) );
    /**
     * 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( '&nbsp;<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' );
    
  4. Skip to note content
    Contributed by 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' );
    
  5. Skip to note content
    Contributed by Kevin Hoffman

    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).
  6. Skip to note content
    Contributed by Marek Aleszczyk

    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.

  7. Skip to note content
    Contributed by janw.oostendorp

    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 content
    Contributed by 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="https://codex.wordpress.org/Posts_Edit_SubPanel" target="_blank">Edit Posts Documentation</a>', 'your_text_domain') . '</p>' .
    			'<p>' . __('<a href="https://wordpress.org/support/" 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;
    }
    
  9. Skip to note content
    Contributed by 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 );
    
    }
    
  10. Skip to note content
    Contributed by Bronson Quick

    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

  11. Skip to note content
    Contributed by tripflex

    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/

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