WordPress Custom Post Types

WordPress ships with two built-in content types: Posts (for blog articles) and Pages (for static pages). But many websites need to manage fundamentally different types of content — products, portfolios, events, testimonials, or recipes. This is exactly what Custom Post Types are for.

This guide explains what Custom Post Types are, why you should use them, and how to create them both with a no-code plugin (CPT UI) and with PHP code for developers.

1. What Is a Custom Post Type?

A Custom Post Type (CPT) is a content type you create in WordPress beyond the built-in Post and Page types. Think of it as adding a new "drawer" to the WordPress Admin for organizing a specific kind of structured content.

Common Custom Post Type examples:

2. How CPTs Differ From Posts and Pages

TypeUsed ForHas Category/Tag?Sorted By
PostBlog articles, newsYesDate
PagePermanent pages (About, Contact)NoHierarchy (Parent/Child)
Custom Post TypeSpecialized content — configurableConfigurableConfigurable

The power of CPTs is that you choose exactly which features they have — categories, tags, featured images, custom fields, excerpts — making your content more organized and structured than using generic Posts.

3. Creating a CPT With a Plugin — Custom Post Type UI

CPT UI is the most user-friendly free plugin for creating Custom Post Types without any coding required.

Installing and Using CPT UI

  1. Go to Plugins → Add New, search "Custom Post Type UI", install and activate
  2. Navigate to CPT UI → Add/Edit Post Types
  3. Enter the Post Type Slug (e.g., portfolio) — lowercase, no spaces
  4. Enter the Plural Label (e.g., "Portfolio") and Singular Label (e.g., "Portfolio Item")
  5. In Supports, select: Title, Editor, Thumbnail, Excerpt as needed
  6. Set Has Archive to True if you want a listing page (e.g., /portfolio/)
  7. Click Add Post Type — done!

A "Portfolio" menu item will now appear in the WordPress Admin Sidebar and you can immediately start adding entries.

Tip: CPT UI also lets you create Custom Taxonomies — category-like groupings specific to your CPT. For example, create a "Portfolio Category" separate from standard post categories. Go to CPT UI → Add/Edit Taxonomies.

4. Creating a CPT With Code — register_post_type()

The code-based approach gives developers complete flexibility without plugin dependency. Add this to your Child Theme's functions.php file only.

/**
 * Register Custom Post Type: Portfolio
 */
function asiagb_register_portfolio() {
    $labels = array(
        'name'               => 'Portfolio',
        'singular_name'      => 'Portfolio Item',
        'add_new'            => 'Add New',
        'add_new_item'       => 'Add New Portfolio Item',
        'edit_item'          => 'Edit Portfolio Item',
        'all_items'          => 'All Portfolio Items',
        'menu_name'          => 'Portfolio',
    );

    $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-images-alt2',
        'supports'           => array( 'title', 'editor', 'thumbnail', 'excerpt' ),
        'show_in_rest'       => true, // Required for Gutenberg Block Editor
    );

    register_post_type( 'portfolio', $args );
}
add_action( 'init', 'asiagb_register_portfolio' );

Important: Only edit a Child Theme's functions.php — never the parent theme. Changes to a parent theme are overwritten when the theme updates. If you don't have a Child Theme yet, create one first.

5. Adding Custom Fields With ACF

By default, Custom Post Types support only a title, editor content, and featured image. For specialized fields — price, project URL, event date, or ingredients list — use ACF (Advanced Custom Fields).

Adding Custom Fields With ACF

  1. Install the free Advanced Custom Fields plugin from WordPress.org
  2. Go to Custom Fields → Add New
  3. Name the Field Group (e.g., "Portfolio Details")
  4. Click Add Field and choose a Field Type:
    • Text — for short text like a client name
    • URL — for a live project link
    • Image — for additional gallery images
    • Date Picker — for a project completion date
    • Select — for a dropdown category
  5. In Location Rules, set "Post Type" is equal to "Portfolio"
  6. Click Publish — the fields appear immediately in the Portfolio editor

6. Displaying CPT Content in Your Theme With WP_Query

Once you've registered a Custom Post Type, use WP_Query in your template files to fetch and display the content.

<?php
// Fetch the 12 most recent Portfolio items
$portfolio_query = new WP_Query( array(
    'post_type'      => 'portfolio',
    'posts_per_page' => 12,
    'orderby'        => 'date',
    'order'          => 'DESC',
) );

if ( $portfolio_query->have_posts() ) :
    while ( $portfolio_query->have_posts() ) : $portfolio_query->the_post(); ?>

    <article class="portfolio-item">
        <a href="<?php the_permalink(); ?>">
            <?php the_post_thumbnail( 'medium' ); ?>
            <h3><?php the_title(); ?></h3>
        </a>
        <!-- Display ACF field -->
        <p><?php echo get_field( 'client_name' ); ?></p>
    </article>

    <?php endwhile;
    wp_reset_postdata();
else :
    echo '<p>No portfolio items found.</p>';
endif; ?>

9. Custom Taxonomies — Organize CPT Content Your Way

A Custom Taxonomy is a classification system you create specifically for a Custom Post Type — separate from WordPress's built-in Categories and Tags. Dedicated taxonomies give your structured content a much cleaner organizational hierarchy. Examples:

Registering a Custom Taxonomy in Code

/**
 * Register Custom Taxonomy: Project Type for Portfolio
 */
function asiagb_register_portfolio_taxonomy() {
    $labels = array(
        'name'          => 'Project Types',
        'singular_name' => 'Project Type',
        'all_items'     => 'All Project Types',
        'edit_item'     => 'Edit Project Type',
        'add_new_item'  => 'Add New Project Type',
    );
    register_taxonomy(
        'portfolio_type',        // taxonomy slug
        array( 'portfolio' ),    // attach to portfolio CPT
        array(
            'hierarchical' => true,  // true = category-like, false = tag-like
            'labels'       => $labels,
            'show_ui'      => true,
            'show_in_rest' => true,
            'rewrite'      => array( 'slug' => 'portfolio-type' ),
        )
    );
}
add_action( 'init', 'asiagb_register_portfolio_taxonomy' );

Note: hierarchical => true creates a Category-style taxonomy with parent/child terms. Setting it to false creates a Tag-style flat list. Choose based on how your content needs to be organized.

10. Filtering CPT Content by Taxonomy With WP_Query

Once you have a Custom Taxonomy in place, you can filter WP_Query results using tax_query to display only portfolio items in a specific project type — perfect for building filtered gallery pages.

<?php
// Fetch only "Web Design" portfolio items
$args = array(
    'post_type'  => 'portfolio',
    'tax_query'  => array(
        array(
            'taxonomy' => 'portfolio_type',
            'field'    => 'slug',
            'terms'    => 'web-design',
        ),
    ),
    'posts_per_page' => 6,
);
$query = new WP_Query( $args );

while ( $query->have_posts() ) : $query->the_post();
    echo '<h3>' . get_the_title() . '</h3>';
    echo '<p>' . get_the_excerpt() . '</p>';
endwhile;
wp_reset_postdata();

Advanced tip: Use get_terms( 'portfolio_type' ) to retrieve all taxonomy terms and build a dynamic filter menu — for example, tabs labeled "All | Web Design | Branding | Photography" — without hardcoding anything.

11. Custom Post Type Template Files in Your Theme

WordPress automatically looks for theme template files specific to each Custom Post Type using its Template Hierarchy. You can create these files inside your Child Theme to fully customize the display:

Template File Used For Example
single-{post_type}.php Single CPT entry page single-portfolio.php
archive-{post_type}.php Archive listing page archive-portfolio.php
taxonomy-{taxonomy}.php Taxonomy term page taxonomy-portfolio_type.php
single.php Fallback if no specific template exists Used when single-portfolio.php is absent

WordPress selects the most specific template available. If single-portfolio.php exists it uses that file; otherwise it falls back through the hierarchy to single.php. Creating CPT-specific templates gives you complete design control without affecting other content types.

7. Flushing Permalink Rules After Registration

After creating a new Custom Post Type, you must flush the permalink rules so WordPress generates the correct URL routing. Without this step, single post and archive pages will return 404 errors.

How to flush permalinks:

  1. Go to Settings → Permalinks in WordPress Admin
  2. Click Save Changes without modifying anything
  3. WordPress will regenerate the rewrite rules automatically

Note: Always flush permalinks when adding a new CPT, and also whenever you activate or deactivate the CPT UI plugin.

8. Using Custom Post Types With Gutenberg

To make your Custom Post Type compatible with the Gutenberg Block Editor, verify that 'show_in_rest' => true is set in register_post_type().

For CPT UI: go to CPT UI → Add/Edit Post Types and set Show in REST API to True.

This setting also enables the post type to work with REST API integrations, headless WordPress setups, and modern page builders that depend on the block editor infrastructure.

12. Querying CPT Data Through the REST API

Once 'show_in_rest' => true is set, WordPress automatically creates REST API endpoints for your Custom Post Type. This opens the door to headless WordPress setups, mobile apps, and SPAs that consume WordPress content without a traditional theme.

Example endpoints generated automatically for a CPT named portfolio:

# Retrieve all portfolio items (JSON)
GET /wp-json/wp/v2/portfolio

# Retrieve a single portfolio item by ID
GET /wp-json/wp/v2/portfolio/123

# Filter by taxonomy term
GET /wp-json/wp/v2/portfolio?portfolio_type=web-design

# Retrieve with ACF fields (requires ACF + REST API enabled on field group)
GET /wp-json/wp/v2/portfolio?_fields=id,title,acf

Security note: REST API endpoints are public by default. If your CPT contains sensitive data, combine 'public' => false with 'show_in_rest' => true to require authentication for API access while still supporting the block editor.

13. Caching WP_Query Results for Custom Post Types

On high-traffic sites, repeatedly querying the same Custom Post Type can increase database load. WordPress Transients provide a simple built-in caching mechanism that stores query results temporarily, reducing the number of database calls.

Caching With the Transient API

<?php
// Check for cached results first
$portfolio_items = get_transient( 'portfolio_all' );

if ( false === $portfolio_items ) {
    $q = new WP_Query( array(
        'post_type'              => 'portfolio',
        'posts_per_page'         => 20,
        'no_found_rows'          => true, // skip SQL_CALC_FOUND_ROWS
        'update_post_meta_cache' => true,
    ) );

    $portfolio_items = $q->posts;
    // Cache for 1 hour (3600 seconds)
    set_transient( 'portfolio_all', $portfolio_items, HOUR_IN_SECONDS );
}

foreach ( $portfolio_items as $post ) {
    setup_postdata( $post );
    the_title( '<h3>', '</h3>' );
}
wp_reset_postdata();

Always clear the transient cache when portfolio items are added, updated, or deleted by hooking into save_post_portfolio to ensure the front end always shows current data.

Tip: Setting 'no_found_rows' => true skips the SQL_CALC_FOUND_ROWS operation — the slowest part of WP_Query — whenever you don't need pagination count data.

14. Migrating CPT Data Between Environments With WP-CLI

When moving Custom Post Type content from a development environment to production — or creating targeted backups — WP-CLI offers more precision than the standard WordPress import/export tools.

WP-CLI Command What It Does
wp post list --post_type=portfolio List all portfolio items with their IDs
wp export --post_type=portfolio Export only portfolio items to an XML file
wp import portfolio.xml --authors=create Import portfolio XML into a new WordPress install
wp post delete $(wp post list --post_type=portfolio --format=ids) Delete all portfolio items in a single command

WP-CLI runs over SSH, making it ideal for hosting environments with shell access — such as AsiaGB VPS plans that include full root access.

WordPress Hosting That Supports Custom Post Types and ACF

AsiaGB Hosting fully supports WordPress with multiple PHP versions, MySQL databases, and an easy-to-use DirectAdmin control panel. Starting at 500 THB/year.

View Hosting Plans →