Registering a custom post type with labels and REST support
WordPress ships with Posts and Pages out of the box, but real-world projects almost always need more. Custom post types let you organise different kinds of content — portfolio items, team members, events, testimonials — separately from standard blog posts, each with its own admin menu, labels, and permalink structure.
Problem: How do you register a custom post type in WordPress with proper labels, block editor support, and a working archive permalink?
Solution: The example below registers a portfolio post type with full labels, REST API support (added in WordPress 4.7), and a proper activation hook to flush rewrite rules — a step that's easy to miss but required for the archive permalink to work on the front end.
<?php
add_action( 'init', 'register_portfolio_cpt' );
function register_portfolio_cpt() {
$labels = [
'name' => __( 'Portfolio', 'textdomain' ),
'singular_name' => __( 'Project', 'textdomain' ),
'add_new_item' => __( 'Add New Project', 'textdomain' ),
'edit_item' => __( 'Edit Project', 'textdomain' ),
'new_item' => __( 'New Project', 'textdomain' ),
'view_item' => __( 'View Project', 'textdomain' ),
'search_items' => __( 'Search Portfolio', 'textdomain' ),
'not_found' => __( 'No projects found', 'textdomain' ),
'not_found_in_trash' => __( 'No projects in trash', 'textdomain' ),
'menu_name' => __( 'Portfolio', 'textdomain' ),
];
register_post_type( 'portfolio', [
'labels' => $labels,
'public' => true,
'has_archive' => true,
'rewrite' => [ 'slug' => 'portfolio' ],
'supports' => [ 'title', 'editor', 'thumbnail', 'excerpt' ],
'show_in_rest' => true, // enables the block editor (WP 4.7+)
'menu_icon' => 'dashicons-portfolio',
] );
}
// Flush rewrite rules on plugin activation — never on every request
register_activation_hook( __FILE__, function() {
register_portfolio_cpt();
flush_rewrite_rules();
} );
NOTE: Never call flush_rewrite_rules() on every page load — it is expensive and will noticeably slow your site. Call it only in an activation hook. If your post type archive returns a 404 after registration, go to Settings → Permalinks and click Save to rebuild the rules manually.