📦 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
🎨 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