If you’re building a WordPress site and want to organize content beyond posts and pages—like portfolios, testimonials, or case studies—custom post types are the way to go. While plugins like CPT UI make this easy, you can create them manually with just a few lines of code. This keeps your site lightweight and gives you full control over the structure.
✅ Why Use Custom Post Types?
- Separate content types for better organization
- Custom templates and taxonomies
- Cleaner admin interface
- No plugin bloat
🧩 How to Register a Custom Post Type Manually
To create a custom post type without a plugin, you’ll need to add code to your theme’s functions.php
file or a custom plugin. Here’s a quick overview of what the code does:
- Registers the post type with WordPress
- Sets labels, visibility, and supported features
- Adds it to the admin menu
💡 Tip: Always use a child theme or custom plugin to avoid losing changes during theme updates.
<?php
function register_projects_post_type() {
// Labels for the custom post type
$labels = array(
'name' => _x( 'Projects', 'Post type general name', 'textdomain' ),
'singular_name' => _x( 'Project', 'Post type singular name', 'textdomain' ),
'menu_name' => _x( 'Projects', 'Admin Menu text', 'textdomain' ),
'name_admin_bar' => _x( 'Project', 'Add New on Toolbar', 'textdomain' ),
'add_new' => __( 'Add New', 'textdomain' ),
'add_new_item' => __( 'Add New Project', 'textdomain' ),
'new_item' => __( 'New Project', 'textdomain' ),
'edit_item' => __( 'Edit Project', 'textdomain' ),
'view_item' => __( 'View Project', 'textdomain' ),
'all_items' => __( 'All Projects', 'textdomain' ),
'search_items' => __( 'Search Projects', 'textdomain' ),
'parent_item_colon' => __( 'Parent Project:', 'textdomain' ),
'not_found' => __( 'No Projects found.', 'textdomain' ),
'not_found_in_trash' => __( 'No Projects found in Trash.', 'textdomain' ),
'featured_image' => __( 'Project Featured Image', 'textdomain' ),
'set_featured_image' => __( 'Set featured image', 'textdomain' ),
'remove_featured_image' => __( 'Remove featured image', 'textdomain' ),
'use_featured_image' => __( 'Use as featured image', 'textdomain' ),
);
// Arguments for the custom post type
$args = array(
'labels' => $labels,
'public' => true,
'publicly_queryable' => true,
'show_ui' => true,
'show_in_menu' => true,
'query_var' => true,
'rewrite' => array( 'slug' => 'project' ),
'capability_type' => 'post',
'has_archive' => true,
'hierarchical' => false,
'menu_position' => 20,
'menu_icon' => 'dashicons-portfolio',
'supports' => array( 'title', 'editor', 'thumbnail', 'excerpt', 'comments', 'author', 'revisions' ),
'taxonomies' => array( 'project_category' ),
'show_in_rest' => true, // Enable Gutenberg editor support
);
// Register the custom post type
register_post_type( 'project', $args );
// Labels for the custom taxonomy (categories)
$taxonomy_labels = array(
'name' => _x( 'Project Categories', 'Taxonomy general name', 'textdomain' ),
'singular_name' => _x( 'Project Category', 'Taxonomy singular name', 'textdomain' ),
'search_items' => __( 'Search Project Categories', 'textdomain' ),
'all_items' => __( 'All Project Categories', 'textdomain' ),
'parent_item' => __( 'Parent Project Category', 'textdomain' ),
'parent_item_colon' => __( 'Parent Project Category:', 'textdomain' ),
'edit_item' => __( 'Edit Project Category', 'textdomain' ),
'update_item' => __( 'Update Project Category', 'textdomain' ),
'add_new_item' => __( 'Add New Project Category', 'textdomain' ),
'new_item_name' => __( 'New Project Category Name', 'textdomain' ),
'menu_name' => __( 'Project Categories', 'textdomain' ),
);
// Arguments for the custom taxonomy
$taxonomy_args = array(
'hierarchical' => true, // Category-like (true) vs tag-like (false)
'labels' => $taxonomy_labels,
'show_ui' => true,
'show_admin_column' => true,
'query_var' => true,
'rewrite' => array( 'slug' => 'project-category' ),
'show_in_rest' => true, // Enable for Gutenberg
);
// Register the custom taxonomy
register_taxonomy( 'project_category', array( 'project' ), $taxonomy_args );
}
// Hook into the 'init' action to register the post type and taxonomy
add_action( 'init', 'register_projects_post_type' );
?>
📄 Example Use Case
Let’s say you want to create a “Projects” post type for showcasing client work. You can define it with custom fields, categories, and even a custom archive page.
🔍 SEO Benefits
Custom post types help improve your site’s structure and crawlability. You can optimize URLs, titles, and meta descriptions for each type—making your content more discoverable.