Building Block Theme Templates and Template Parts with Full Site Editing

WordPress Full Site Editing (FSE) replaces the classic PHP template hierarchy — single.php, header.php, footer.php, calls to get_header() — with a unified system of block-markup HTML files stored in the theme’s templates/ and parts/ directories. A block theme’s templates/single.html contains WordPress block comment markup that the runtime parses and renders; parts/header.html is a reusable template part referenced from templates with the <!-- wp:template-part {"slug":"header","tagName":"header"} /--> block. The template resolution order mirrors the classic hierarchy: for a single post WordPress checks (1) a user-edited template in the wp_template database custom post type for this site, (2) templates/single-{post-type}-{slug}.html in the theme, (3) templates/single-{post-type}.html, (4) templates/single.html, (5) templates/index.html, (6) a WordPress core fallback. Template parts follow the same three-tier lookup: database wp_template_part → theme parts/{slug}.html → core. When a user edits a template in the Site Editor (Appearance → Editor), WordPress saves the customised version as a wp_template database post — this record permanently takes precedence over the theme file until the user resets to theme defaults. Dynamic content is injected through core query blocks: <!-- wp:post-title /-->, <!-- wp:post-content /-->, <!-- wp:post-featured-image /-->, <!-- wp:query /--> with inner <!-- wp:post-template /--> — these render server-side via their PHP render_callback without any custom PHP in the theme. Custom templates are registered in theme.json under "customTemplates" and appear in the Template dropdown in the Page/Post editor sidebar. Template parts are registered under "templateParts" with an "area" value ("header", "footer", "general") that controls which section of the Site Editor displays them. The theme.json global styles post covered the design token layer; the Block Bindings API post covered dynamic attribute injection — FSE block templates are the structural layer that ties both together into a complete, visually editable page architecture.

Problem: A WordPress portfolio theme uses single-portfolio.php with get_header( 'portfolio' ), a separate header-portfolio.php, and a custom sidebar. Migrating to a block theme requires converting this to block template files while keeping the right-sidebar layout with project metadata editable through the Site Editor.

Solution: Create templates/single-portfolio.html using the Group, Columns, Template Part, and post-content blocks, register it in theme.json under customTemplates, and create parts/sidebar-portfolio.html for the project metadata block region.

<!-- templates/single-portfolio.html -->
<!-- wp:template-part {"slug":"header","tagName":"header"} /-->

<!-- wp:group {"tagName":"main","layout":{"type":"constrained"}} -->
<main class="wp-block-group">

    <!-- wp:columns -->
    <div class="wp-block-columns">

        <!-- wp:column {"width":"66.66%"} -->
        <div class="wp-block-column" style="flex-basis:66.66%">
            <!-- wp:post-featured-image {"isLink":false,"aspectRatio":"16/9"} /-->
            <!-- wp:post-title {"level":1} /-->
            <!-- wp:post-content {"layout":{"type":"constrained"}} /-->
        </div>
        <!-- /wp:column -->

        <!-- wp:column {"width":"33.33%"} -->
        <div class="wp-block-column" style="flex-basis:33.33%">
            <!-- wp:template-part {"slug":"sidebar-portfolio","tagName":"aside"} /-->
        </div>
        <!-- /wp:column -->

    </div>
    <!-- /wp:columns -->

</main>
<!-- /wp:group -->

<!-- wp:template-part {"slug":"footer","tagName":"footer"} /-->

<!-- parts/sidebar-portfolio.html -->
<!-- wp:group {"backgroundColor":"base-2","layout":{"type":"flow"},"style":{"spacing":{"padding":{"all":"var:preset|spacing|30"}}}} -->
<div class="wp-block-group has-base-2-background-color has-background">
    <!-- wp:heading {"level":3,"fontSize":"medium"} -->
    <h3 class="wp-block-heading has-medium-font-size">Project Details</h3>
    <!-- /wp:heading -->

    <!-- wp:post-terms {"term":"portfolio_category","separator":", "} /-->
    <!-- wp:post-date {"format":"F j, Y","prefix":"Completed: "} /-->

    <!-- wp:separator {"className":"is-style-wide"} -->
    <hr class="wp-block-separator is-style-wide"/>
    <!-- /wp:separator -->

    <!-- wp:buttons -->
    <div class="wp-block-buttons">
        <!-- wp:button {"width":100} -->
        <div class="wp-block-button has-custom-width wp-block-button__width-100">
            <a class="wp-block-button__link wp-element-button" href="#contact">Start a Project</a>
        </div>
        <!-- /wp:button -->
    </div>
    <!-- /wp:buttons -->
</div>
<!-- /wp:group -->

{
    "version": 3,
    "customTemplates": [
        {
            "name": "single-portfolio",
            "title": "Single Portfolio Item",
            "postTypes": [ "portfolio" ]
        },
        {
            "name": "page-no-title",
            "title": "Page (No Title)",
            "postTypes": [ "page" ]
        }
    ],
    "templateParts": [
        { "name": "header",           "title": "Header",            "area": "header"  },
        { "name": "footer",           "title": "Footer",            "area": "footer"  },
        { "name": "sidebar-portfolio","title": "Portfolio Sidebar", "area": "general" }
    ]
}

NOTE: When a user edits a template in the Site Editor, WordPress creates a wp_template post with post_name = {theme-slug}//{template-name} (e.g., my-block-theme//single-portfolio). This database record takes precedence over the theme file — shipping a theme update with a revised template file has no effect for users who have edited it in the Site Editor. The “Reset to theme defaults” button in the Site Editor deletes the database record and restores theme-file control. During development, clear all database-stored template overrides with WP-CLI: wp post delete --force $(wp post list --post_type=wp_template --format=ids) to force WordPress to re-read the theme files — this is essential when you update a template file during development and wonder why your changes are not showing up.