Skip to main content

Course Progress

Loading...

📦 Custom Post Types & Taxonomies

Extend WordPress beyond posts and pages

Create custom content structures for any type of website

Learning Objectives

  • Understand custom post types and their use cases
  • Register custom post types with register_post_type()
  • Create custom taxonomies for organization
  • Build custom archive and single templates
  • Add meta boxes and custom fields
  • Query custom post types effectively
  • Implement custom rewrite rules
  • Create admin columns for CPTs

Understanding Custom Post Types

Custom Post Types (CPTs) allow you to create content types beyond the default posts and pages. They're perfect for portfolios, testimonials, products, events, and any structured content.

💡
Key Concept
WordPress stores all content types in the same database table (wp_posts). Custom post types are simply posts with a different 'post_type' value.

🎨 Portfolio

Showcase projects and work samples

💬 Testimonials

Client reviews and feedback

📅 Events

Conferences, meetups, workshops

👥 Team Members

Staff profiles and bios

🛍️ Products

Catalog items for sale

📚 Resources

Downloads, guides, documentation

Registering Custom Post Types

Basic Custom Post Type Registration

<?php
/**
 * Register Portfolio Custom Post Type
 */
function mytheme_register_portfolio_cpt() {
    
    $labels = array(
        'name'               => _x( 'Portfolio', 'post type general name', 'mytheme' ),
        'singular_name'      => _x( 'Project', 'post type singular name', 'mytheme' ),
        'menu_name'          => _x( 'Portfolio', 'admin menu', 'mytheme' ),
        'add_new'            => _x( 'Add New', 'project', 'mytheme' ),
        'add_new_item'       => __( 'Add New Project', 'mytheme' ),
        'edit_item'          => __( 'Edit Project', 'mytheme' ),
        'new_item'           => __( 'New Project', 'mytheme' ),
        'view_item'          => __( 'View Project', 'mytheme' ),
        'all_items'          => __( 'All Projects', 'mytheme' ),
        'search_items'       => __( 'Search Projects', 'mytheme' ),
        'not_found'          => __( 'No projects found.', 'mytheme' ),
        'not_found_in_trash' => __( 'No projects found in Trash.', 'mytheme' ),
    );
    
    $args = array(
        'labels'             => $labels,
        'public'             => true,
        'publicly_queryable' => true,
        'show_ui'            => true,
        'show_in_menu'       => true,
        'query_var'          => true,
        'rewrite'            => array( 'slug' => 'portfolio' ),
        'capability_type'    => 'post',
        'has_archive'        => true,
        'hierarchical'       => false,
        'menu_position'      => 5,
        'menu_icon'          => 'dashicons-portfolio',
        'supports'           => array( 'title', 'editor', 'thumbnail', 'excerpt' ),
        'show_in_rest'       => true, // Enable Gutenberg
    );
    
    register_post_type( 'portfolio', $args );
}
add_action( 'init', 'mytheme_register_portfolio_cpt' );

Creating Custom Taxonomies

Register Custom Taxonomy

<?php
/**
 * Register Project Type Taxonomy
 */
function mytheme_register_project_type_taxonomy() {
    
    $labels = array(
        'name'              => _x( 'Project Types', 'taxonomy general name', 'mytheme' ),
        'singular_name'     => _x( 'Project Type', 'taxonomy singular name', 'mytheme' ),
        'search_items'      => __( 'Search Project Types', 'mytheme' ),
        'all_items'         => __( 'All Project Types', 'mytheme' ),
        'parent_item'       => __( 'Parent Project Type', 'mytheme' ),
        'parent_item_colon' => __( 'Parent Project Type:', 'mytheme' ),
        'edit_item'         => __( 'Edit Project Type', 'mytheme' ),
        'update_item'       => __( 'Update Project Type', 'mytheme' ),
        'add_new_item'      => __( 'Add New Project Type', 'mytheme' ),
        'new_item_name'     => __( 'New Project Type Name', 'mytheme' ),
        'menu_name'         => __( 'Project Types', 'mytheme' ),
    );
    
    $args = array(
        'hierarchical'      => true, // Like categories
        'labels'            => $labels,
        'show_ui'           => true,
        'show_admin_column' => true,
        'query_var'         => true,
        'rewrite'           => array( 'slug' => 'project-type' ),
        'show_in_rest'      => true,
    );
    
    register_taxonomy( 'project_type', array( 'portfolio' ), $args );
}
add_action( 'init', 'mytheme_register_project_type_taxonomy' );

Key Parameters

Parameter Description Default
public Whether post type is public false
has_archive Enable post type archives false
hierarchical Whether post type is hierarchical false
supports Core features to support title, editor
show_in_rest Enable REST API and Gutenberg false

CPT Template Files

single-portfolio.php

<?php
/**
 * Single Portfolio Template
 */
get_header(); ?>

<div class="portfolio-single">
    <?php while ( have_posts() ) : the_post(); ?>
        <article id="post-<?php the_ID(); ?>" <?php post_class(); ?>>
            <h1><?php the_title(); ?></h1>
            
            <?php if ( has_post_thumbnail() ) : ?>
                <?php the_post_thumbnail( 'large' ); ?>
            <?php endif; ?>
            
            <?php the_content(); ?>
            
            <?php
            // Display custom taxonomy terms
            $terms = get_the_terms( get_the_ID(), 'project_type' );
            if ( $terms && ! is_wp_error( $terms ) ) : ?>
                <div class="project-types">
                    <?php foreach ( $terms as $term ) : ?>
                        <a href="<?php echo get_term_link( $term ); ?>">
                            <?php echo $term->name; ?>
                        </a>
                    <?php endforeach; ?>
                </div>
            <?php endif; ?>
        </article>
    <?php endwhile; ?>
</div>

<?php get_footer(); ?>

archive-portfolio.php

<?php
/**
 * Portfolio Archive Template
 */
get_header(); ?>

<div class="portfolio-archive">
    <h1>Portfolio</h1>
    
    <?php if ( have_posts() ) : ?>
        <div class="portfolio-grid">
            <?php while ( have_posts() ) : the_post(); ?>
                <article class="portfolio-item">
                    <a href="<?php the_permalink(); ?>">
                        <?php if ( has_post_thumbnail() ) : ?>
                            <?php the_post_thumbnail( 'medium' ); ?>
                        <?php endif; ?>
                        <h3><?php the_title(); ?></h3>
                    </a>
                </article>
            <?php endwhile; ?>
        </div>
        
        <?php the_posts_pagination(); ?>
    <?php endif; ?>
</div>

<?php get_footer(); ?>

Querying Custom Post Types

Query Examples

<?php
// Basic CPT Query
$args = array(
    'post_type'      => 'portfolio',
    'posts_per_page' => 10,
);
$portfolio_query = new WP_Query( $args );

// Query with Taxonomy
$args = array(
    'post_type' => 'portfolio',
    'tax_query' => array(
        array(
            'taxonomy' => 'project_type',
            'field'    => 'slug',
            'terms'    => 'web-design',
        ),
    ),
);
$query = new WP_Query( $args );

// Query with Meta Fields
$args = array(
    'post_type'  => 'portfolio',
    'meta_key'   => 'project_year',
    'meta_value' => '2024',
    'orderby'    => 'meta_value',
    'order'      => 'DESC',
);
$query = new WP_Query( $args );

Best Practices

Development Best Practices

  • Use descriptive names: Choose clear, semantic post type names
  • Flush rewrite rules: Only on activation/deactivation
  • Enable REST API: Set show_in_rest to true for Gutenberg
  • Create proper templates: Use template hierarchy correctly
  • Add admin columns: Make content manageable in admin
  • Use proper capabilities: Set appropriate user permissions
  • Sanitize meta data: Always sanitize custom field input
Never call flush_rewrite_rules() on every page load. Only flush on activation/deactivation or when CPT settings change.

Practice Exercise

💻
Build Complete CPT System

Create a comprehensive custom content system that includes:

  1. Register a "Products" custom post type
  2. Create hierarchical "Product Categories" taxonomy
  3. Create non-hierarchical "Features" taxonomy
  4. Add meta boxes for price, SKU, and availability
  5. Create single-product.php template
  6. Create archive-product.php template
  7. Add custom admin columns for price and SKU
  8. Make admin columns sortable
  9. Create a shortcode to display products
  10. Add REST API support for headless usage

Additional Resources