WordPress 5.9 Full Site Editing: Block Theme Structure, Templates, and theme.json v2

WordPress 5.9, released in January 2022, delivered Full Site Editing (FSE) as a stable feature — a major architectural milestone that extends the Gutenberg block editor from editing post content to editing the entire site template: header, footer, archive pages, single post templates, 404 pages, and more. With FSE, block themes replace PHP template files (header.php, footer.php, single.php) with block template files (HTML files in the /templates/ and /parts/ directories). The theme.json file (version: 2) becomes the primary configuration mechanism for design tokens, block defaults, and style overrides. The Site Editor (wp-admin/site-editor.php) is the new UI for editing these templates visually. Understanding FSE’s architecture — and how it differs from classic theme development — is fundamental for developers building or converting themes in 2022 and beyond.

Problem: A developer wants to build a minimal block theme compatible with WordPress 5.9 FSE. The theme needs: a style.css with the correct block theme header, a theme.json (version 2), a /templates/index.html as the catch-all template, and a /parts/header.html block template part — with no PHP files required (except functions.php for enqueuing custom styles).

Solution: Create the required files for a minimal block theme. Declare the block theme capability in style.css, define templates in /templates/ as block markup HTML, and define reusable template parts in /parts/.

// ── File structure of a minimal block theme ───────────────────────────
// my-block-theme/
// ├── style.css            ← required: theme metadata
// ├── theme.json           ← required: design tokens, version 2
// ├── functions.php        ← optional: enqueue, hooks
// ├── templates/
// │   ├── index.html       ← required: catch-all template
// │   ├── single.html      ← optional: single post template
// │   └── 404.html         ← optional: 404 template
// └── parts/
//     ├── header.html      ← reusable template part
//     └── footer.html      ← reusable template part

// ── style.css (block theme header) ───────────────────────────────────
/*
Theme Name:   My Block Theme
Theme URI:    https://example.com
Description:  A minimal Full Site Editing block theme.
Version:      1.0.0
Requires at least: 5.9
Tested up to: 6.4
Requires PHP: 7.4
Text Domain:  my-block-theme
*/

// ── theme.json (version 2 for WP 5.9+) ───────────────────────────────
// See previous article for full theme.json structure.
// Version 2 added: fluid typography, individual padding/margin per side,
// and the 'styles.blocks' per-block style overrides.

// ── templates/index.html (catch-all block template) ───────────────────
// This is standard Gutenberg block markup, NOT PHP:
/*
<!-- wp:template-part {"slug":"header","theme":"my-block-theme","tagName":"header"} /-->

<!-- wp:group {"tagName":"main"} -->
<main class="wp-block-group">

    <!-- wp:query {"queryId":0,"query":{"perPage":10,"offset":0,"postType":"post"}} -->
    <div class="wp-block-query">

        <!-- wp:post-template -->
        <!-- wp:post-title {"isLink":true} /-->
        <!-- wp:post-excerpt /-->
        <!-- /wp:post-template -->

        <!-- wp:query-pagination -->
        <!-- wp:query-pagination-previous /-->
        <!-- wp:query-pagination-numbers /-->
        <!-- wp:query-pagination-next /-->
        <!-- /wp:query-pagination -->

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

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

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

<?php
// functions.php — minimal block theme setup
add_action( 'after_setup_theme', function () {
    // Block themes do NOT call add_theme_support for most features —
    // theme.json handles color palettes, font sizes, layout sizes, etc.
    // Only add_theme_support calls that theme.json cannot replace:
    add_theme_support( 'editor-styles' );
    add_editor_style( 'editor-style.css' );
} );

// Enqueue custom CSS (layout and component styles)
add_action( 'wp_enqueue_scripts', function () {
    wp_enqueue_style(
        'my-block-theme-style',
        get_stylesheet_uri(),
        [],
        wp_get_theme()->get( 'Version' )
    );
} );

// ── Registering custom template parts in PHP (optional) ───────────────
// WordPress automatically discovers template parts from the /parts/ directory.
// Register them explicitly to control their area (header, footer, general):
add_filter( 'default_wp_template_part_areas', function ( array $areas ): array {
    $areas[] = [
        'area'        => 'sidebar',
        'area_tag'    => 'aside',
        'label'       => __( 'Sidebar', 'my-block-theme' ),
        'description' => __( 'Reusable sidebar block area.', 'my-block-theme' ),
        'icon'        => 'sidebar',
    ];
    return $areas;
} );

NOTE: A block theme is detected by WordPress by the presence of the /templates/index.html file in the theme directory — this single file is the minimum requirement for a valid block theme. Classic themes (index.php-based) and block themes (templates/index.html-based) can coexist in the themes directory, but only block themes get access to the Site Editor. Converting a classic theme to a block theme is not a drop-in upgrade — the template system is fundamentally different. The most reliable migration path is to start with a new block theme and port design decisions using theme.json and custom block styles. The WP CLI command wp scaffold block-theme (via the Gutenberg plugin's CLI) generates a minimal starter block theme structure.