WordPress
wordpress
custom post types
wordpress development
content types
CPT

WordPress Custom Post Types: When and How to Use Them

WordPress ships with two main content types: posts and pages. But what if you need to manage portfolios, testimonials, products, events, or team members? Custom Post Types (CPTs) allow you to create d...

Bibin WilsonAuthor
January 14, 2026
8 min read
0 views
Introduction

WordPress ships with two main content types: posts and pages. But what if you need to manage portfolios, testimonials, products, events, or team members? Custom Post Types (CPTs) allow you to create distinct content types with their own admin menus, templates, and organization systems. This guide explains when to use CPTs and how to implement them effectively.

Understanding Custom Post Types
What Are Custom Post Types?

Custom Post Types are content types beyond the default posts and pages. They let you create structured content with:

  • Custom admin menus
  • Separate management interfaces
  • Custom fields and metadata
  • Unique URL structures
  • Dedicated templates
Default WordPress Post Types

WordPress includes these built-in post types:

Post Type Purpose Default
post Blog articles Yes
page Static pages Yes
attachment Media files Yes
revision Content versions Yes
nav_menu_item Menu items Yes
Why Use Custom Post Types?

Organization:

  • Separate content logically
  • Dedicated admin sections
  • Custom categorization

Functionality:

  • Type-specific features
  • Custom display templates
  • Unique metadata

User Experience:

  • Cleaner admin interface
  • Content-specific editors
  • Role-based permissions
When to Create Custom Post Types
Good Use Cases

Portfolio/Projects:

  • Creative agencies
  • Photographers
  • Developers

Testimonials:

  • Service businesses
  • E-commerce
  • B2B companies

Team Members:

  • Corporate sites
  • Agency websites
  • Professional services

Products (non-WooCommerce):

  • Simple catalogs
  • Software products
  • Service packages

Events:

  • Conferences
  • Meetups
  • Scheduled activities

Locations:

  • Store locators
  • Real estate
  • Travel sites

Case Studies:

  • B2B services
  • Consulting firms
  • Agencies
When NOT to Use CPTs

Avoid When:

  • Blog post categories would suffice
  • Content shares structure with posts
  • Only need slight variations
  • Plugin already provides functionality

Use Regular Posts Instead For:

  • Different article categories
  • Guest posts vs staff posts
  • Featured vs regular content
Creating Custom Post Types

In Child Theme functions.php:

<?php
/**
 * Register Custom Post Type: Portfolio
 */
function register_portfolio_cpt() {

    $labels = array(
        'name'                  => 'Portfolio',
        'singular_name'         => 'Portfolio Item',
        'menu_name'             => 'Portfolio',
        'name_admin_bar'        => 'Portfolio Item',
        'add_new'               => 'Add New',
        'add_new_item'          => 'Add New Portfolio Item',
        'new_item'              => 'New Portfolio Item',
        'edit_item'             => 'Edit Portfolio Item',
        'view_item'             => 'View Portfolio Item',
        'all_items'             => 'All Portfolio Items',
        'search_items'          => 'Search Portfolio',
        'not_found'             => 'No portfolio items found.',
        'not_found_in_trash'    => 'No portfolio items found in Trash.',
        'archives'              => 'Portfolio Archives',
    );

    $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', 'register_portfolio_cpt');

Key Arguments Explained:

Argument Purpose Common Values
public Visible on front-end true/false
show_ui Show in admin true/false
show_in_menu Add to admin menu true/false
has_archive Archive page true/false
hierarchical Parent/child (like pages) true/false
supports Editor features title, editor, thumbnail, etc.
show_in_rest Gutenberg/API true/false
menu_icon Admin icon dashicons-* or URL
rewrite URL structure array with 'slug'
Method 2: Using Plugin (Easier)

Custom Post Type UI (CPT UI):

  1. Install "Custom Post Type UI" plugin
  2. Go to CPT UI > Add/Edit Post Types
  3. Fill in:
    • Post Type Slug
    • Labels
    • Settings
  4. Click "Add Post Type"

Pros:

  • No coding required
  • Visual interface
  • Export to PHP code

Cons:

  • Plugin dependency
  • Less control
  • Potential conflicts
Method 3: Using ACF (Advanced)

Advanced Custom Fields Pro:

  1. Install ACF Pro
  2. Go to ACF > Post Types
  3. Create new post type
  4. Add custom fields
  5. Configure settings
Adding Custom Taxonomies
What Are Custom Taxonomies?

Taxonomies organize content (like categories/tags for posts). Create custom ones for CPTs.

Register Custom Taxonomy:

<?php
/**
 * Register Custom Taxonomy: Portfolio Category
 */
function register_portfolio_taxonomy() {

    $labels = array(
        'name'              => 'Portfolio Categories',
        'singular_name'     => 'Portfolio Category',
        'search_items'      => 'Search Categories',
        'all_items'         => 'All Categories',
        'parent_item'       => 'Parent Category',
        'parent_item_colon' => 'Parent Category:',
        'edit_item'         => 'Edit Category',
        'update_item'       => 'Update Category',
        'add_new_item'      => 'Add New Category',
        'new_item_name'     => 'New Category Name',
        'menu_name'         => 'Categories',
    );

    $args = array(
        'hierarchical'      => true, // Like categories (false = like tags)
        'labels'            => $labels,
        'show_ui'           => true,
        'show_admin_column' => true,
        'query_var'         => true,
        'rewrite'           => array('slug' => 'portfolio-category'),
        'show_in_rest'      => true,
    );

    register_taxonomy('portfolio_category', array('portfolio'), $args);
}
add_action('init', 'register_portfolio_taxonomy');
Creating Templates for CPTs
Template Hierarchy

WordPress looks for templates in this order:

Single Post:

  1. single-{post_type}-{slug}.php
  2. single-{post_type}.php
  3. single.php
  4. singular.php
  5. index.php

Archive:

  1. archive-{post_type}.php
  2. archive.php
  3. index.php
Creating Single Template

Create single-portfolio.php in child theme:

<?php
/**
 * Template for single portfolio items
 */
get_header();
?>

<main class="portfolio-single">
    <?php while (have_posts()) : the_post(); ?>

        <article id="post-<?php the_ID(); ?>" <?php post_class(); ?>>

            <?php if (has_post_thumbnail()) : ?>
                <div class="portfolio-featured-image">
                    <?php the_post_thumbnail('large'); ?>
                </div>
            <?php endif; ?>

            <header class="entry-header">
                <h1><?php the_title(); ?></h1>

                <?php
                // Display portfolio categories
                $terms = get_the_terms(get_the_ID(), 'portfolio_category');
                if ($terms && !is_wp_error($terms)) :
                ?>
                    <div class="portfolio-categories">
                        <?php foreach ($terms as $term) : ?>
                            <span class="category"><?php echo esc_html($term->name); ?></span>
                        <?php endforeach; ?>
                    </div>
                <?php endif; ?>
            </header>

            <div class="entry-content">
                <?php the_content(); ?>
            </div>

        </article>

    <?php endwhile; ?>
</main>

<?php get_footer(); ?>
Creating Archive Template

Create archive-portfolio.php in child theme:

<?php
/**
 * Template for portfolio archive
 */
get_header();
?>

<main class="portfolio-archive">

    <header class="archive-header">
        <h1>Our Portfolio</h1>
    </header>

    <?php if (have_posts()) : ?>

        <div class="portfolio-grid">
            <?php while (have_posts()) : the_post(); ?>

                <article class="portfolio-item">
                    <?php if (has_post_thumbnail()) : ?>
                        <a href="<?php the_permalink(); ?>">
                            <?php the_post_thumbnail('medium'); ?>
                        </a>
                    <?php endif; ?>

                    <h2>
                        <a href="<?php the_permalink(); ?>">
                            <?php the_title(); ?>
                        </a>
                    </h2>

                    <?php the_excerpt(); ?>
                </article>

            <?php endwhile; ?>
        </div>

        <?php the_posts_pagination(); ?>

    <?php else : ?>
        <p>No portfolio items found.</p>
    <?php endif; ?>

</main>

<?php get_footer(); ?>
Adding Custom Fields

Advanced Custom Fields makes adding custom fields easy:

  1. Install ACF plugin
  2. Go to ACF > Field Groups
  3. Add New Field Group
  4. Create fields:
    • Text fields
    • Image fields
    • Date fields
    • Relationship fields
    • etc.
  5. Set location rules for your CPT
  6. Publish

Display ACF Fields in Template:

<?php
// Text field
$client_name = get_field('client_name');
echo '<p>Client: ' . esc_html($client_name) . '</p>';

// Image field (returns array)
$project_image = get_field('project_image');
if ($project_image) {
    echo '<img src="' . esc_url($project_image['url']) . '" alt="' . esc_attr($project_image['alt']) . '">';
}

// Repeater field
if (have_rows('gallery')) :
    while (have_rows('gallery')) : the_row();
        $image = get_sub_field('image');
        echo '<img src="' . esc_url($image['url']) . '">';
    endwhile;
endif;
?>
Using Native Meta Boxes

Register Meta Box:

<?php
function portfolio_meta_boxes() {
    add_meta_box(
        'portfolio_details',
        'Project Details',
        'portfolio_meta_callback',
        'portfolio',
        'normal',
        'high'
    );
}
add_action('add_meta_boxes', 'portfolio_meta_boxes');

function portfolio_meta_callback($post) {
    wp_nonce_field('portfolio_meta_nonce', 'portfolio_nonce');

    $client = get_post_meta($post->ID, '_portfolio_client', true);
    $url = get_post_meta($post->ID, '_portfolio_url', true);
    ?>

    <p>
        <label>Client Name:</label>
        <input type="text" name="portfolio_client" value="<?php echo esc_attr($client); ?>">
    </p>
    <p>
        <label>Project URL:</label>
        <input type="url" name="portfolio_url" value="<?php echo esc_url($url); ?>">
    </p>

    <?php
}

function save_portfolio_meta($post_id) {
    if (!isset($_POST['portfolio_nonce'])) return;
    if (!wp_verify_nonce($_POST['portfolio_nonce'], 'portfolio_meta_nonce')) return;
    if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) return;

    if (isset($_POST['portfolio_client'])) {
        update_post_meta($post_id, '_portfolio_client', sanitize_text_field($_POST['portfolio_client']));
    }
    if (isset($_POST['portfolio_url'])) {
        update_post_meta($post_id, '_portfolio_url', esc_url_raw($_POST['portfolio_url']));
    }
}
add_action('save_post_portfolio', 'save_portfolio_meta');
Querying Custom Post Types
Basic Query
<?php
$portfolio_query = new WP_Query(array(
    'post_type'      => 'portfolio',
    'posts_per_page' => 6,
    'orderby'        => 'date',
    'order'          => 'DESC',
));

if ($portfolio_query->have_posts()) :
    while ($portfolio_query->have_posts()) : $portfolio_query->the_post();
        // Display content
    endwhile;
    wp_reset_postdata();
endif;
?>
Query with Taxonomy
<?php
$portfolio_query = new WP_Query(array(
    'post_type'      => 'portfolio',
    'posts_per_page' => 6,
    'tax_query'      => array(
        array(
            'taxonomy' => 'portfolio_category',
            'field'    => 'slug',
            'terms'    => 'web-design',
        ),
    ),
));
?>
Query with Custom Field
<?php
$portfolio_query = new WP_Query(array(
    'post_type'      => 'portfolio',
    'posts_per_page' => -1,
    'meta_query'     => array(
        array(
            'key'     => '_portfolio_featured',
            'value'   => '1',
            'compare' => '=',
        ),
    ),
));
?>
Best Practices
Naming Conventions
// Post type name: lowercase, no spaces, max 20 chars
'portfolio'  // Good
'Portfolio'  // Bad (uppercase)
'my-portfolio' // Acceptable but avoid hyphens
'my_portfolio' // Good for longer names

// Function prefix: unique to avoid conflicts
'mytheme_register_portfolio' // Good
'register_portfolio' // Could conflict
Performance Considerations
  1. Limit queries:

    • Use posts_per_page limit
    • Cache results when possible
    • Avoid posts_per_page => -1 on large sites
  2. Database optimization:

    • Index custom meta fields
    • Clean up revisions
    • Limit transients
  3. Template efficiency:

    • Use specific templates
    • Avoid unnecessary queries
    • Lazy load images

After registering CPTs, flush permalinks:

  1. Go to Settings > Permalinks
  2. Click "Save Changes" (no changes needed)
  3. Or use: flush_rewrite_rules(); (once)
Common CPT Patterns
Testimonials
register_post_type('testimonial', array(
    'labels'       => array('name' => 'Testimonials'),
    'public'       => true,
    'has_archive'  => false, // Usually no archive needed
    'supports'     => array('title', 'editor', 'thumbnail'),
    'menu_icon'    => 'dashicons-format-quote',
));
Team Members
register_post_type('team', array(
    'labels'       => array('name' => 'Team Members'),
    'public'       => true,
    'has_archive'  => true,
    'supports'     => array('title', 'editor', 'thumbnail'),
    'menu_icon'    => 'dashicons-groups',
));
Events
register_post_type('event', array(
    'labels'       => array('name' => 'Events'),
    'public'       => true,
    'has_archive'  => true,
    'supports'     => array('title', 'editor', 'thumbnail', 'excerpt'),
    'menu_icon'    => 'dashicons-calendar-alt',
));
Frequently Asked Questions
Will CPTs slow down my site?

Not inherently. Performance depends on queries and template efficiency, not the CPT itself.

Can I convert posts to CPTs?

Yes, using plugins like "Post Type Switcher" or database queries. Backup first.

Do CPTs work with page builders?

Yes, most page builders (Elementor, Divi) support CPT templates.

What about CPTs and SEO?

CPTs are SEO-friendly. Configure with your SEO plugin (Yoast, RankMath) for sitemaps and meta.

Can different user roles access different CPTs?

Yes, using capability_type argument and custom capabilities.

Key Takeaways
  • CPTs create structured content beyond posts and pages
  • Use when content needs distinct management and display
  • Register via code (preferred) or plugins
  • Custom taxonomies organize CPT content
  • Create dedicated templates for archives and singles
  • ACF simplifies custom field management
  • Always flush permalinks after CPT changes
Next Steps

Learn to enhance your CPTs with our guide on Advanced Custom Fields, or explore WordPress Template Hierarchy for more display control.


Meta Description: Learn when and how to create WordPress Custom Post Types. Complete guide covering registration, taxonomies, templates, custom fields, and best practices.

Keywords: wordpress custom post types, CPT, wordpress development, content types, register post type

Frequently Asked Questions

Find answers to common questions about this topic

Not inherently. Performance depends on queries and template efficiency, not the CPT itself.
Yes, using plugins like "Post Type Switcher" or database queries. Backup first.
Yes, most page builders (Elementor, Divi) support CPT templates.
CPTs are SEO-friendly. Configure with your SEO plugin (Yoast, RankMath) for sitemaps and meta.
Yes, using capability_type argument and custom capabilities.

Ready to Invest in Premium Domains?

Browse our curated marketplace of high-quality domains and find your perfect investment