WordPress ships with posts and pages, but many sites need other content types: products, events, properties, team members, case studies, recipes, documentation. Custom post types (CPTs) give each type its own admin menu, URLs, templates and fields. Custom taxonomies group them, like categories do for posts.
Register a custom post type
Put this in a small site-specific plugin (not the theme), so your content type survives a theme change:
<?php
/**
* Plugin Name: Site Content Types
*/
add_action( 'init', function () {
register_post_type( 'case_study', [
'labels' => [
'name' => 'Case Studies',
'singular_name' => 'Case Study',
'add_new_item' => 'Add New Case Study',
],
'public' => true,
'has_archive' => true, // /case-studies/
'rewrite' => [ 'slug' => 'case-studies' ],
'menu_icon' => 'dashicons-portfolio',
'supports' => [ 'title', 'editor', 'thumbnail', 'excerpt', 'revisions', 'custom-fields' ],
'show_in_rest' => true, // block editor + REST API
] );
} );
Save it as wp-content/plugins/site-content-types/site-content-types.php, then activate it (wp plugin activate site-content-types).
Key arguments:
| Argument | Purpose |
|---|---|
public |
Visible on the front end and in the admin |
has_archive |
Enables an archive page listing all items |
rewrite → slug |
URL base (/case-studies/my-project/) |
supports |
Editor features (title, editor, thumbnail, …) |
show_in_rest |
Required for the block editor and REST API |
hierarchical |
true for page-like parent/child structure |
capability_type |
Custom capabilities, for fine-grained roles |
Register a taxonomy
add_action( 'init', function () {
register_taxonomy( 'industry', [ 'case_study' ], [
'labels' => [ 'name' => 'Industries', 'singular_name' => 'Industry' ],
'hierarchical' => true, // true = like categories, false = like tags
'public' => true,
'show_in_rest' => true,
'show_admin_column' => true,
'rewrite' => [ 'slug' => 'industry' ],
] );
} );
Flush permalinks
After registering (or changing the slug), URLs return 404 until rewrite rules are rebuilt:
wp rewrite flush
Or visit Settings → Permalinks, and click Save. Never call flush_rewrite_rules() on every page load, because it’s expensive. Call it only on plugin activation. See WordPress permalinks 404.
Templates
WordPress picks templates by the template hierarchy:
- Classic themes:
single-case_study.phpfor single items,archive-case_study.phpfor the archive,taxonomy-industry.phpfor terms. Put them in your child theme. See WordPress child theme. - Block themes: create
single-case_study.htmlandarchive-case_study.htmltemplates in the Site Editor, or in the theme’stemplates/folder.
Custom fields
Add structured data (client name, project year, location) with:
- Advanced Custom Fields (ACF), Meta Box, or Pods: field groups with a UI.
- Core
register_post_meta()withshow_in_rest, for simple fields usable in the block editor and API.
Querying custom post types
$q = new WP_Query( [
'post_type' => 'case_study',
'posts_per_page' => 6,
'tax_query' => [ [ 'taxonomy' => 'industry', 'field' => 'slug', 'terms' => 'hosting' ] ],
] );
In the block editor, the Query Loop block can list any post type with show_in_rest enabled.
Via the REST API: /wp-json/wp/v2/case_study (or the rest_base you set). See WordPress REST API.
Plugin alternatives (no code)
- Custom Post Type UI (CPT UI): register post types and taxonomies from the admin, and export the code later.
- ACF (recent versions) can register post types and taxonomies too.
- Pods: post types, taxonomies and fields in one plugin.
These are great for prototyping. For long-lived sites, exporting the registration to code (version-controlled) makes deployments predictable. See WordPress Git deployment.
Tips
- Choose singular, lowercase post type keys, with a maximum of 20 characters. Avoid reserved names (
post,page,attachment,action,order,theme). - Don’t change a post type key after content exists. The posts would become orphaned (you’d need
wp db queryto updatepost_type). - Add CPT archives to your sitemap, and check that SEO plugins include the new type.
Quick reference
| Task | Function / command |
|---|---|
| Post type | register_post_type() on init |
| Taxonomy | register_taxonomy() on init |
| Block editor support | 'show_in_rest' => true |
| Fix 404s | wp rewrite flush |
| Templates | single-{type}, archive-{type}, taxonomy-{tax} |
Related: WordPress hooks: actions and filters and WP-CLI guide.