WordPress Custom Post Types Tutorial — Build Custom Content Like a Pro
WordPress custom post types let you create entirely new content types — like portfolios, products, testimonials, or events — beyond the default posts and pages. This wordpress custom post types tutorial will show you exactly how to register them with the register_post_type() function hooked to init, giving your content its own admin menu, archive pages, and full Block Editor support.
⚓ KEY TAKEAWAYS
- Custom post types extend WordPress far beyond posts and pages
register_post_type()is the core function — always hook it toinit- Always set
show_in_rest => truefor Block Editor (Gutenberg) support - Register CPTs in a dedicated plugin, not your theme’s
functions.php - Custom taxonomies pair with CPTs to give your content structure and filterable categories
What Are WordPress Custom Post Types?

WordPress ships with five default post types baked right in: post, page, attachment, revision, and nav_menu_item. They cover the basics. But the moment you try to build a portfolio, a recipe archive, an event calendar, or a product catalogue without WooCommerce, you hit a wall. That’s where this wordpress custom post types tutorial starts pulling weight.
Custom post types (CPTs) are additional content structures you define and register in WordPress. Think of them as new drawers in your content filing cabinet — same cabinet, completely different drawer. A “Movies” CPT, a “Testimonials” CPT, a “Case Studies” CPT — each gets its own admin section, its own URL structure, and its own template files.
Here’s the part most tutorials skip: WordPress stores every single post type in the same wp_posts database table. There’s no separate table for your custom movies or testimonials. WordPress simply uses a post_type column value to distinguish them. If you want to dig deeper into how that table is structured, check out our WordPress database structure breakdown — it’ll change how you think about content.
43%
of the entire internet runs on WordPress — and custom post types are the primary reason it can power everything from recipe blogs to enterprise directories.
How register_post_type() Actually Works

The heart of every wordpress custom post types tutorial is one function: register_post_type(). It accepts two arguments — the post type slug (max 20 characters, cannot start with wp_) and an array of arguments that controls every behavior, label, and capability of your new content type.
You must call it on the init action hook. WordPress isn’t ready for post type registration before that point. If you’re not fully clear on how WordPress hooks and actions work, read our WordPress hooks, actions, and filters explainer before going further — it’ll save you hours of debugging.
Here’s a stripped-down look at the function signature and the arguments that matter most in 2026:
register_post_type( string $post_type, array|string $args = array() )
The $args array is where the real power lives. Key parameters include:
- labels — controls all text strings in the admin UI
- public — whether the post type is publicly queryable and visible
- has_archive — enables an archive page at
/your-slug/ - show_in_rest — critical — enables REST API support and Block Editor
- supports — features like title, editor, thumbnail, excerpt, custom-fields
- menu_icon — the dashicon for the admin sidebar entry
- rewrite — controls the URL slug structure
🏴☠️ PIRATE TIP
If you forget to set show_in_rest => true, your CPT will open in the Classic Editor — that ancient, dusty relic. In 2026, that’s not just bad UX, it’s a red flag to every developer who touches your project. Always. Set. It. True.
Building Your First Custom Post Type Step by Step

No real wordpress custom post types tutorial skips the “where to put this code” question. The answer is a dedicated plugin file — not your theme’s functions.php. Why? Because if you ever switch themes, every post type registered in functions.php disappears from the admin. Your content still exists in the database, but WordPress can’t find it. That’s a content hostage situation you do not want.
Create a new folder in wp-content/plugins/ — call it aodn-custom-post-types. Inside it, create aodn-custom-post-types.php. Add your plugin header, and you’re ready to build. According to the WordPress Plugin Handbook on Custom Post Types, this is the recommended approach for portability.
Here’s a complete, production-ready example registering a “Movies” custom post type:
<?php
/**
* Plugin Name: AODN Custom Post Types
* Description: Registers custom post types for the site.
* Version: 1.0
*/
function aodn_register_movies_cpt() {
$labels = array(
'name' => 'Movies',
'singular_name' => 'Movie',
'add_new' => 'Add New Movie',
'add_new_item' => 'Add New Movie',
'edit_item' => 'Edit Movie',
'new_item' => 'New Movie',
'view_item' => 'View Movie',
'search_items' => 'Search Movies',
'not_found' => 'No movies found',
'not_found_in_trash' => 'No movies found in trash',
'menu_name' => 'Movies',
);
$args = array(
'labels' => $labels,
'public' => true,
'has_archive' => true,
'show_in_rest' => true, // CRITICAL for Block Editor
'supports' => array( 'title', 'editor', 'thumbnail', 'excerpt', 'custom-fields' ),
'menu_icon' => 'dashicons-video-alt2',
'rewrite' => array( 'slug' => 'movies' ),
);
register_post_type( 'movie', $args );
}
add_action( 'init', 'aodn_register_movies_cpt' );
Activate the plugin from your WordPress admin. Then go to Settings → Permalinks and click Save — this flushes the rewrite rules so your archive at /movies/ doesn’t throw a 404. That one step trips up more developers than any code error ever will.
Adding Custom Taxonomies to Your CPT

A custom post type without taxonomies is a filing cabinet with no folders. Taxonomies let you group and filter CPT content the same way categories and tags work for regular posts. You register them with register_taxonomy(), also hooked to init.
Taxonomies come in two flavors: hierarchical (like categories — parent/child structure with checkboxes in the editor) and non-hierarchical (like tags — free-form text input). Here’s how you’d add a “Genre” taxonomy to the Movies CPT from the previous section:
function aodn_register_genre_taxonomy() {
$labels = array(
'name' => 'Genres',
'singular_name' => 'Genre',
'search_items' => 'Search Genres',
'all_items' => 'All Genres',
'edit_item' => 'Edit Genre',
'add_new_item' => 'Add New Genre',
'menu_name' => 'Genres',
);
$args = array(
'labels' => $labels,
'hierarchical' => true, // true = category-style, false = tag-style
'public' => true,
'show_in_rest' => true,
'rewrite' => array( 'slug' => 'genre' ),
);
register_taxonomy( 'genre', 'movie', $args );
}
add_action( 'init', 'aodn_register_genre_taxonomy' );
“Custom taxonomies are the difference between a content archive and a searchable, filterable content system. Skip them and you’re leaving half the power of this wordpress custom post types tutorial on the table.”
— AI Or Die Now
⚓ READY TO LEVEL UP YOUR WORDPRESS ARSENAL?
CPTs are just the beginning. Our full toolkit covers every weapon a WordPress developer needs to ship faster and smarter.
Template Files for Custom Post Types

WordPress has a template hierarchy that automatically looks for specific theme files when rendering your CPT content. For a custom post type with the slug movie, WordPress will look for single-movie.php when displaying a single entry, and archive-movie.php when displaying the archive listing. If neither exists, it falls back to single.php and archive.php respectively.
If you’re working with a classic theme, create those files in your child theme — never directly in the parent theme. If you’re on a block theme using Full Site Editing, you’ll instead create template files in the templates/ folder of your theme. A file named single-movie.html using block markup will be automatically recognized. Check out our Full Site Editing tutorial for the complete picture on block theme templates.
Querying Custom Post Types

Registering a CPT only gets you halfway there. Eventually you need to pull that content and display it on the frontend. This wordpress custom post types tutorial covers three approaches: WP_Query, get_posts(), and the pre_get_posts filter.
WP_Query is your most flexible option for building custom loops anywhere in your theme or plugin:
$movies = new WP_Query( array(
'post_type' => 'movie',
'posts_per_page' => 12,
'tax_query' => array(
array(
'taxonomy' => 'genre',
'field' => 'slug',
'terms' => 'action',
),
),
) );
if ( $movies->have_posts() ) {
while ( $movies->have_posts() ) {
$movies->the_post();
// your template output here
}
wp_reset_postdata();
}
pre_get_posts is the right tool when you want to modify the main archive query without overriding it entirely — for example, changing the number of posts per page on your /movies/ archive. And because you set show_in_rest => true, your CPT also gets a REST API endpoint at /wp-json/wp/v2/movie automatically. Our WordPress REST API beginners guide walks you through working with those endpoints.
Common Mistakes and How to Avoid Them

Every wordpress custom post types tutorial worth its salt ends with a brutally honest mistakes section. Here’s what breaks CPTs in the wild — and how to dodge every one of them.
| ❌ Wrong Way | ✅ Right Way | Why It Matters |
|---|---|---|
Omit show_in_rest |
Set show_in_rest => true |
Without it, you get Classic Editor only. Block Editor is dead to you. |
Register in functions.php |
Register in a dedicated plugin | Theme switch = content disappears from admin. Don’t hold your content hostage. |
| Slug over 20 characters | Keep slug ≤ 20 characters | WordPress silently truncates or rejects long slugs. Silent bugs are the worst bugs. |
| Never flush rewrite rules | Save Permalinks after registering | Your archive URL returns a 404 until rules are flushed. Simple fix, easy to forget. |
Slug starts with wp_ |
Use a custom prefix (e.g., aodn_) |
WordPress reserves the wp_ namespace. Collision incoming. |
One more thing this wordpress custom post types tutorial won’t let you skip: 72% of WordPress developers still reach for CPT UI (1M+ installs) to handle registration. Plugins aren’t evil — but they’re a dependency. A plugin update, an abandoned repo, a license change — any of it can break your content structure. Fifteen lines of code in a plugin you control is a permanent solution. A third-party plugin is a rental agreement.
🏴☠️ PIRATE TIP
After registering any new CPT or taxonomy, always go to Settings → Permalinks and hit Save Changes. You don’t need to change anything — just save. This flushes the rewrite rules and prevents the dreaded 404 on your new archive URL. Burn this into your muscle memory.
Pirate Verdict

⚓ PIRATE VERDICT
Custom post types are what separate WordPress amateurs from WordPress builders. This wordpress custom post types tutorial has given you everything you need: the function, the arguments, the taxonomy pairing, the template files, the query methods, and the landmines to avoid.
Don’t let a $49/year plugin hold your content structure hostage when 15 lines of code give you the same thing for free — forever. Learn register_post_type(). Own your content architecture. Build something that doesn’t break when you switch themes or abandon a plugin.
That’s the pirate way. Now go build something real. 🏴☠️
What is the difference between a custom post type and a regular post?
A regular WordPress post is a built-in content type designed for blog entries. A custom post type is a new content structure you define — like movies, testimonials, or events. They share the same wp_posts database table but use a different post_type value. CPTs get their own admin menu, URL structure, archive pages, and template files, making them far more suited to structured content than shoehorning everything into the default post type.
Do I need a plugin to create custom post types in WordPress?
No. You can register custom post types directly in PHP using the register_post_type() function hooked to the init action. A simple plugin file with 15-20 lines of code is all you need. Plugins like CPT UI are popular (1M+ installs) but create a dependency you don’t need. Code-first is more portable, more stable, and free forever.
Can I use custom post types with the WordPress Block Editor?
Yes — but only if you set show_in_rest => true in your register_post_type() arguments. Without that parameter, your CPT will open in the Classic Editor instead of Gutenberg. In 2026, this is non-negotiable. Always include show_in_rest => true when following any wordpress custom post types tutorial.
Where should I register my custom post type — in my theme or a plugin?
Always register custom post types in a dedicated plugin, never in your theme’s functions.php. If you switch or deactivate your theme, any CPTs registered in functions.php disappear from the WordPress admin — your content still exists in the database but WordPress can’t surface it. A small custom plugin keeps your content structure independent of your theme.
How do I display custom post types on the frontend?
You have three main options. First, if you’ve set has_archive => true and created an archive-{post_type}.php template, WordPress will handle the archive automatically. Second, use WP_Query with the post_type parameter to build custom loops anywhere in your theme. Third, for block themes using Full Site Editing, create single-{post_type}.html and archive-{post_type}.html templates in your theme’s templates/ folder. Always call wp_reset_postdata() after a custom WP_Query loop.
Frequently Asked Questions
What is a custom post type in WordPress?
A custom post type is a content type beyond the default posts and pages. It lets you create structured content like portfolios, testimonials, products, or events with their own admin menus, templates, and archive pages.
How do I register a custom post type in WordPress?
Use the register_post_type() function hooked to the init action. Pass it a slug and an array of arguments including labels, public visibility, menu icon, and which features to support like title, editor, and thumbnails.
What is the difference between a custom post type and a custom taxonomy?
A custom post type defines a new content type (like Products or Events), while a custom taxonomy defines a way to group and classify content (like Product Categories or Event Types). Post types hold content, taxonomies organize it.
Can I create custom post types without coding?
Yes, plugins like Custom Post Type UI (CPT UI) let you create custom post types through the WordPress admin without writing any PHP. However, coding them yourself gives you more control and avoids plugin dependency.
Do custom post types affect WordPress performance?
Registering a few custom post types has negligible performance impact. Performance issues only arise with thousands of posts and poorly optimized queries. Proper indexing and caching handle most concerns.