What Is a Custom Post Type?
In WordPress, everything is a “post” under the hood, but post types categorize content into buckets with unique behaviors and templates. A CPT gives you:
- Its own admin menu and editor features
- Custom archive and single templates (e.g.,
/books/
,/books/the-hobbit/
) - REST API endpoints for headless/JS apps
- Custom taxonomies (e.g., Genre) and meta fields (e.g., ISBN)
Quick Start: Production‑Ready CPT (as a Plugin)
Create a file wp-content/plugins/my-books-cpt.php
and paste:
<?php
/**
* Plugin Name: My Books CPT
* Description: Registers a "Book" custom post type with taxonomy, REST, and meta fields.
* Version: 1.0.0
* Author: You
*/
if ( ! defined( 'ABSPATH' ) ) exit; // No direct access
// 1) Register the CPT
function mb_register_book_cpt() {
$labels = [
'name' => __('Books', 'mybooks'),
'singular_name' => __('Book', 'mybooks'),
'menu_name' => __('Books', 'mybooks'),
'add_new_item' => __('Add New Book', 'mybooks'),
'edit_item' => __('Edit Book', 'mybooks'),
'view_item' => __('View Book', 'mybooks'),
'all_items' => __('All Books', 'mybooks'),
'search_items' => __('Search Books', 'mybooks'),
'not_found' => __('No books found.', 'mybooks'),
];
$args = [
'labels' => $labels,
'public' => true,
'show_in_menu' => true,
'menu_icon' => 'dashicons-book',
'supports' => ['title','editor','excerpt','thumbnail','custom-fields','revisions'],
'has_archive' => true, // /books/
'rewrite' => ['slug' => 'books'], // /books/book-title/
'show_in_rest' => true, // Gutenberg + REST
'rest_base' => 'book',
'capability_type' => 'post',
'map_meta_cap' => true,
'publicly_queryable' => true,
'hierarchical' => false,
];
register_post_type( 'book', $args );
}
add_action( 'init', 'mb_register_book_cpt' );
// 2) Register the taxonomy: Genre
function mb_register_book_genre_taxonomy() {
register_taxonomy(
'genre',
['book'],
[
'labels' => ['name' => __('Genres','mybooks'),'singular_name' => __('Genre','mybooks')],
'public' => true,
'hierarchical' => true, // like categories
'show_in_rest' => true,
'rewrite' => ['slug' => 'genre'],
]
);
}
add_action( 'init', 'mb_register_book_genre_taxonomy' );
// 3) Register meta fields (author_name, isbn) with REST
function mb_register_book_meta() {
register_post_meta( 'book', 'author_name', [
'type' => 'string',
'single' => true,
'default' => '',
'sanitize_callback' => 'sanitize_text_field',
'show_in_rest' => true,
'auth_callback' => fn() => current_user_can('edit_posts'),
] );
register_post_meta( 'book', 'isbn', [
'type' => 'string',
'single' => true,
'sanitize_callback' => 'sanitize_text_field',
'show_in_rest' => true,
'auth_callback' => fn() => current_user_can('edit_posts'),
] );
}
add_action( 'init', 'mb_register_book_meta' );
// 4) Flush rewrites on activation/deactivation
function mb_books_activate() {
mb_register_book_cpt();
mb_register_book_genre_taxonomy();
flush_rewrite_rules();
}
register_activation_hook( __FILE__, 'mb_books_activate' );
function mb_books_deactivate() {
flush_rewrite_rules();
}
register_deactivation_hook( __FILE__, 'mb_books_deactivate' );
Activate it under Plugins → Installed Plugins.
Using Your CPT
- Find a new admin menu item: Books.
- Add content like normal posts: title, content, featured image, excerpt.
- Use the registered meta fields (Author Name, ISBN) via the sidebar or custom UI.
REST API Endpoints
- List books:
/wp-json/wp/v2/book
- Single book:
/wp-json/wp/v2/book/{id}
- Filter by genre slug:
/wp-json/wp/v2/book?genre=fantasy
Theme Templates for CPT
Create these files in your active theme to control layout:
archive-book.php
– CPT archive (e.g.,/books/
)single-book.php
– Single CPT pagetaxonomy-genre.php
– Genre archive
archive-book.php (example)
<?php get_header(); ?>
<main class="site-main">
<h1><?php post_type_archive_title(); ?></h1>
<?php if ( have_posts() ) : ?>
<div class="books-grid">
<?php while ( have_posts() ) : the_post(); ?>
<article <?php post_class(); ?>>
<a href="<?php the_permalink(); ?>">
<?php if ( has_post_thumbnail() ) the_post_thumbnail('medium'); ?>
<h2><?php the_title(); ?></h2>
</a>
<?php if ( $author = get_post_meta( get_the_ID(), 'author_name', true ) ) : ?>
<p>Author: <?= esc_html( $author ); ?></p>
<?php endif; ?>
<?php the_excerpt(); ?>
</article>
<?php endwhile; ?>
</div>
<?php the_posts_pagination(); ?>
<?php else : ?>
<p>No books found.</p>
<?php endif; ?>
</main>
<?php get_footer(); ?>
single-book.php (meta snippet)
<?php get_header(); ?>
<main class="site-main">
<?php while ( have_posts() ) : the_post(); ?>
<article <?php post_class(); ?>>
<h1><?php the_title(); ?></h1>
<?php if ( has_post_thumbnail() ) the_post_thumbnail('large'); ?>
<ul class="book-meta">
<?php if ( $author = get_post_meta( get_the_ID(), 'author_name', true ) ) : ?>
<li><strong>Author:</strong> <?= esc_html( $author ); ?></li>
<?php endif; ?>
<?php if ( $isbn = get_post_meta( get_the_ID(), 'isbn', true ) ) : ?>
<li><strong>ISBN:</strong> <?= esc_html( $isbn ); ?></li>
<?php endif; ?>
<li><strong>Genres:</strong> <?= get_the_term_list( get_the_ID(), 'genre', '', ', ' ); ?></li>
</ul>
<div class="content"><?php the_content(); ?></div>
</article>
<?php endwhile; ?>
</main>
<?php get_footer(); ?>
Querying Custom Post Types in PHP
Basic query
$books = new WP_Query([
'post_type' => 'book',
'posts_per_page' => 6,
'orderby' => 'date',
'order' => 'DESC',
]);
if ( $books->have_posts() ) {
while ( $books->have_posts() ) {
$books->the_post();
the_title('<h3>','</h3>');
}
wp_reset_postdata();
}
Filter by taxonomy term
$fantasy_books = new WP_Query([
'post_type' => 'book',
'tax_query' => [[
'taxonomy' => 'genre',
'field' => 'slug',
'terms' => ['fantasy'],
]],
]);
Gutenberg & REST Support
Setting 'show_in_rest' => true
on both CPT and taxonomy enables the Block Editor and REST API endpoints, making your CPT headless‑friendly and ready for custom blocks, Patterns, and JS apps.
Custom Capabilities (Granular Permissions)
Need role‑based control (e.g., editors can publish books but not pages)? Switch to custom capabilities:
// In register_post_type() args:
'capability_type' => ['book','books'],
'map_meta_cap' => true;
Then grant caps like edit_book
, publish_books
, delete_books
to roles using a role editor plugin or add_cap()
.
Improve Admin UX: Custom List Columns
// Add an Author column for Books
add_filter( 'manage_book_posts_columns', function( $cols ) {
$cols['author_name'] = __('Author', 'mybooks');
return $cols;
});
add_action( 'manage_book_posts_custom_column', function( $col, $post_id ) {
if ( 'author_name' === $col ) {
echo esc_html( get_post_meta( $post_id, 'author_name', true ) ?: '—' );
}
}, 10, 2 );
Common Pitfalls & Quick Fixes
- 404 on archive/single: Visit Settings → Permalinks and click Save, or flush rules on activation (as shown).
- CPT not in Block Editor: Ensure
'show_in_rest' => true
and thatsupports
includes needed features. - Slug conflicts: Make sure your CPT
rewrite['slug']
isn’t used by a page or another CPT. - Meta hidden in REST: Use
register_post_meta()
withshow_in_rest => true
.
FAQs
What’s the main benefit of a Custom Post Type?
Structure. CPTs separate content into meaningful groups (e.g., Books vs. Posts), each with its own templates, taxonomies, and admin UI, which keeps your site organized and scalable.
Should I register a CPT in a theme or a plugin?
Prefer a plugin. CPTs are content, not presentation. If you switch themes, your CPT remains intact.
Do I need a taxonomy for my CPT?
No, but a taxonomy (like Genre for Books) improves filtering, archives, and navigation. Use hierarchical if it behaves like categories; non‑hierarchical for tag‑like behavior.
How do I expose CPT data to JavaScript apps?
Enable show_in_rest
. Then use /wp-json/wp/v2/book
endpoints in your frontend (React/Vue/Next.js) to fetch, filter, and display entries.
Why am I getting 404s after registering a CPT?
WordPress needs to regenerate rewrite rules. Visit Settings → Permalinks and click Save, or flush on activation using flush_rewrite_rules()
as shown.
Is it safe to use custom SQL with $wpdb?
Yes, for advanced needs just prepare and sanitize queries. Prefer WP_Query
for most content retrieval to leverage caching and core APIs.
Conclusion
Custom Post Types are the backbone of structured WordPress sites. With the plugin scaffold above, REST endpoints, meta fields, templates, and admin polish, you’re ready to model any content—from portfolios to events cleanly and reliably.