Custom Post Types (CPTs) are one of WordPress’s most powerful features, allowing developers to extend the default content management system beyond standard posts and pages. Since WordPress 3.0, CPTs have enabled the creation of distinct content types with their own attributes, behaviors, and taxonomies.
By default, WordPress comes with several post types:
Post (post)
Page (page)
Attachment (attachment)
Revision (revision)
Navigation menu (nav_menu_item)
However, many websites need to manage different kinds of content that don’t fit these default types. That’s where custom post types come in.
Why Use Custom Post Types?
Custom post types offer several advantages:
Better Content Organization: Separate different content types logically (e.g., Products, Portfolio Items, Testimonials)
Custom Functionality: Each CPT can have unique fields, behaviors, and display templates
Improved Admin Interface: Different content types get their own admin menu sections
Specialized Taxonomies: Attach custom classification systems to specific content types
Enhanced Performance: Query specific content types more efficiently
Methods to Create Custom Post Types
There are three primary ways to create custom post types in WordPress:
1. Using register_post_type() in functions.php
The most common method is to use the register_post_type() function in your theme’s functions.php file or a custom plugin:
function create_book_post_type() {
$labels = array(
'name' => __( 'Books' ),
'singular_name' => __( 'Book' ),
'add_new' => __( 'Add New Book' ),
'add_new_item' => __( 'Add New Book' ),
'edit_item' => __( 'Edit Book' ),
'new_item' => __( 'New Book' ),
'all_items' => __( 'All Books' ),
'view_item' => __( 'View Book' ),
'search_items' => __( 'Search Books' ),
'not_found' => __( 'No books found' ),
'not_found_in_trash' => __( 'No books found in the Trash' ),
'menu_name' => __( 'Books' )
);
$args = array(
‘labels’ => $labels,
‘description’ => ‘Holds our books and book specific data’,
‘public’ => true,
‘menu_position’ => 5,
‘supports’ => array( ‘title’, ‘editor’, ‘thumbnail’, ‘excerpt’, ‘comments’ ),
‘has_archive’ => true,
‘show_in_rest’ => true, // Enable Gutenberg editor
);
register_post_type( ‘book’, $args );
}
add_action( ‘init’, ‘create_book_post_type’ );
2. Using a Plugin (Custom Post Type UI)
For non-developers, plugins like “Custom Post Type UI” provide a user interface to create and manage custom post types:
Install and activate “Custom Post Type UI”
Navigate to CPT UI → Add/Edit Post Types
Fill in the post type slug, labels, and settings
Save the post type
Basic Parameters:
label – Name of the post type shown in the admin menu
labels – Array of labels for various admin screens
description – Brief description of the post type
public – Whether the post type is publicly queryable
exclude_from_search – Whether to exclude from search results
publicly_queryable – Whether queries can be performed on the front end
show_ui – Whether to generate a default UI in the admin
show_in_menu – Where to show in admin menu (true/false or string)
menu_position – Position in admin menu (5=below Posts, 10=below Media)
menu_icon – URL to icon or dashicon name
capability_type – String to use to build capabilities (default ‘post’)
hierarchical – Whether post type is hierarchical like pages
supports – Features the post type supports (title, editor, thumbnail, etc.)
taxonomies – Array of registered taxonomies to associate with
has_archive – Whether post type has an archive page
rewrite – Triggers the handling of rewrites for this post type
query_var – Sets the query_var key for this post type
show_in_rest – Whether to enable the Gutenberg editor
Advanced Parameters:
can_export – Whether post type is available for export
delete_with_user – Whether to delete posts when user is deleted
template – Array of blocks to use as default initial state
template_lock – Whether the block template should be locked
rest_base – Base slug for REST API routes
rest_controller_class – REST API controller class name
Taxonomies allow you to classify your CPTs. WordPress has built-in taxonomies (categories and tags), but you can create custom ones.
Registering a Custom Taxonomy
function create_book_taxonomies() {
// Genre Taxonomy
$labels = array(
'name' => __( 'Genres' ),
'singular_name' => __( 'Genre' ),
'search_items' => __( 'Search Genres' ),
'all_items' => __( 'All Genres' ),
'parent_item' => __( 'Parent Genre' ),
'parent_item_colon' => __( 'Parent Genre:' ),
'edit_item' => __( 'Edit Genre' ),
'update_item' => __( 'Update Genre' ),
'add_new_item' => __( 'Add New Genre' ),
'new_item_name' => __( 'New Genre Name' ),
'menu_name' => __( 'Genre' ),
);
$args = array(
‘hierarchical’ => true, // like categories
‘labels’ => $labels,
‘show_ui’ => true,
‘show_admin_column’ => true,
‘query_var’ => true,
‘rewrite’ => array( ‘slug’ => ‘genre’ ),
‘show_in_rest’ => true,
);
register_taxonomy( ‘genre’, array( ‘book’ ), $args );
// Non-hierarchical taxonomy (like tags)
register_taxonomy( ‘book_tag’, ‘book’, array(
‘label’ => __( ‘Book Tags’ ),
‘rewrite’ => array( ‘slug’ => ‘book-tag’ ),
‘hierarchical’ => false,
‘show_in_rest’ => true,
) );
}
add_action( ‘init’, ‘create_book_taxonomies’, 0 );
hierarchical – Whether the taxonomy is hierarchical (like categories) or not (like tags)
labels – Array of labels for the admin interface
show_ui – Whether to show in admin UI
show_admin_column – Whether to show in admin posts list as column
query_var – Sets the query_var key for this taxonomy
rewrite – Triggers the handling of rewrites for this taxonomy
show_in_rest – Whether to include in REST API
capabilities – Array of capabilities for this taxonomy
meta_box_cb – Callback for meta box display
1. Creating Archive Templates
WordPress will look for archive templates in this order:
archive-{post_type}.php
archive.php
index.php
For our “book” CPT, create archive-book.php:
2. Creating Single Post Templates
For single book posts, WordPress looks for:
single-{post_type}.php
single.php
singular.php
index.php
Create single-book.php:
Basic WP_Query for CPTs
$books_query = new WP_Query( array(
'post_type' => 'book',
'posts_per_page' => 10,
'orderby' => 'title',
'order' => 'ASC',
'tax_query' => array(
array(
'taxonomy' => 'genre',
'field' => 'slug',
'terms' => 'science-fiction',
),
),
'meta_query' => array(
array(
'key' => 'book_price',
'value' => 20,
'compare' => '<', 'type' => 'NUMERIC',
),
),
) );
if ( $books_query->have_posts() ) {
while ( $books_query->have_posts() ) {
$books_query->the_post();
// Display book content
}
wp_reset_postdata();
}
Pre-get-posts Filter
To modify the main query for your CPT archive:
function modify_book_archive_query( $query ) {
if ( ! is_admin() && $query->is_main_query() && is_post_type_archive( 'book' ) ) {
$query->set( 'posts_per_page', 12 );
$query->set( 'orderby', 'title' );
$query->set( 'order', 'ASC' );
}
}
add_action( 'pre_get_posts', 'modify_book_archive_query' );
Use Unique Prefixes: Prefix your CPT and taxonomy names to avoid conflicts (e.g., ‘myplugin_book’ instead of ‘book’)
Flush Rewrite Rules: After registering a new CPT, flush rewrite rules by visiting Settings → Permalinks
Enable REST API: Set show_in_rest to true for modern WordPress development
Use Proper Text Domains: For internationalization, use a consistent text domain in your labels
Document Your CPTs: Maintain documentation for your custom post types and taxonomies
Consider Performance: Be mindful of query complexity, especially with meta queries
Provide Uninstall Hooks: Clean up your CPTs when the plugin is uninstalled
Conclusion
Custom post types are a cornerstone of advanced WordPress development, enabling the platform to manage virtually any type of content. Whether you’re building an e-commerce site with WooCommerce, a learning management system, or a custom business directory, CPTs provide the flexibility to extend WordPress beyond its default capabilities.
By understanding how to properly register post types, create associated taxonomies, and display them on the front end, you can build sophisticated WordPress applications that maintain all the benefits of the WordPress ecosystem while providing tailored content management solutions.
For scenarios where the post/term/meta model doesn’t fit, direct database access via $wpdb remains an option, though it should be used judiciously given the loss of WordPress’s built-in content management features.
Share: