2026年11月12日

2026年11月12日

WordPress Classic Theme to Block Theme: Templates, theme.json, Widgets

This article is a localized edition of the Japanese original. Read the Japanese original

This article explains how to move a classic theme you built (or maintain for your company) to a block theme that can be edited in the Site Editor. It assumes WordPress as of October 2026.

You don't have to rebuild an existing classic theme all at once; you can also migrate it step by step. Either way, do the work on a staging site, and check the display and URLs before switching production over.

Differences between classic and block themes

Item Classic theme Block theme
Templates PHP files such as index.php and single.php HTML files such as templates/index.html (block markup)
Header and footer header.php, footer.php parts/header.html, parts/footer.html (template parts)
Colors, fonts, spacing style.css and the Customizer theme.json and "Styles" under "Appearance" → "Editor" in the admin
Menus "Appearance" → "Menus" Navigation block
Widgets "Appearance" → "Widgets" Place blocks directly in templates and template parts

WordPress treats a theme as a block theme when it has templates/index.html. The minimum is style.css (with the theme header) and templates/index.html; you would normally add theme.json as well.

Step 1: Prepare and take stock before migrating

If you can use WP-CLI, check the current state with these commands.

# Check the active theme
wp theme list --status=active

# Back up the database
wp db export backup-before-migration.sql

# List widget areas (sidebars)
wp sidebar list

# Widgets placed in a specific widget area (replace sidebar-1 with an ID from the list above)
wp widget list sidebar-1 --fields=name,id,options

# List classic menus
wp menu list

Also write down the following:

  • The template files the theme has (single.php, page.php, archive.php, 404.php, etc.) and what each one displays
  • The add_theme_support(), register_nav_menus() and register_sidebar() calls in functions.php, plus any custom shortcodes and hooks
  • What you have set in the Customizer (logo, colors, the contents of "Additional CSS," etc.)

Step 2: Decide how to proceed

Migrate gradually (convert parts while staying a classic theme)

Even as a classic theme, you can:

  • Add theme.json so that color and font-size presets and the content width apply in the block editor
  • Add add_theme_support( 'block-template-parts' ); so that template parts (HTML in parts/) can be edited in the admin
  • Use add_theme_support( 'block-templates' ); to apply templates built with blocks to individual posts and pages (it is enabled automatically for classic themes that have theme.json)

A PHP template can load a template part with block_template_part( 'header' );. This lets you convert only the header to blocks first and keep the main content in PHP.

Rebuild as a new block theme

If the theme has few templates, or you are revisiting the design anyway, it can be faster to build a new block theme and switch at the end. The "Create Block Theme" plugin (available in the official WordPress.org plugin directory) can create a blank block theme and write changes made in the Site Editor out to the theme's files.

The steps below are the conversion methods used in either approach.

Step 3: Replace header.php with a template part

Here is an example header.php from a classic theme.

<?php
// Before migration: header portion of header.php
?>
<header id="site-header">
	<div class="site-branding">
		<?php the_custom_logo(); ?>
		<h1 class="site-title">
			<a href="<?php echo esc_url( home_url( '/' ) ); ?>"><?php bloginfo( 'name' ); ?></a>
		</h1>
	</div>
	<nav>
		<?php wp_nav_menu( array( 'theme_location' => 'primary' ) ); ?>
	</nav>
</header>

In a block theme, you write the same content as block markup in parts/header.html. Replace the logo, site title and menu with the "Site Logo," "Site Title" and "Navigation" blocks.

<!-- wp:group {"style":{"spacing":{"padding":{"top":"1rem","bottom":"1rem"}}},"layout":{"type":"flex","justifyContent":"space-between"}} -->
<div class="wp-block-group" style="padding-top:1rem;padding-bottom:1rem">
<!-- wp:site-logo {"width":120} /-->
<!-- wp:site-title {"level":1} /-->
<!-- wp:navigation {"overlayMenu":"mobile"} /-->
</div>
<!-- /wp:group -->

Specify the <header> element with tagName when the template loads this part (next step). If you also set header inside the part, you end up with a nested <header>.

Rather than typing markup by hand, you make fewer mistakes by arranging blocks in the Site Editor and then extracting the markup with "Copy" from the block's "⋮" menu or with the code editor. If block markup doesn't match the block's saved format, the editor shows "Block contains unexpected or invalid content."

Step 4: Replace index.php with an HTML template

Here is an example index.php from a classic theme.

<?php
// Before migration: index.php
get_header();
if ( have_posts() ) :
	while ( have_posts() ) :
		the_post();
		get_template_part( 'template-parts/content' );
	endwhile;
	the_posts_pagination();
endif;
get_footer();

In a block theme, write it in templates/index.html. Replace the loop with the "Query Loop" block and use inherit: true so it uses the page's own query (on an archive, that archive's posts).

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

<!-- wp:group {"tagName":"main","layout":{"type":"constrained"}} -->
<main class="wp-block-group">
<!-- wp:query {"query":{"inherit":true}} -->
<div class="wp-block-query">
<!-- wp:post-template -->
<!-- wp:post-title {"isLink":true} /-->
<!-- wp:post-date /-->
<!-- 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","tagName":"footer"} /-->

The template hierarchy names stay the same and only the extension changes to .html: single.php becomes templates/single.html, page.php becomes templates/page.html, archive.php becomes templates/archive.html, and 404.php becomes templates/404.html. In templates for individual posts, display the body with the "Content" block (<!-- wp:post-content /-->).

Step 5: Move CSS colors and fonts to theme.json

Here is an example style.css before migration.

/* Before migration: colors and fonts in style.css */
:root {
	--color-primary: #0073aa;
	--color-text: #1d2327;
	--font-base: "Noto Sans JP", sans-serif;
}
body {
	font-family: var(--font-base);
	font-size: 16px;
	color: var(--color-text);
}
a {
	color: var(--color-primary);
}

Write the same settings in theme.json at the root of the theme folder. JSON does not allow comments, so keep any explanations outside the file.

{
	"$schema": "https://schemas.wp.org/trunk/theme.json",
	"version": 3,
	"settings": {
		"color": {
			"palette": [
				{ "slug": "primary", "color": "#0073aa", "name": "Primary" },
				{ "slug": "text", "color": "#1d2327", "name": "Text" }
			],
			"defaultPalette": false
		},
		"typography": {
			"fontFamilies": [
				{
					"fontFamily": "\"Noto Sans JP\", sans-serif",
					"slug": "base",
					"name": "Base"
				}
			],
			"defaultFontSizes": false
		},
		"layout": {
			"contentSize": "760px",
			"wideSize": "1200px"
		}
	},
	"styles": {
		"color": {
			"text": "var(--wp--preset--color--text)"
		},
		"typography": {
			"fontFamily": "var(--wp--preset--font-family--base)",
			"fontSize": "1rem"
		},
		"elements": {
			"link": {
				"color": {
					"text": "var(--wp--preset--color--primary)"
				}
			}
		}
	}
}

Colors defined in settings are output as CSS variables such as --wp--preset--color--primary and also appear as color choices in the editor. "defaultPalette": false and "defaultFontSizes": false hide WordPress's default color and font-size choices. "Noto Sans JP" only names the font, so either load the font files separately or rely on fonts installed on the visitor's device.

Detailed CSS that can't move to theme.json (styling for specific classes, for example) stays in style.css and is loaded separately (Step 8).

Step 6: Move menus to the Navigation block

Classic menus remain in the database after you switch to a block theme. In the Site Editor, select the Navigation block in the header and choose "Import Classic Menus" from the block's menu selector to bring an existing menu in as a Navigation block menu.

From then on, the imported menu is managed as data separate from the classic menu. Changes to the classic menu are not reflected in the Navigation block.

Step 7: Rebuild widget content as blocks

Widgets are stored not as posts (in a post type such as wp_block) but as options in the wp_options table. Which widgets sit in which widget area is stored in sidebars_widgets, and each widget's settings are stored in options such as widget_text or widget_block (block widgets).

Block themes have no "Appearance" → "Widgets" screen and don't use dynamic_sidebar(), so widgets stop being displayed when you switch to a block theme. The data remains but is not moved automatically. Before switching, record the content from the wp widget list output in Step 1 or from the screen, and place blocks with the same content in your templates or template parts.

Here is an example parts/footer.html for a site that had widgets in the footer.

<!-- wp:group {"layout":{"type":"constrained"}} -->
<div class="wp-block-group">
<!-- wp:columns -->
<div class="wp-block-columns">
<!-- wp:column -->
<div class="wp-block-column">
<!-- wp:site-title {"level":0} /-->
<!-- wp:site-tagline /-->
</div>
<!-- /wp:column -->

<!-- wp:column -->
<div class="wp-block-column">
<!-- wp:latest-posts {"postsToShow":5} /-->
</div>
<!-- /wp:column -->
</div>
<!-- /wp:columns -->
</div>
<!-- /wp:group -->

Commonly used widgets can be replaced with blocks such as "Latest Posts," "Categories," "Archives," "Search" and "Custom HTML." For widgets that came from a plugin, check whether the plugin provides a block.

Step 8: Clean up functions.php

For block themes, WordPress automatically enables the following theme supports (_add_default_theme_supports() in wp-includes/theme.php):

  • post-thumbnails
  • responsive-embeds
  • editor-styles
  • html5 (comment form, search form, etc.)
  • automatic-feed-links

So you can remove those add_theme_support() calls. On the other hand, block themes don't load style.css automatically, so load any CSS you kept yourself.

<?php
// Example functions.php after migration
add_action( 'after_setup_theme', function () {
	// Load style.css in the editor too (editor-styles is enabled automatically)
	add_editor_style( 'style.css' );
} );

// Load style.css on the front end
add_action( 'wp_enqueue_scripts', function () {
	wp_enqueue_style(
		'my-theme-style',
		get_stylesheet_uri(),
		array(),
		wp_get_theme()->get( 'Version' )
	);
} );

The following are no longer needed in a block theme:

  • register_sidebar() and dynamic_sidebar() in templates
  • PHP templates that use get_header(), get_footer() and get_sidebar()
  • add_theme_support( 'customize-selective-refresh-widgets' )
  • Customizer settings (items added with customize_register). In a block theme, change colors and fonts under "Appearance" → "Editor" → "Styles"

You can keep register_nav_menus() until you have imported the classic menus. Code unrelated to display, such as custom shortcodes, custom post types and hooks, keeps working as is (consider moving it out of the theme into a plugin).

Step 9: What to check after switching

  • Whether the front page, single posts, pages, category and tag archives, search results and the 404 page all display as expected
  • Whether the <head> output from your SEO plugin or theme (<title>, meta description, OGP, structured data, etc.) has changed (block themes still run wp_head() automatically)
  • Whether any feature depends on theme-specific hooks other than wp_head and wp_footer (for example, plugin code that assumes get_header)
  • Whether the CSS you wrote in the Customizer's "Additional CSS" is still applied (if not, move it to Additional CSS in the Site Editor's "Styles" or to style.css)

Summary

  • A block theme is a theme with templates/index.html. Replace each PHP template with an .html template of the same name
  • Turn the header and footer into template parts in parts/, and set elements such as <header> with the template part block's tagName
  • Move colors, fonts and widths to theme.json. JSON doesn't allow comments
  • Bring menus in with "Import Classic Menus" in the Navigation block
  • Widgets stay in wp_options but are no longer displayed, so record their content and rebuild it with blocks
  • post-thumbnails and others are enabled automatically. Load style.css yourself

Related articles:

Questions about WordPress? The publisher of this site is based in Japan and replies in Japanese. Contact (Japanese)

お気軽にご相談ください

お見積りへ お問い合わせへ
▲