Independent Editorial DeskWordPress Releases, Builds, and Operations
Back to Archive
Implementation Notes

WordPress Custom Post Type Example: Register a Production-Ready CPT Plugin

A practical custom post type plugin example covering register_post_type(), REST visibility, archives, rewrite rules, and production validation checks.

Published

April 16, 2026

Reading Time

5 min read

Updated

April 16, 2026

Abstract editorial interface cards representing a WordPress custom post type content model.
Build PatternImplementation Notes

Implementation Notes

Extension points, code paths, and implementation choices that should survive contact with production.

Best For

WordPress developers, agencies, and technical teams building custom plugin or theme functionality with cleaner operational defaults.

Primary Topics

Implementation Notes

Editorial Focus

Build Pattern: Extension points, code paths, and implementation choices that should survive contact with production. Updated on April 16, 2026.

Full Report

Last reviewed: April 16, 2026

Searches for “WordPress custom post type example” usually come from developers who need more than a reference page. They need a working snippet, a decision about whether the code belongs in a plugin or the theme, and enough production context to avoid creating a content model that breaks after the next redesign.

This guide shows a plugin-first custom post type implementation for a real content type. It covers register_post_type(), REST API visibility, archives, menu labels, rewrite rules, supported editor fields, and the checks to run before the post type is used on a production site.

Who this is for

This article is for WordPress developers, agencies, and site operators who need to add structured content such as case studies, reports, jobs, documentation entries, events, or resources. The goal is not to paste a random snippet into functions.php. The goal is to create a stable content model that survives theme changes and can be maintained like application code.

Why custom post type examples are searched so often

Custom post types sit at the center of many WordPress projects because they turn WordPress from a simple blog into a structured publishing system. The common search intent is practical:

  • how to register a custom post type;
  • where the code should live;
  • how to make the content available in the REST API;
  • how to avoid permalink and rewrite problems; and
  • which labels, supports, archives, and capabilities matter.

Those are implementation questions, not just documentation questions. A production-ready example should answer them together.

Use a plugin for the content model

If the custom post type represents business content, put it in a plugin. A theme can change how content looks, but it should not own whether the content type exists. If a site has “Resources” or “Case Studies”, that content should not disappear because a team switches themes.

A theme-owned custom post type is acceptable only when the content exists purely for that theme and should intentionally disappear with it. That is rare in production. Plugin-first is the safer default.

Production-ready custom post type plugin example

Create a directory such as wp-content/plugins/vulnwp-resources, then create a PHP file named vulnwp-resources.php.

<?php
/**
 * Plugin Name: VulnWP Resources Content Type
 * Description: Registers a Resources custom post type for structured editorial content.
 * Version: 1.0.0
 */

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

add_action( 'init', 'vulnwp_register_resource_post_type' );

function vulnwp_register_resource_post_type() {
	$labels = array(
		'name'                  => __( 'Resources', 'vulnwp' ),
		'singular_name'         => __( 'Resource', 'vulnwp' ),
		'menu_name'             => __( 'Resources', 'vulnwp' ),
		'name_admin_bar'        => __( 'Resource', 'vulnwp' ),
		'add_new'               => __( 'Add New', 'vulnwp' ),
		'add_new_item'          => __( 'Add New Resource', 'vulnwp' ),
		'edit_item'             => __( 'Edit Resource', 'vulnwp' ),
		'new_item'              => __( 'New Resource', 'vulnwp' ),
		'view_item'             => __( 'View Resource', 'vulnwp' ),
		'search_items'          => __( 'Search Resources', 'vulnwp' ),
		'not_found'             => __( 'No resources found.', 'vulnwp' ),
		'not_found_in_trash'    => __( 'No resources found in Trash.', 'vulnwp' ),
		'all_items'             => __( 'All Resources', 'vulnwp' ),
		'archives'              => __( 'Resource Archives', 'vulnwp' ),
		'attributes'            => __( 'Resource Attributes', 'vulnwp' ),
		'insert_into_item'      => __( 'Insert into resource', 'vulnwp' ),
		'uploaded_to_this_item' => __( 'Uploaded to this resource', 'vulnwp' ),
	);

	register_post_type(
		'vulnwp_resource',
		array(
			'labels'             => $labels,
			'public'             => true,
			'show_in_rest'       => true,
			'has_archive'        => true,
			'rewrite'            => array( 'slug' => 'resources' ),
			'menu_icon'          => 'dashicons-media-document',
			'menu_position'      => 20,
			'supports'           => array( 'title', 'editor', 'excerpt', 'thumbnail', 'revisions' ),
			'taxonomies'         => array( 'category', 'post_tag' ),
			'capability_type'    => 'post',
			'map_meta_cap'       => true,
		)
	);
}

What the example does well

It registers on init. Custom post types should be registered during WordPress initialization so rewrite rules, admin menus, queries, and REST routes can see the content type consistently.

It uses a prefixed post type key. The key vulnwp_resource avoids generic names such as resource that could collide with another plugin or future code.

It enables the block editor and REST API. show_in_rest makes the custom post type available to the block editor and REST API. That matters for modern WordPress and for headless projects.

It includes archives and a stable rewrite slug. If resources should have an archive page, has_archive and rewrite need to be deliberate. Do not let public URLs happen accidentally.

It supports thumbnails, excerpts, and revisions. These features are practical for editorial workflows, previews, cards, search snippets, and rollback.

Activation and rewrite rules

After adding or changing a custom post type, flush rewrite rules once. Do not call flush_rewrite_rules() on every request because it is expensive and unnecessary. The simplest safe path is to activate the plugin, then visit Settings – Permalinks and save once.

If you need activation handling in the plugin, register the post type inside the activation callback before flushing:

register_activation_hook( __FILE__, 'vulnwp_resources_activate' );

function vulnwp_resources_activate() {
	vulnwp_register_resource_post_type();
	flush_rewrite_rules();
}

register_deactivation_hook( __FILE__, 'vulnwp_resources_deactivate' );

function vulnwp_resources_deactivate() {
	flush_rewrite_rules();
}

Production checklist before publishing content

  • Confirm the post type key is prefixed and will not collide with another component.
  • Decide whether the content type should be public, private, or admin-only.
  • Confirm whether the post type should expose REST responses to unauthenticated users.
  • Validate archive URLs, single URLs, and canonical behavior after saving permalinks.
  • Add templates or frontend rendering only after the data model is stable.
  • Check whether editorial teams need revisions, excerpts, featured images, or taxonomy filters.
  • Document whether the post type is owned by a plugin, theme, or platform team.

Common mistakes

  • Putting durable content models in functions.php. This creates migration risk when the theme changes.
  • Using a generic post type key. Names like book, project, or resource can collide.
  • Forgetting show_in_rest. The content type may not behave as expected in the block editor or API-driven frontend.
  • Flushing rewrite rules on every request. Flush once on activation or manually through permalink settings.
  • Not deciding whether the archive should exist. Public URLs should be intentional.

How this connects to headless WordPress

Headless sites often rely on custom post types because the frontend needs structured data. A well-designed post type is easier to query, cache, preview, and expose through REST. If you are building a headless stack, pair this with our Headless WordPress Security Checklist and the REST API exposure checklist.

Quick validation with WP-CLI and REST

After activation, confirm that WordPress can see the post type and that the public API behavior matches your intent. If the content type is supposed to be headless-accessible, the REST response should exist. If it is not supposed to be public, the response should be blocked or omitted by design.

# Confirm the post type is registered.
wp post-type list --fields=name,public,show_in_rest,has_archive

# Create a draft resource for testing.
wp post create --post_type=vulnwp_resource --post_status=draft --post_title="CPT Smoke Test"

# Inspect the REST collection if show_in_rest is enabled.
curl -s https://example.com/wp-json/wp/v2/vulnwp_resource

That last command should not be treated as a formality. It verifies the API surface your frontend or integration will actually consume. For production work, test with a published item, a draft item, a logged-out request, and an authenticated editor request so permissions and visibility are clear before content entry begins.

References and further reading

Popular Guides

Popular WordPress guides to read next.

These articles connect recurring production concerns: implementation details, updates, troubleshooting, recovery paths, and operational cleanup.

Continue Reading

More from the archive.

Diagnostic dashboard scene representing a WordPress Site Health review before major updates.
01Build Pattern
Implementation Notes

Build Pattern

Extension points, code paths, and implementation choices that should survive contact with production.

May 21, 2026 · 3 min read

WordPress Site Health Check Before Major Updates: What to Review First

A pre-update WordPress Site Health checklist covering loopbacks, connectivity, debug settings, and environment readiness.

Structured data and route review scene representing permalink validation after a WordPress migration.
02Build Pattern
Implementation Notes

Build Pattern

Extension points, code paths, and implementation choices that should survive contact with production.

May 21, 2026 · 3 min read

WordPress Permalink Checklist After Migration: Catch URL Problems Early

A post-migration WordPress permalink checklist for checking rewrite rules, post URLs, archives, and redirect noise.

Technical media workspace representing image preparation and optimization before upload to WordPress.
03Build Pattern
Implementation Notes

Build Pattern

Extension points, code paths, and implementation choices that should survive contact with production.

May 21, 2026 · 3 min read

WordPress Image Optimization Checklist: What to Fix Before Upload

A practical WordPress image optimization checklist covering dimensions, compression, formats, and Media settings before upload.