WordPress ships with Posts and Pages, but most real-world projects demand something more: portfolios, products, events, testimonials. That is exactly what Custom Post Types (CPTs) solve — and since WordPress 3.0 introduced register_post_type(), they have been the backbone of serious WordPress development.
Registering a Custom Post Type
All you need is a call to register_post_type() inside a function hooked to init. The example below creates a Portfolio CPT:
function my_register_portfolio_cpt() {
$args = array(
'label' => __( 'Portfolio', 'mytheme' ),
'public' => true,
'has_archive' => true,
'supports' => array( 'title', 'editor', 'thumbnail' ),
'rewrite' => array( 'slug' => 'portfolio' ),
'menu_icon' => 'dashicons-portfolio',
);
register_post_type( 'portfolio', $args );
}
add_action( 'init', 'my_register_portfolio_cpt' );
Once registered, WordPress automatically creates admin-menu entries, archive and single template hooks, and includes the CPT in queries when public is true.
Adding Custom Meta Boxes
CPTs rarely travel alone — they carry extra data. Before Advanced Custom Fields became ubiquitous, developers wrote meta boxes by hand using add_meta_box() and save_post. This approach gives you complete control over sanitisation and output:
add_action( 'add_meta_boxes', function() {
add_meta_box( 'portfolio_url', 'Project URL', 'portfolio_url_cb', 'portfolio' );
} );
function portfolio_url_cb( $post ) {
$url = get_post_meta( $post->ID, '_portfolio_url', true );
echo '<input type="url" name="portfolio_url" value="' . esc_attr( $url ) . '">';
}
add_action( 'save_post_portfolio', function( $post_id ) {
if ( isset( $_POST['portfolio_url'] ) ) {
update_post_meta( $post_id, '_portfolio_url', esc_url_raw( $_POST['portfolio_url'] ) );
}
} );
Displaying CPT Content
WordPress follows the Template Hierarchy for CPTs. Create archive-portfolio.php for the listing page and single-portfolio.php for individual entries. Use WP_Query to fetch CPT posts anywhere on the site:
$projects = new WP_Query( array(
'post_type' => 'portfolio',
'posts_per_page' => 6,
'post_status' => 'publish',
) );
Taxonomies
Pair your CPT with a custom taxonomy via register_taxonomy() — for example a Project Type taxonomy linked to Portfolio. This gives editors a familiar category-like interface and keeps your query logic clean.
Custom Post Types are the foundation of scalable WordPress development. Master them now and the rest of the WordPress CMS landscape opens up.