Register custom post types (CPTs) and taxonomies in functions.php or a dedicated plugin file. Never in theme files if the content needs to persist across theme changes.
Register a Custom Post Type
function register_custom_post_types() {
register_post_type('portfolio', [
'labels' => [
'name' => __('Portfolio', 'my-theme'),
'singular_name' => __('Project', 'my-theme'),
'add_new_item' => __('Add New Project', 'my-theme'),
'edit_item' => __('Edit Project', 'my-theme'),
'view_item' => __('View Project', 'my-theme'),
'search_items' => __('Search Portfolio', 'my-theme'),
'not_found' => __('No projects found', 'my-theme'),
],
'public' => true,
'has_archive' => true,
'menu_icon' => 'dashicons-portfolio',
'supports' => ['title', 'editor', 'thumbnail', 'excerpt', 'custom-fields'],
'rewrite' => ['slug' => 'portfolio', 'with_front' => false],
'show_in_rest' => true, // Required for Gutenberg support
'menu_position' => 5,
]);
}
add_action('init', 'register_custom_post_types');
Register a Custom Taxonomy
function register_custom_taxonomies() {
register_taxonomy('project_type', ['portfolio'], [
'labels' => [
'name' => __('Project Types', 'my-theme'),
'singular_name' => __('Project Type', 'my-theme'),
'search_items' => __('Search Project Types', 'my-theme'),
'all_items' => __('All Project Types', 'my-theme'),
'edit_item' => __('Edit Project Type', 'my-theme'),
'update_item' => __('Update Project Type', 'my-theme'),
'add_new_item' => __('Add New Project Type', 'my-theme'),
],
'hierarchical' => true, // true = category-like, false = tag-like
'public' => true,
'show_in_rest' => true,
'rewrite' => ['slug' => 'project-type'],
]);
}
add_action('init', 'register_custom_taxonomies');
Flush Rewrite Rules
After registering new CPTs, flush rewrite rules once:
// In activation hook of a plugin
register_activation_hook(__FILE__, function() {
register_custom_post_types();
flush_rewrite_rules();
});
Or navigate to Settings > Permalinks and click Save in WordPress admin.
Query CPT Posts
$projects = new WP_Query([
'post_type' => 'portfolio',
'posts_per_page' => 12,
'tax_query' => [
[
'taxonomy' => 'project_type',
'field' => 'slug',
'terms' => 'web-design',
],
],
'orderby' => 'date',
'order' => 'DESC',
]);
Common Dashicons for Menu Icons
dashicons-portfolio, dashicons-testimonial, dashicons-location-alt, dashicons-star-filled, dashicons-hammer, dashicons-admin-tools