Governance: Enforcing Block Templates for Custom Post Types
In a Block Theme, the global site layout (Header, Footer, Sidebar) is controlled by HTML files in the templates/ folder.
However, you often want to control the Content Area itself. For example, ensuring every "Case Study" starts with a specific Cover Image and Heading structure, and preventing the client from deleting them, effectively turning the Block Editor into a structured data form.
This is done via PHP when registering the Custom Post Type (CPT).
1. The template Argument
When you register a CPT using register_post_type(), you can pass a template array. This defines the initial blocks that appear when a user clicks "Add New".
'template' => array(
array( 'core/cover', array( 'overlayColor' => 'black' ) ),
array( 'core/heading', array( 'placeholder' => 'Case Study Title' ) ),
array( 'core/paragraph', array( 'placeholder' => 'Write the summary here...' ) ),
),
2. The template_lock Argument
By default, the template just provides a starting point. Users can delete the blocks. To enforce structure, use template_lock.
'all': Strict. Users cannot add new blocks, remove blocks, or reorder blocks. They can only edit the content/attributes of the existing blocks.'insert': Users cannot add new blocks or remove existing ones, but they can move them (rarely used).false: (Default) No locking.
3. Real World Example: "Team Member" CPT
Let's create a "Team Member" CPT where the layout is strictly locked:
- Image: (Locked)
- Name: (Heading, Locked)
- Bio: (Paragraph, Locked)
- Social Links: (Social Icons, Locked)
function my_register_team_cpt() {
$args = array(
'label' => 'Team',
'public' => true,
'show_in_rest' => true, // Required for Block Editor
'supports' => array( 'title', 'editor', 'thumbnail' ), // 'editor' is required
// Define the fixed block structure
'template' => array(
array( 'core/image', array(
'align' => 'center',
) ),
array( 'core/heading', array(
'placeholder' => 'Full Name',
'level' => 2,
'textAlign' => 'center'
) ),
array( 'core/paragraph', array(
'placeholder' => 'Job Title & Bio...',
'align' => 'center'
) ),
array( 'core/social-links', array( 'layout' => array( 'type' => 'flex', 'justifyContent' => 'center' ) ), array(
array( 'core/social-link', array( 'service' => 'linkedin' ) ),
array( 'core/social-link', array( 'service' => 'twitter' ) ),
) ),
),
// Lock the entire container
'template_lock' => 'all',
);
register_post_type( 'team_member', $args );
}
add_action( 'init', 'my_register_team_cpt' );
4. Nested Templates (The "Container" Strategy)
If you want a locked "Hero" section at the top, but want to allow free-form editing below it, you cannot use template_lock => 'all' on the CPT itself, as that locks the entire post.
The Solution: Use a Group block as a container in your template, and lock that specific block using nested locking logic.
'template' => array(
// 1. Locked Hero Section (Group Block)
array( 'core/group', array(
'tagName' => 'section',
'templateLock' => 'all', // Locks children inside this group
'lock' => array( 'remove' => true, 'move' => true ) // Prevents deleting this group
), array(
array( 'core/heading', array( 'content' => 'Mandatory Header' ) ),
) ),
// 2. Free editing area (A paragraph to start)
array( 'core/paragraph', array( 'placeholder' => 'Add free-form content here...' ) ),
),
'template_lock' => false, // Allow adding blocks at the root level (below the hero)