Custom Post Types & Taxonomies
Out of the box, WordPress ships with a handful of built-in post types: post (blog
posts), page (static pages), and attachment (uploaded media). These cover
a lot of ground, but plenty of real sites need structured content that isn't really a "blog post" or
a "page" — think a Book catalog, a Product listing, or a set of
Testimonials. That's exactly what custom post types are for: your
own content type, with its own admin screen, its own fields, and its own templates.
post type.WordPress's Built-in Post Types
| Post type | Used for |
|---|---|
post | Standard blog posts, shown in reverse chronological order, organized with categories and tags |
page | Static content like "About Us" or "Contact," not part of the chronological blog flow |
attachment | Uploaded media files (images, PDFs, videos) tracked in the Media Library |
Registering a Custom Post Type
Custom post types are registered with register_post_type(), hooked into the
init action so WordPress registers it early enough on every request. Here's a full
worked example registering a "Book" post type:
<?php
function register_book_post_type() {
$labels = [
'name' => 'Books',
'singular_name' => 'Book',
'add_new_item' => 'Add New Book',
'edit_item' => 'Edit Book',
'all_items' => 'All Books',
];
register_post_type('book', [
'labels' => $labels,
'public' => true,
'has_archive' => true,
'supports' => ['title', 'editor', 'thumbnail', 'excerpt'],
'show_in_rest' => true,
'menu_icon' => 'dashicons-book-alt',
]);
}
add_action('init', 'register_book_post_type');
| Argument | Meaning |
|---|---|
labels | Text shown throughout the admin dashboard (menu name, "Add New" button, etc.) |
public | Whether this post type is visible on the front end and in the admin dashboard at all |
supports | Which built-in editor features this post type has — a title field, the block editor content area, a featured image, an excerpt box, and so on |
show_in_rest | Exposes this post type to the block editor and the WordPress REST API (explained below) |
menu_icon | The Dashicon shown next to this post type's entry in the admin sidebar menu |
Registering a Custom Taxonomy
Just like post has categories and tags, your custom post type can have its own custom
taxonomies — essentially your own grouping system. Here's a "Genre" taxonomy attached to the "Book"
post type:
<?php
function register_genre_taxonomy() {
register_taxonomy('genre', 'book', [
'labels' => [
'name' => 'Genres',
'singular_name' => 'Genre',
],
'hierarchical' => true,
'public' => true,
'show_in_rest' => true,
]);
}
add_action('init', 'register_genre_taxonomy');
hierarchical to true
makes the taxonomy behave like categories (parent/child terms allowed, checkbox UI). Setting it to
false makes it behave like tags (flat list, free-text entry UI).Querying Your Custom Post Type
To display "Book" entries on the front end, you query them the same way you'd query any post type —
with WP_Query and a custom loop (see the The Loop
lesson for the full loop mechanics). The key is passing your custom post type's slug as the
post_type argument:
<?php
$book_query = new WP_Query([
'post_type' => 'book',
'posts_per_page' => 10,
]);
if ($book_query->have_posts()) {
while ($book_query->have_posts()) { $book_query->the_post();
?>
<h2><?php the_title(); ?></h2>
<?php the_excerpt();
<?php
}
wp_reset_postdata();
}
wp_reset_postdata(): After running a custom
WP_Query loop, always call this to restore WordPress's main global post data — otherwise
later parts of the template can end up reading the wrong post.Displaying Custom Field Data
Custom post types often carry extra structured data beyond title and content — an "Author Name"
or "ISBN" field for a Book, for example. That data is stored as post meta and read back with
get_post_meta():
<?php
$isbn = get_post_meta(get_the_ID(), 'book_isbn', true);
echo 'ISBN: ' . esc_html($isbn);
Building the admin-side input fields for data like this (a "meta box") is a deeper topic on its own,
covering add_meta_box() and saving values on the save_post hook — worth
exploring once you're comfortable with the basics shown here.
Why show_in_rest Matters
Setting show_in_rest => true does two important things: it makes the post type
editable in the modern block editor (Gutenberg), and it exposes the post type's data through the
WordPress REST API at an endpoint like /wp-json/wp/v2/book. Without
it, your custom post type is stuck using the older classic editor and is invisible to any JavaScript
front end or mobile app that talks to WordPress over the REST API — a topic covered in full in the
REST API lesson later in this track.