How to register block templates for a CPT with PHP in WordPress

Contents

Introduction — What are block templates and why register them for a CPT?

Block templates let you define a fixed or starter block structure when creating or editing a post. For a custom post type (CPT) this ensures authors get a pre-built layout (headings, columns, custom blocks, metadata blocks, etc.) tailored to that content type. Templates can be defined in PHP (programmatically) or as theme files (block-based theme templates). This article explains every detail needed to register block templates for a CPT with PHP, plus practical examples you can copy into a plugin or theme.

Compatibility and prerequisites

  • WordPress version: Block templates rely on the block editor and block APIs introduced across WP 5.8 features described here assume modern WordPress (5.8 ) and will be most consistent on WP 6.x.
  • Editor enabled for the CPT: The CPT must use the block editor. That generally requires show_in_rest =gt true and supporting the editor feature.
  • Where to place code: Register the CPT and its template on the init hook (or via a plugin that runs on init). Use the same approach for plugins or theme functions.

Overview of ways to register block templates for a CPT

  1. Programmatically in PHP when registering the CPT: pass a template array in the arguments to register_post_type().
  2. Add or modify the CPT args using the register_post_type_args filter if the CPT is registered by another plugin/theme and you want to attach a template.
  3. Provide a theme file in block-templates/{post-type}.html (block-based theme approach) — covered briefly for completeness.

Key concepts and arguments

  • template — an array defining a list of blocks, their attributes, and inner blocks. Each block item is an array: [blockName, attributesArray, innerBlocksArray].
  • template_lock — locks the template. Accepts all (fully locked), insert (prevents adding new blocks existing blocks may be moved/removed depending on editor versions), or false (no lock). Use carefully because locking affects editor flexibility.
  • show_in_rest — must be true for the block editor otherwise templates won’t apply in the block editor UI.
  • supports — include editor to ensure standard editor support is present (many registrations include it by default).

1) Basic example — Register a CPT with a simple block template (PHP)

Below is a minimal example plugin-style snippet that registers a CPT and assigns a simple template with a heading and a paragraph. Put this code in a plugin file or in your themes functions.php (prefer plugin for portability).

 Case Studies,
        singular_name      => Case Study,
    )

    template = array(
        array( core/heading, array(
            level => 2,
            placeholder => Case study headline…
        ) ),
        array( core/paragraph, array(
            placeholder => Summary paragraph…
        ) ),
    )

    args = array(
        labels             => labels,
        public             => true,
        show_in_rest       => true,          // REQUIRED for block editor templates
        supports           => array( title, editor, thumbnail ),
        has_archive        => true,
        rewrite            => array( slug => case-studies ),
        template           => template,
        template_lock      => false,         // false  all  insert
    )

    register_post_type( case_study, args )
} )

Notes about this example

  • Each template block is an array with block name and attributes.
  • Attributes like placeholder are supported for core blocks custom blocks accept attributes you define.
  • Because show_in_rest is true and the editor is supported, the block editor will open and the template will be inserted for new posts of this type.

2) Complex nested template: columns, inner blocks, and custom blocks

Templates can express nested structures. The third array element contains inner blocks. Here is a more complex template with columns and inner paragraphs, plus a custom block placeholder.

 1,
            placeholder => Full-width headline…
        ) ),
        // Columns block with two column children:
        array(
            core/columns,
            array(),
            array(
                array(
                    core/column,
                    array( width => 50% ),
                    array(
                        array( core/paragraph, array( placeholder => Left column text… ) )
                    )
                ),
                array(
                    core/column,
                    array( width => 50% ),
                    array(
                        array( core/paragraph, array( placeholder => Right column text… ) )
                    )
                ),
            )
        ),
        // Custom block from a plugin, with attributes
        array( my-plugin/feature-grid, array(
            columns => 3
        ) ),
        array( core/paragraph, array(
            placeholder => Conclusion or CTA…
        ) ),
    )

    register_post_type( resource, array(
        labels       => array( name => Resources, singular_name => Resource ),
        public       => true,
        show_in_rest => true,
        supports     => array( title, editor ),
        template     => template,
        template_lock=> insert,  // prevents adding new blocks but allows reordering or editing existing (editor behavior may vary)
    ) )
} )

How nested blocks are represented

For nested blocks: [blockName, attributes, innerBlocksArray]. The innerBlocksArray follows the same structure recursively. Notice the columns example: core/columns has inner core/column blocks, which in turn contain core/paragraph.

3) Locking templates (template_lock)

  • false (default) — no lock users can add, remove, and rearrange blocks freely.
  • insert — prevents inserting new blocks into the template structure (editor behavior across versions may differ slightly) typically you can edit content of the provided blocks and rearrange, but cant add new sibling blocks.
  • all — fully lock the template structure and block types are enforced blocks are not removable or replaceable from the editor.

Use locking when you need strict editorial control (e.g., a landing page CPT with a rigid pattern). Avoid over-locking if content editors require flexibility.

4) Add a template to an existing CPT (registered by another plugin)

If a CPT is already registered by a third-party plugin or a theme and you want to attach a block template, use the register_post_type_args filter to modify the args before the type is finalized.

 2, placeholder => Book title (display-only) ) ),
            array( core/paragraph, array( placeholder => Short synopsis… ) ),
            array( core/paragraph, array( placeholder => Author bio or credits… ) ),
        )

        args[template_lock] = false
    }

    return args
}, 10, 2 )

Important

Use this filter early (priority default 10 is usually fine). Ensure you do not unintentionally remove other args set by the original registration. Typically you set only the keys you want to change and return the modified args.

5) Theme-based block template files (block-templates/.html)

If you are building a block-based theme or prefer to ship templates with your theme, create a file at your-theme/block-templates/{post-type}.html. The file should contain serialized block HTML (the same HTML you see when you copy a blocks content). Example file for case-study:


Case study headline…

Summary paragraph…

When the theme provides a block-templates file, the editor will use that template for new posts of that type. Theme templates are particularly handy when your layout is tightly coupled to theme CSS and markup.

6) Tips, gotchas, and best practices

  • REST/Block editor requirement: Always ensure show_in_rest =gt true and the editor is supported otherwise templates won’t apply in the block editor.
  • Template vs. existing posts: Templates apply to new posts (when you click Add New). Existing posts are not restructured automatically — they keep their current block content. To apply templates to existing posts programmatically, you must update post_content via code (risky — back up first).
  • Custom block availability: If you include custom blocks in the template, ensure those blocks are registered before the editor loads otherwise the editor will show an “unknown block” placeholder. Shipping blocks in plugin or theme that registers on init is common.
  • Translations and placeholders: Use translatable strings for placeholders if your project is localized for templates in PHP, wrap strings in translation functions like esc_html__( …, text-domain ) or similar.
  • Style and markup: When templates include HTML structure relying on theme CSS, prefer theme files so the template and theme are shipped together.
  • Debugging: If the template is not appearing, check browser console for JS errors and ensure the block editor is available for that post type.

7) Debugging checklist

  1. Is your CPT using the block editor? Verify show_in_rest is true and supports includes editor.
  2. Is the registration code running on init with correct priority? (Double-check plugin loading order.)
  3. If a custom block is used in the template, is that block registered and available when the editor loads?
  4. Are there JS console errors in the editor that might prevent template insertion?
  5. Are you testing with Add New? Templates are inserted on creation of a new post existing posts aren’t retrofitted.

8) Example: Complete plugin file (compact)

Drop this into a file like my-cpt-templates.php inside a plugin folder and activate.

 2, placeholder => Project title ) ),
        array( core/paragraph, array( placeholder => Project summary ) ),
        array(
            core/columns,
            array(),
            array(
                array(
                    core/column,
                    array(),
                    array(
                        array( core/image ),
                        array( core/paragraph, array( placeholder => Image caption or description ) ),
                    )
                ),
                array(
                    core/column,
                    array(),
                    array(
                        array( core/paragraph, array( placeholder => Project details ) )
                    )
                ),
            )
        ),
        array( core/paragraph, array( placeholder => Call to action or next steps ) ),
    )

    register_post_type( project, array(
        labels       => array( name => Projects, singular_name => Project ),
        public       => true,
        show_in_rest => true,
        supports     => array( title, editor, thumbnail ),
        menu_icon    => dashicons-portfolio,
        template     => project_template,
        template_lock=> insert,
    ) )
} )

9) Advanced considerations

  • Programmatic updates: To change templates after users have created content, consider feature flags or migration scripts that update specific posts instead of changing the template for all new posts (to avoid surprising editors).
  • Conditional templates: The template assigned in register_post_type is static for conditional templates you must create post content conditionally in PHP (e.g., upon post creation hook), or use multiple templates via separate CPTs or patterns.
  • Block patterns vs templates: Sometimes block patterns (register_block_pattern) are a more flexible alternative if you want reusable starting layouts without locking structure.

Final checklist before shipping

  1. Ensure block-editor support: show_in_rest amp supports are correct.
  2. Register custom blocks (if any) so they are available for the template.
  3. Test template insertion for new posts and confirm existing posts are unaffected unless explicitly migrated.
  4. Decide template locking policy and document it for editors.
  5. If shipping as a theme, consider putting the template in block-templates/{post-type}.html instead.

Useful references



Acepto donaciones de BAT's mediante el navegador Brave 🙂



Leave a Reply

Your email address will not be published. Required fields are marked *