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' ],
    ] );
} );

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.php for single items, archive-case_study.php for the archive, taxonomy-industry.php for terms. Put them in your child theme. See WordPress child theme.
  • Block themes: create single-case_study.html and archive-case_study.html templates in the Site Editor, or in the theme’s templates/ 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() with show_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 query to update post_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.

  • WordPress
  • Development
  • PHP