Custom Post Types & Taxonomies

30-45 minIntermediate

While Posts and Pages cover many use cases, custom post types (CPTs) and taxonomies let you create dedicated content structures for Products, Testimonials, Portfolio Items, Events, and more. CPTs define the content type; Taxonomies define how to group and classify that content.

Prerequisites

  • Understanding of WordPress hooks (init action)
  • Access to theme functions.php or custom plugin
  • Clear content structure requirements

Step-by-Step Guide

1

Understand Posts vs Taxonomies

1

Post Types define the TYPE of content (the "thing")

2

Core examples: Posts, Pages, Attachments, Revisions

3

Custom examples: Products, Portfolio Items, Testimonials, Events

4

Taxonomies define ways to GROUP and CLASSIFY content

5

Core examples: Categories, Tags

6

Custom examples: Genre, Location, Skill, Color

Think: Post Types are nouns (things), Taxonomies are adjectives (descriptors)
2

Register a Custom Post Type

1

Use register_post_type() hooked to the init action

2

First parameter: post type slug (lowercase, no spaces, max 20 chars)

3

Second parameter: array of arguments defining behavior

4

Key arguments: labels, public, has_archive, supports, rewrite

Example
// Register "Book" Custom Post Type
add_action( 'init', 'create_book_post_type' );
function create_book_post_type() {
    $labels = array(
        'name'               => 'Books',
        'singular_name'      => 'Book',
        'menu_name'          => 'Books',
        'add_new'            => 'Add New Book',
        'add_new_item'       => 'Add New Book',
        'edit_item'          => 'Edit Book',
        'new_item'           => 'New Book',
        'view_item'          => 'View Book',
        'search_items'       => 'Search Books',
        'not_found'          => 'No books found',
        'not_found_in_trash' => 'No books found in trash',
    );

    $args = array(
        'labels'             => $labels,
        'public'             => true,
        'publicly_queryable' => true,
        'show_ui'            => true,
        'show_in_menu'       => true,
        'show_in_rest'       => true,  // Enable Gutenberg editor
        'has_archive'        => true,  // Creates /books/ archive page
        'rewrite'            => array( 'slug' => 'books' ),
        'supports'           => array( 'title', 'editor', 'thumbnail', 'excerpt' ),
        'menu_icon'          => 'dashicons-book-alt',
    );

    register_post_type( 'book', $args );
}
After registering, go to Settings > Permalinks and click Save to flush rewrite rules
3

Key CPT Arguments Explained

1

labels: Array of UI text for admin screens (required for good UX)

2

public: true = visible to authors and public (most common)

3

has_archive: true = creates archive page at /your-slug/

4

supports: Array of features - title, editor, thumbnail, excerpt, custom-fields, etc.

5

rewrite: Controls URL structure - set slug for SEO-friendly URLs

6

show_in_rest: true = enables Gutenberg block editor and REST API access

7

menu_icon: Dashicon class or URL for admin menu icon

4

Register a Custom Taxonomy

1

Use register_taxonomy() hooked to init action

2

First param: taxonomy slug; Second: post type(s) it applies to; Third: args array

3

Key choice: hierarchical (like Categories) or non-hierarchical (like Tags)

Example
// Register "Genre" Taxonomy for Books
add_action( 'init', 'create_book_taxonomies' );
function create_book_taxonomies() {
    // Hierarchical taxonomy (like Categories)
    $genre_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'         => 'Genres',
    );

    register_taxonomy( 'genre', 'book', array(
        'labels'            => $genre_labels,
        'hierarchical'      => true,  // Like Categories (parent/child)
        'public'            => true,
        'show_in_rest'      => true,
        'rewrite'           => array( 'slug' => 'genre' ),
    ));

    // Non-hierarchical taxonomy (like Tags)
    register_taxonomy( 'book_tag', 'book', array(
        'labels'            => array(
            'name'          => 'Book Tags',
            'singular_name' => 'Book Tag',
        ),
        'hierarchical'      => false,  // Like Tags (flat)
        'public'            => true,
        'show_in_rest'      => true,
        'rewrite'           => array( 'slug' => 'book-tag' ),
    ));
}
5

Create Templates for Your CPT

1

WordPress automatically looks for CPT-specific templates

2

Single view: single-book.php (falls back to single.php)

3

Archive view: archive-book.php (falls back to archive.php)

4

Taxonomy archive: taxonomy-genre.php or taxonomy-genre-fiction.php

5

Create these files in your child theme to customize display

Verification Checklist

  • Custom Post Type appears in WordPress admin menu
  • Can add new items with all supported features
  • Archive page works at the configured slug
  • Custom taxonomy appears in CPT edit screen
  • Gutenberg editor works (show_in_rest is true)
  • Permalinks are clean after saving Settings > Permalinks

Pro Tips

  • Always set show_in_rest => true for Gutenberg compatibility
  • Use a plugin like Custom Post Type UI (CPT UI) for GUI-based registration
  • Register CPTs in a plugin (not theme) if content should persist across theme changes
  • Flush rewrite rules after any changes (save permalinks or wp rewrite flush)
  • Use register_post_type_args filter to modify CPT registered by plugins