How to Create Custom Post Type Gutenberg Block
Learn to create custom post types in WordPress and seamlessly integrate them with the Gutenberg block editor. This guide uses real-world examples to enhance
On this page

If you've been working with WordPress for any serious length of time, especially on client projects or custom applications, you know the power of Custom Post Types (CPTs). They transform WordPress from a simple blogging platform into a robust content management system capable of handling anything from products and portfolios to event listings or, in my case, detailed notification templates like those in my OpenWA WhatsApp Gateway plugin.
But simply registering a CPT isn't enough in today's WordPress landscape. The real magic happens when you integrate these custom post types seamlessly with the modern Gutenberg block editor. This integration doesn't just make the editing experience smoother for your users; it empowers them to structure rich, dynamic content exactly as needed. In this in-depth guide, I'll walk you through the practical steps to create custom post type Gutenberg block editor support, drawing from my 8+ years of real-world development experience building complex plugins and applications.
I've learned that merely understanding the theory isn't enough. You need actionable steps and examples that reflect common scenarios. Whether it's setting up complex data structures for a School ERP or defining notification messages for WooCommerce orders using OpenWA, mastering CPTs with Gutenberg is crucial.
Why Custom Post Types are Essential for Modern WordPress Development
Before diving into Gutenberg, let's quickly recap why CPTs are indispensable. In my early days, I'd sometimes try to cram everything into pages or posts, maybe using categories and tags to differentiate. It quickly became unmanageable. CPTs provide a structured way to manage different types of content independently. Think about a real-world scenario: for my OpenWA WhatsApp Gateway plugin, I needed a way to store and manage various WhatsApp notification templates - for new orders, order status changes, OTP verification, and more. Creating a CPT called 'whatsapp_template' for these allowed me to define custom fields, assign specific capabilities, and keep them separate from blog posts or pages.
Similarly, for the Frontend File Explorer plugin, I leveraged CPTs to manage virtual files and folders, enabling a robust file system within WordPress. This separation ensures data integrity, improves administration, and allows for specific queries and functionalities tied only to that content type.
But the editing experience for these custom content types often felt lacking with classic meta boxes. That's where Gutenberg steps in.
The Power of Gutenberg Integration for Custom Post Types
The Gutenberg block editor has revolutionized how we create content in WordPress. It moves beyond a simple WYSIWYG editor, offering a visual, block-based system that mirrors the front-end output. For custom post types, this means:
- Enhanced User Experience: Instead of juggling multiple meta boxes and custom fields, users can visually construct content using pre-defined blocks.
- Structured Content: Blocks encourage structured content. You can enforce certain layouts or components for your CPTs, ensuring consistency.
- Developer Control: As developers, we get fine-grained control over what blocks are available for a specific CPT, ensuring content creators stick to the intended design and functionality.
- Reusability: Blocks are inherently reusable, speeding up content creation.
When I was designing the admin interface for OpenWA, I wanted to give merchants an intuitive way to compose their WhatsApp messages. While I initially used custom text areas, envisioning a block-based template builder was always a goal. This is exactly the kind of problem Gutenberg solves - letting users build dynamic content with ease for their custom data structures.
Step-by-Step: How to Register a Custom Post Type and Enable Gutenberg
Let's get practical. The first step to integrate with the Gutenberg block editor is to correctly register your custom post type with the necessary arguments. I typically do this within my plugin's main file or a dedicated CPT file, hooked into init.
1. Registering Your Custom Post Type
Here's a basic example, inspired by how I might set up a 'Template' CPT for a notification system:
function shafat_register_whatsapp_template_cpt() {
$labels = [
'name' => _x( 'WhatsApp Templates', 'Post Type General Name', 'openwa-gateway' ),
'singular_name' => _x( 'WhatsApp Template', 'Post Type Singular Name', 'openwa-gateway' ),
'menu_name' => __( 'WhatsApp Templates', 'openwa-gateway' ),
'name_admin_bar' => __( 'WhatsApp Template', 'openwa-gateway' ),
'archives' => __( 'Template Archives', 'openwa-gateway' ),
'attributes' => __( 'Template Attributes', 'openwa-gateway' ),
'parent_item_colon' => __( 'Parent Template:', 'openwa-gateway' ),
'all_items' => __( 'All Templates', 'openwa-gateway' ),
'add_new_item' => __( 'Add New Template', 'openwa-gateway' ),
'add_new' => __( 'Add New', 'openwa-gateway' ),
'new_item' => __( 'New Template', 'openwa-gateway' ),
'edit_item' => __( 'Edit Template', 'openwa-gateway' ),
'update_item' => __( 'Update Template', 'openwa-gateway' ),
'view_item' => __( 'View Template', 'openwa-gateway' ),
'view_items' => __( 'View Templates', 'openwa-gateway' ),
'search_items' => __( 'Search Templates', 'openwa-gateway' ),
'not_found' => __( 'Not found', 'openwa-gateway' ),
'not_found_in_trash' => __( 'Not found in Trash', 'openwa-gateway' ),
'featured_image' => __( 'Featured Image', 'openwa-gateway' ),
'set_featured_image' => __( 'Set featured image', 'openwa-gateway' ),
'remove_featured_image' => __( 'Remove featured image', 'openwa-gateway' ),
'use_featured_image' => __( 'Use as featured image', 'openwa-gateway' ),
'insert_into_item' => __( 'Insert into template', 'openwa-gateway' ),
'uploaded_to_this_item' => __( 'Uploaded to this template', 'openwa-gateway' ),
'items_list' => __( 'Templates list', 'openwa-gateway' ),
'items_list_navigation' => __( 'Templates list navigation', 'openwa-gateway' ),
'filter_items_list' => __( 'Filter templates list', 'openwa-gateway' ),
];
$args = [
'label' => __( 'WhatsApp Template', 'openwa-gateway' ),
'description' => __( 'Custom WhatsApp notification templates.', 'openwa-gateway' ),
'labels' => $labels,
'supports' => ['title', 'editor', 'custom-fields'], // 'editor' is crucial for Gutenberg!
'hierarchical' => false,
'public' => false, // Often CPTs for settings/internal data are not public
'show_ui' => true,
'show_in_menu' => true,
'menu_position' => 5, // Just below Posts
'menu_icon' => 'dashicons-whatsapp', // A nice WhatsApp icon
'show_in_admin_bar' => true,
'show_in_nav_menus' => true,
'can_export' => true,
'has_archive' => false,
'exclude_from_search' => true,
'publicly_queryable' => false,
'capability_type' => 'post',
'show_in_rest' => true, // THIS IS THE KEY FOR GUTENBERG BLOCK EDITOR INTEGRATION!
];
register_post_type( 'whatsapp_template', $args );
}
add_action( 'init', 'shafat_register_whatsapp_template_cpt', 0 );
The most critical argument for Gutenberg integration here is 'show_in_rest' => true. This makes your CPT available via the WordPress REST API, which is what Gutenberg relies on to fetch and save content. Without it, you'll be stuck with the classic editor. Also, ensure 'supports' includes 'editor'.
2. Controlling Allowed Blocks for Your CPT
Once Gutenberg is enabled for your CPT, you might not want all available blocks to be usable. For a specific CPT like 'WhatsApp Template', you might only want a 'Heading' block for the template title, a 'Paragraph' block for the message body, and perhaps a custom block for inserting dynamic variables. This is where the allowed_block_types_for_post_type filter comes in handy.
function shafat_allowed_whatsapp_template_blocks( $allowed_blocks, $editor_context ) {
if ( ! empty( $editor_context->post->post_type ) && 'whatsapp_template' === $editor_context->post->post_type ) {
// Define which blocks are allowed for 'whatsapp_template' CPT.
// 'core/paragraph' is essential for text.
// 'core/heading' for titles.
// 'openwa/template-variable' would be a custom block I'd create for OpenWA.
$allowed_blocks = [
'core/paragraph',
'core/heading',
'core/list',
'core/shortcode',
// 'openwa/template-variable', // Example of a custom block for OpenWA
// 'openwa/media-attachment', // If template supports media
];
}
return $allowed_blocks;
}
add_filter( 'allowed_block_types_for_post_type', 'shafat_allowed_whatsapp_template_blocks', 10, 2 );
This snippet demonstrates how you can whitelist specific blocks. I've used this principle in various projects to ensure that content creators stick to the design guidelines. For instance, in an ERP system where student profiles are CPTs, I'd only allow blocks relevant to displaying student data, not arbitrary image galleries.
Deep Dive: Creating Custom Blocks for Your CPT
While standard blocks are useful, the real power often lies in creating custom blocks tailored to your CPT's needs. Let's say for our 'WhatsApp Template' CPT, we want a block that allows users to easily insert dynamic variables (like {{customer_name}}, {{order_total}}) into the message, much like how I'd imagine enhancing the OpenWA WhatsApp Gateway templates beyond simple text. This block would simplify the user experience, rather than expecting them to remember obscure shortcodes or placeholders.
Creating custom Gutenberg blocks involves a bit more setup, typically requiring a build process with Node.js and npm (or yarn). Here's a simplified overview of the process:
1. Setting Up Your Block Development Environment
You'll typically start by creating a dedicated folder for your custom block(s) within your plugin. I often use wp-scripts, a package provided by WordPress, to handle the build process (Webpack, Babel, React preset). It simplifies block development significantly.
Inside your plugin (e.g., openwa-gateway/blocks/whatsapp-variable/), you'd have:
block.json: Defines the block's metadata.index.js: The main JavaScript file for the block's frontend and backend logic.editor.scss(optional): Styles for the block in the editor.style.scss(optional): Styles for the block on the frontend.
2. Defining Your Block with block.json
This file is the manifest for your block. It registers the block type and defines its properties. For our 'Template Variable' block, it might look like this:
// openwa-gateway/blocks/whatsapp-variable/block.json
{
"$schema": "https://schemas.wp.org/trunk/block.json",
"apiVersion": 2,
"name": "openwa/template-variable",
"version": "1.0.0",
"title": "WhatsApp Template Variable",
"category": "widgets",
"icon": "text-page",
"description": "Insert a dynamic variable into your WhatsApp message template.",
"supports": {
"html": false
},
"textdomain": "openwa-gateway",
"editorScript": "file:./index.js", // Path to your block's JavaScript
"attributes": { // Define any attributes your block will use
"variableName": {
"type": "string",
"default": ""
}
}
}
The name is crucial and should be unique (e.g., {plugin_slug}/{block_name}). attributes define the data stored with the block.
3. Building the Block Logic with index.js
This is where the React magic happens. You'll define how the block looks and behaves in the editor (edit function) and how it's saved to the database (save function).
// openwa-gateway/blocks/whatsapp-variable/index.js
import { registerBlockType } from '@wordpress/blocks';
import { SelectControl, PanelBody, TextControl } from '@wordpress/components';
import { useBlockProps, InspectorControls, RichText } from '@wordpress/block-editor';
registerBlockType( 'openwa/template-variable', {
edit: ({ attributes, setAttributes }) => {
const blockProps = useBlockProps();
const { variableName } = attributes;
// Options for the dropdown. In a real scenario, this might be dynamic.
const variableOptions = [
{ label: 'Select a Variable', value: '' },
{ label: 'Customer Name', value: '{{customer_name}}' },
{ label: 'Order ID', value: '{{order_id}}' },
{ label: 'Order Total', value: '{{order_total}}' },
{ label: 'Shop Name', value: '{{shop_name}}' },
];
return (
<div { ...blockProps }>
<InspectorControls>
<PanelBody title="Variable Settings">
<SelectControl
label="Choose Variable"
value={ variableName }
options={ variableOptions }
onChange={ ( newVariable ) => setAttributes({ variableName: newVariable }) }
/>
{variableName && (
<TextControl
label="Preview (Read-only)"
value={ variableName }
disabled
/>
)}
</PanelBody>
</InspectorControls>
<div style={{ padding: '10px', border: '1px dashed #ccc', minHeight: '30px' }}>
{ variableName ? `[OpenWA Variable: ${variableName}]` : 'Choose a WhatsApp variable' }
</div>
</div>
);
},
save: ({ attributes }) => {
const blockProps = useBlockProps.save();
const { variableName } = attributes;
// This is what gets saved to the database. Render just the variable.
// The actual replacement happens during notification sending logic.
return <span { ...blockProps }>{ variableName }</span>;
},
} );
This example demonstrates:
editfunction: What users see and interact with in the Gutenberg editor. It uses core WordPress components likeSelectControlandPanelBodyfor a consistent UI. The selected variable name is stored in the block'sattributes.savefunction: What actually gets saved to the database. For this specific block, we might just save the variable placeholder string. When a WhatsApp notification is sent via OpenWA, my plugin's logic would then parse this content and replace{{customer_name}}with the actual customer's name.
Remember, for this to work, you need to enqueue your block's JavaScript and CSS files, typically in your plugin's main file, using wp_enqueue_script and wp_enqueue_style, ensuring they only load when the block editor is active for your specific CPT.
// In your plugin's main file (e.g., openwa-gateway.php)
function shafat_enqueue_whatsapp_block_assets() {
// Ensure we are in the block editor AND on our 'whatsapp_template' CPT.
$screen = get_current_screen();
if ( $screen && $screen->is_block_editor() && 'whatsapp_template' === $screen->post_type ) {
// Enqueue your block's editor script
wp_enqueue_script(
'openwa-whatsapp-variable-editor-script',
plugins_url( 'blocks/whatsapp-variable/index.js', __FILE__ ),
['wp-blocks', 'wp-element', 'wp-editor', 'wp-components', 'wp-data', 'wp-i18n'],
filemtime( plugin_dir_path( __FILE__ ) . 'blocks/whatsapp-variable/index.js' ),
true
);
// Enqueue your block's editor styles
wp_enqueue_style(
'openwa-whatsapp-variable-editor-style',
plugins_url( 'blocks/whatsapp-variable/editor.css', __FILE__ ),
[],
filemtime( plugin_dir_path( __FILE__ ) . 'blocks/whatsapp-variable/editor.css' )
);
}
}
add_action( 'admin_enqueue_scripts', 'shafat_enqueue_whatsapp_block_assets' );
This conditional enqueuing is crucial for performance. You don't want these assets loading on every admin page. It's a practice I strictly adhere to - whether it's optimizing image loading or eliminating render-blocking resources for client sites, performance is always a priority. If you want to dive deeper into performance, check out my article on How to Eliminate Render Blocking Resources in WordPress Manually.
Advanced Gutenberg Integration Features for CPTs
Beyond basic custom blocks, Gutenberg offers more sophisticated ways to enhance the CPT editing experience.
1. Block Templates for Consistent Layouts
Sometimes you want your CPTs to have a predefined structure. For instance, if you're building a 'Product Feature' CPT, you might always want it to start with a 'Heading', followed by a 'Description Paragraph', and then a 'Gallery' block. You can define a block template directly in your register_post_type arguments:
// ... inside your register_post_type arguments array ...
'template' => [
['core/heading', ['placeholder' => 'Enter Feature Title']],
['core/paragraph', ['placeholder' => 'Describe the feature here...']],
['core/gallery', []],
['openwa/template-variable', ['variableName' => '{{extra_info}}']], // Example custom block in template
],
'template_lock' => 'all', // 'all' (blocks cannot be added/removed/reordered), 'insert' (blocks can be reordered but not added/removed)
template_lock is a powerful feature. Setting it to 'all' means users can only edit the content within the predefined blocks, but not add or remove any. This is fantastic for ensuring content consistency across CPTs, especially for client projects where maintaining specific branding or layout is critical.
2. Utilizing Core Blocks and Block Patterns
Even without custom blocks, you can create a powerful experience. WordPress's core blocks are highly versatile. Additionally, you can register Block Patterns that contain a collection of blocks, tailored for your CPTs. This is an excellent way to provide pre-designed sections that users can insert with a single click.
For example, you could register a pattern for a
Need help with your project?
Comments(0)
No comments yet. Be the first to share your thoughts.



