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.

Why not just use categories on regular posts? You could tag posts as "book reviews," but a true custom post type gives you a dedicated admin menu, dedicated URLs, dedicated templates, and the ability to attach custom taxonomies and fields that make sense only for that content — much cleaner than overloading the built-in post type.

WordPress's Built-in Post Types

Post typeUsed for
postStandard blog posts, shown in reverse chronological order, organized with categories and tags
pageStatic content like "About Us" or "Contact," not part of the chronological blog flow
attachmentUploaded 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');
ArgumentMeaning
labelsText shown throughout the admin dashboard (menu name, "Add New" button, etc.)
publicWhether this post type is visible on the front end and in the admin dashboard at all
supportsWhich 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_restExposes this post type to the block editor and the WordPress REST API (explained below)
menu_iconThe 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 or not: Setting 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();
}
Don't forget 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.

Custom post types and taxonomies define the shape of your content — next, see exactly where WordPress physically stores all of this in The WordPress Database.