Custom Post Types Location
First, where does this code reside? As with almost any addition, it will reside in your functions.php file or in an external php that functions.php calls through a require_once (e.g., require_once( TEMPLATE.'/lib/custom_post_type.php' );
). Second, registering the custom post type needs to happen in the init hook.
Custom Post Types Namespaces
Naming of the custom post type is critical. Be sure to use a short namespace, which identifies the plugin, theme, or website (in this case, website) that implements the custom post type. Namespaces is the prefix that appears before the custom post type code name. For example, I may have a custom post type of CARS, but my custom post type code name is wps_cars. The "wps_" is a namespace. My recommendation is to create a namespace that identifies you and your brand, e.g., for me wps_, for a company called Bob Barker Media, they may want bbm_ or bbmed_. Then consistently use this prefix in front of all your functions, names, etc. to prevent conflicts.
This is important because without this namespace, it is extremely possible and even likely to conflict with a custom post type in your theme or a plugin that you use. While this namespacing won't guarantee a lack of conflict, it dramatically reduces the chances. However, be sure that your namespace does not exceed 20 characters as the post_type column is a varchar of the latter length. Also be sure to avoid the namespace "wp_" as WordPress may use that moving forward. And finally, never use capital letters.
Summary:
- Put the code in functions.php or call it from functions.php
- Naming Conventions:
- Use namespaces (e.g., wps_) and avoid the namespace "wp_"
- Keep name under 20 characters
- Never use capital letters
- Registered your custom post type in the init hook
Registering the custom post type can take 25 arguments with more sub-arguments. They are:
label (string, default: $post_type):a plural descriptive name | labels (array, default: name is set to label value, and singular_name is set to name value; more below) | description (string, default: blank): A short descriptive summary of what the post type is |
public (boolean, default: false): Meta argument used to define default values for publicly_queriable, show_ui, show_in_nav_menus and exclude_from_search | publicly_queryable (boolean, default: value of public): Whether post_type queries can be performed from the front end | exclude_from_search (boolean, default: opposite value of public): Whether to exclude posts with this post type from search results |
show_ui (boolean, value of public): Whether to generate a default UI for managing this post type | show_in_menu (boolean/string, default: null; false-no menu item, true-top level admin menu item, some string-top level page like 'tools.php'): Whether to show the post type in the admin menu and where to show that menu | menu_position (integer, default: null): The position in the menu order the post type should appear |
menu_icon (string, default: null): The url to the icon to be used for this menu | capability_type (string/array, default: "post"): The string to use to build the read, edit, and delete capabilities | capabilities (array, default: capability_type: post): An array of the capabilities for this post type |
map_meta_cap (boolean, default: false): Whether to use the internal default meta capability handling | hierarchical (boolean, default: false): Whether the post type is hierarchical. Allows Parent to be specified | supports (array, default: title and editor, but can support the following: title, editor, author, thumbnail [featured image, theme must support post-thumbnails], excerpt, trackbacks, custom-fields, comments, revisions, page attributes, post-formats [see Post Formats] ): An alias for calling add_post_type_support() directly |
register_meta_box_cb (string, default: none): Provide a callback function that will be called when setting up the meta boxes for the edit form. Do remove_meta_box() and add_meta_box() calls in the callback | taxonomies (array, default: none): An array of registered taxonomies like category or post_tag that will be used with this post type. This can be use in lieu of calling register_taxonomy_for_object_type() directly. Custom taxonomies still need to be registered with register_taxonomy() |
permalink_epmask (string, default: EP_PERMALINK): The default rewrite endpoint bitmasks |
has_archive (boolean, default: false): Enables post type archives. Will use string as archive slug. Will generate the proper rewrite rules if rewrite is enabled. | rewrite (boolean/array, default: true/slug): Rewrite permalinks with this format. False to prevent rewrite | query_var (boolean/string, default: true, set to $post_type): Whether post_type is available for selection in navigation menus |
can_export (boolean, default: true): Can this post_type be exported | show_in_nav_menus (boolean, default: value of public): Whether post_type is available for selection in navigation menus |
*Two other arguments include _builtin and _edit_link; however, WordPress core developers recommend you don't use these when registering your custom post type.
To me, the most important arguments to set are label, labels, public (which sets publicly_queryable, exclude_from_search, show_ui, and show_in_nav_menus), description, supports, register_meta_box_cb, taxonomies, has_archive, rewrite, and capabilities.
Label
Label (a plural descriptive name for the post type marked for translation) and labels (an array of labels for this post type) are what appears on the UI (use
r interface) on the backend.
Labels
from the Codex:
Default: if empty, name is set to label value, and singular_name is set to name value
- 'name' - general name for the post type, usually plural. The same as, and overridden by $post_type_object->label
- 'singular_name' - name for one object of this post type. Defaults to value of name
- 'add_new' - the add new text. The default is Add New for both hierarchical and non-hierarchical types. When internationalizing this string, please use a gettext context matching your post type. Example:
_x('Add New', 'product');
- 'add_new_item' - the add new item text. Default is Add New Post/Add New Page
- 'edit_item' - the edit item text. Default is Edit Post/Edit Page
- 'new_item' - the new item text. Default is New Post/New Page
- 'view_item' - the view item text. Default is View Post/View Page
- 'search_items' - the search items text. Default is Search Posts/Search Pages
- 'not_found' - the not found text. Default is No posts found/No pages found
- 'not_found_in_trash' - the not found in trash text. Default is No posts found in Trash/No pages found in Trash
- 'parent_item_colon' - the parent text. This string isn't used on non-hierarchical types. In hierarchical ones the default is Parent Page
- 'menu_name' - the menu name text. This string is the name to give menu items. Defaults to value of name
Public
public sets a few other parameters, which don't need to be set unless you want something different (e.g., I want a public post_type but want it unsearchable/unqueryable publicly, then public => true and publicly_queryable => false or exclude_from_search => true).
From the codex:
- 'false' - do not display a user-interface for this post type (show_ui=false), post_type queries can not be performed from the front end (publicly_queryable=false), exclude posts with this post type from search results (exclude_from_search=true), hide post_type for selection in navigation menus (show_in_nav_menus=false)
- 'true' - show_ui=true, publicly_queryable=true, exclude_from_search=false, show_in_nav_menus=true
Description
Just like category description, description could have some SEO value but doesn't inherently as of 3.1.
Supports
supports refers to the current existing post/page metaboxes: title, editor, author, thumbnail, excerpt, trackbacks, custom-fields, comments, revisions, and page-attributes.
Meta Boxes
register_meta_box_cb is the way to add more metaboxes that can be built out specifically for the custom post type.
Taxonomies
taxonomies refers to the specific taxonomies that are associated with the custom post type. So if I have a custom post type of movies, I may want my custom taxonomy of producers, stars, etc. attached to it.
Archives
has_archive enables the custom post type to have an archive.
Menu Position
While not inherently important, menu_position could be critical if you want it to appear before posts. So for convenience here are the numbers:
- 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
Rewrite
from the Codex:
$args array
- 'slug' - prepend posts with this slug - defaults to post type's name - use array('slug'=>$slug) to customize permastruct
- 'with_front' - allowing permalinks to be prepended with front base (example: if your permalink structure is /blog/, then your links will be: false->/news/, true->/blog/news/) - defaults to true
- 'feeds' - default to has_archive value
- 'pages' - defaults to true
Capabilities
Justin Tadlock wrote an excellent article regarding capabilities and capability_type: Meta capabilities for custom post types. If you want to segment out someone's capability to read, edit, and/or delete your custom post type, then read Justin's post because this argument allows you to customize and split out those capabilities.
from the Codex:
By default, seven 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.
- publish_posts - Controls publishing objects of this post type.
- read_private_posts - Controls whether private objects can be read.
Note: those last four primitive capabilities are checked in core in various locations.
There are also seven 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_posts - Controls whether objects of this post type can be deleted.
- 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.
For example, check out this blog post: Permissions with WordPress Custom Post Types
EXAMPLES
Simple Example
A simple custom post type registration only requires a few lines of code.
[php] function wps_register_cars_cpt () {<br /> register_post_type ('wps_cars', array( 'label' => 'Cars' , 'public' => true ) );<br />}<br />add_action( 'init' , 'my_register_cpt' );<br />?>[/php]
However, it is only limited to the defaults of registering post types as you can see in the above table. However, to optimize the custom post type, you need to add a few more lines of code.
Complex Example
When registering your post type, it is a best practice to split out labels into a separate array.
[php]<br />add_action('init', 'wps_cpt_init');<br />function wps_cpt_init() {<br /> $labels= array(<br /> 'name' => _x('Cars', 'post type
general name'),<br /> 'singular_name' => _x('Car', 'post type singular name'),<br /> 'add_new' => _x('Add New', 'car'),<br /> 'add_new_item' => __('Add New Car'),<br /> 'edit_item' => __('Edit Car'),<br /> 'new_item' => __('New Car'),<br /> 'view_item' => __('View Car'),<br /> 'search_items' => __('Search Car'),<br /> 'not_found' => __('No cars found'),<br /> 'not_found_in_trash' => __('No cars found in Trash'),<br /> 'parent_item_colon' => '',<br /> 'menu_name' => 'Cars'<br /> );<br /> $args = array(<br /> 'labels' => $labels,<br /> 'public' => true,<br /> 'query_var' => true,<br /> 'rewrite' => true,<br /> 'capability_type' => 'post',<br /> 'has_archive' => true,<br /> 'hierarchical' => false,<br /> 'menu_position' => null,<br /> 'taxonomies' => array('wps_car_model','wps_car_color'),<br /> 'register_meta_box_cb' => 'add_wps_metaboxes',<br /> 'supports' => array('title','editor','author','thumbnail','excerpt','trackbacks','custom-fields','comments','revisions')<br /> );<br /> register_post_type( 'wps_cars' , $args );<br />}<br />[/php]
Very nice write up on Custom Post Types. I have not seen one quite this comprehensive anywhere else.
I never realized namespace should not exceed 20 characters!
I’m still not clear on Capabilities. I have only ever seen ‘post’ used, and never any of the others you listed. The codex explanation is still a bit foggy for me.
Thanks for the great post and information on CPT’s!
Very comprehensive! I needed a master cheat sheet on custom post types to round out some details on a new client WordPress set-up, and this is it– easy to find on Google search, too. Nice work all around, thanks!!!!
Thanks John & Bruce!