Custom Post Types for the Block Editor and REST API

A custom post type works well with the WordPress block editor and REST API when registration is treated as a public contract, not a collection of copied flags. The type needs a stable key, complete labels, deliberate capabilities, REST exposure, supported editor features, rewrite rules, and an archive/template plan. This tutorial registers a case_study type and industry taxonomy while explaining the decisions that prevent common editor and API surprises. These decisions become difficult to reverse after real content and external clients accumulate.
Model content before choosing arguments
A case study is independently publishable, has a title, narrative content, excerpt, featured image, author, revisions, industries, and a public archive. That model justifies a post type. A single field attached to an existing page would not.
The machine key must be no more than 20 characters and should remain stable. Changing it later affects queries, capabilities, REST routes, templates, and stored relationships.
Register the post type on init
add_action( 'init', function () { register_post_type( 'case_study', array( 'labels' => array( 'name' => __( 'Case Studies', 'adz-work' ), 'singular_name' => __( 'Case Study', 'adz-work' ), 'add_new_item' => __( 'Add Case Study', 'adz-work' ), 'edit_item' => __( 'Edit Case Study', 'adz-work' ), 'view_item' => __( 'View Case Study', 'adz-work' ), 'search_items' => __( 'Search Case Studies', 'adz-work' ), 'not_found' => __( 'No case studies found.', 'adz-work' ) ), 'public' => true, 'has_archive' => true, 'rewrite' => array( 'slug' => 'work', 'with_front' => false ), 'show_in_rest' => true, 'rest_base' => 'case-studies', 'menu_icon' => 'dashicons-portfolio', 'supports' => array( 'title', 'editor', 'excerpt', 'thumbnail', 'author', 'revisions', 'custom-fields' ), 'capability_type' => array( 'case_study', 'case_studies' ), 'map_meta_cap' => true ) ); } );
show_in_rest enables the block editor and creates standard REST routes. It does not mean every custom field is exposed; registered metadata needs its own schema and authorization.
Create and assign custom capabilities
Custom capability types are valuable only if roles receive the generated capabilities. On activation, grant the precise set to the intended roles using get_role() and add_cap(). On deactivation, usually preserve capabilities because deactivation may be temporary. Remove them only through an explicit uninstall policy.
Do not run role mutations on every request. Version the capability setup and execute it during activation or an intentional upgrade routine. Test authors, editors, and administrators separately.
Register the taxonomy explicitly
add_action( 'init', function () { register_taxonomy( 'industry', array( 'case_study' ), array( 'labels' => array( 'name' => __( 'Industries', 'adz-work' ), 'singular_name' => __( 'Industry', 'adz-work' ) ), 'public' => true, 'hierarchical' => true, 'show_in_rest' => true, 'rewrite' => array( 'slug' => 'industry', 'with_front' => false ), 'show_admin_column' => true ) ); register_taxonomy_for_object_type( 'industry', 'case_study' ); } );
Register both sides of the relationship to make intent clear, especially when plugins may load in different orders.
Expose structured metadata with a schema
Suppose each case study has a public project URL. Register it with a REST schema and an authorization callback.
register_post_meta( 'case_study', 'project_url', array( 'type' => 'string', 'single' => true, 'default' => '', 'show_in_rest' => array( 'schema' => array( 'type' => 'string', 'format' => 'uri', 'context' => array( 'view', 'edit' ) ) ), 'sanitize_callback' => 'esc_url_raw', 'auth_callback' => function ( $allowed, $meta_key, $post_id ) { return current_user_can( 'edit_post', $post_id ); } ) );
Test unauthenticated GET responses and authenticated edit requests. Never assume a field is private because the editor UI does not display it.
Plan permalinks and templates
The rewrite slug work produces human-readable URLs while the internal type remains case_study. Flush rewrite rules only on activation or when registration changes—never on every request.
Provide single-case_study.php and archive-case_study.php in a classic theme, or matching templates in a block theme. Decide what happens if the theme lacks them; WordPress will fall back through the template hierarchy, but generic output may omit important metadata.
Check REST behavior directly
- GET
/wp-json/wp/v2/case-studiesas a logged-out visitor. - Request drafts without authentication and confirm they are not exposed.
- Create and update a case study with an appropriately privileged application-password user.
- Attempt the same request with an unauthorized role.
- Filter by the industry taxonomy and verify pagination headers.
- Confirm embedded author and media data obey their own permissions.
Document the route and fields consumers may rely on. Once external clients use them, renaming rest_base or removing fields becomes a breaking change.
Verify the editor experience
Create, revise, preview, schedule, trash, restore, and duplicate representative content. Test featured images, excerpts, revisions, taxonomy panels, permalink editing, and reusable patterns. Disable unsupported features instead of leaving controls that do not affect the template.
If the type uses a constrained layout, define a block template and decide whether editors may insert, move, or remove blocks. Template locking should reflect editorial policy, not merely developer preference.
Plan deletion and retention rules alongside creation. Decide whether uninstalling the plugin should preserve case studies, whether deleting a user reassigns authored content, and whether REST consumers need a deprecation period before fields disappear. Store these choices in documentation and tests. Content types often outlive the first plugin or theme that introduced them, so ownership and portability are architectural requirements rather than cleanup tasks.
Keep registration stable and evolvable
A custom post type crosses the database, editor, theme, permissions, URLs, and REST API. Stable identifiers, explicit schemas, role tests, and template coverage are what make it reliable. The official post type guide and REST support documentation describe the APIs. Treat every exposed name and capability as a compatibility promise before content accumulates around it.
Photo by Diva Plavalaguna on Pexels.
Written by
Adrian Saycon
A developer with a passion for emerging technologies, Adrian Saycon focuses on transforming the latest tech trends into great, functional products.


