Type something to search...
How to Convert a Classic WordPress Theme to a Block Theme?

How to Convert a Classic WordPress Theme to a Block Theme?

Many WordPress sites still run on a classic theme built years ago: PHP templates, a Customizer panel full of options, widget areas, and a stylesheet that has grown with every request. It works, but every layout change needs a developer, the block editor never quite matches the front end, and new WordPress features such as the Site Editor, style variations, and the Font Library are out of reach. Rebuilding from scratch is expensive. Converting the existing theme to a block theme keeps the design and moves the site onto the modern system, one piece at a time if needed.

This article covers what actually changes between a classic and a block theme, how to plan the conversion, how to add theme.json, how to adopt block template parts gradually inside a classic theme, how to turn PHP templates into block templates, and how to handle the pieces that do not map cleanly: menus, widgets, Customizer options, and plugin hooks. The examples use theme.json version 3 and WordPress 7.1.

What Changes Between Classic and Block Themes

A block theme is still a theme folder with a style.css header. The difference is that layouts are made of blocks stored as HTML, and global design settings live in theme.json instead of PHP and CSS.

Classic themeBlock theme equivalent
index.php, single.php, page.phptemplates/index.html, templates/single.html, templates/page.html
header.php, footer.php, sidebar.phpparts/header.html, parts/footer.html, parts/sidebar.html
template-parts/*.php loaded with get_template_part()Template parts, or patterns in patterns/*.php
The Loop with have_posts()The Query Loop block with a Post Template block
wp_nav_menu() and menu locationsThe Navigation block
register_sidebar() and widgetsBlocks placed directly in templates or template parts
Customizer colors, fonts, and optionstheme.json settings and global styles in the Site Editor
add_theme_support() for many featuresMostly automatic, or theme.json settings
Page templates with a PHP header commentCustom templates declared in theme.json

WordPress decides whether a theme is a block theme by one simple test: whether the theme has a templates/index.html file. The moment that file exists, WordPress treats the theme as a block theme, opens the Site Editor under Appearance → Editor, and stops using the PHP templates for any request that a block template can handle. That single switch is why the conversion needs a plan.

Choosing a Conversion Strategy

There are two practical approaches.

Full conversion. You build all block templates and parts on a staging site, then switch the live site in one release. This is the cleanest result and suits smaller themes, or themes you are redesigning anyway.

Gradual conversion. You keep the classic theme running and introduce block features one at a time: first theme.json, then block-based template parts for the header and footer, then individual templates. The final step, adding templates/index.html, flips the theme into block mode after most pieces are already proven. This suits large sites and themes with many custom features.

Either way, work on a staging copy with a fresh backup. The instructions in how to create a backup for a WordPress website apply before you change a single file.

Audit the Existing Theme First

Before writing any block markup, list what the theme actually does:

  1. Templates in use. Which of front-page.php, home.php, single.php, page.php, archive.php, category.php, search.php, and 404.php exist, plus any custom page templates.
  2. Customizer options. Colors, fonts, logo, layout toggles, footer text, and any theme mods stored in the database.
  3. Menus and widget areas. Every registered menu location and sidebar, and what is currently placed in each.
  4. Functions. Custom post types, shortcodes, image sizes, enqueued scripts, and filters in functions.php.
  5. Plugin integrations. Page builders, WooCommerce template overrides, and plugins that hook into theme-specific actions such as get_header or custom hooks the theme fires.

This list becomes your conversion checklist. Anything that relies on PHP template execution needs a new home.

Step 1: Add theme.json to the Classic Theme

theme.json works in classic themes too, so it is the safest first step. It defines the color palette, font sizes, spacing scale, and layout widths that the block editor uses, and it makes the editor match the front end.

Translate the Customizer's design options into presets. Create wp-content/themes/my-theme/theme.json:

{
  "$schema": "https://schemas.wp.org/wp/7.1/theme.json",
  "version": 3,
  "settings": {
    "appearanceTools": true,
    "layout": {
      "contentSize": "720px",
      "wideSize": "1200px"
    },
    "color": {
      "palette": [
        { "slug": "base", "name": "Base", "color": "#ffffff" },
        { "slug": "contrast", "name": "Contrast", "color": "#1d2327" },
        { "slug": "primary", "name": "Primary", "color": "#0a48ac" },
        { "slug": "muted", "name": "Muted", "color": "#f0f4f8" }
      ]
    },
    "typography": {
      "fontSizes": [
        { "slug": "small", "name": "Small", "size": "0.875rem" },
        { "slug": "medium", "name": "Medium", "size": "1.125rem" },
        { "slug": "large", "name": "Large", "size": "1.75rem" },
        { "slug": "x-large", "name": "Extra Large", "size": "2.5rem" }
      ]
    },
    "spacing": {
      "units": ["px", "rem", "%", "vw"]
    }
  },
  "styles": {
    "color": {
      "background": "var:preset|color|base",
      "text": "var:preset|color|contrast"
    },
    "elements": {
      "link": {
        "color": { "text": "var:preset|color|primary" }
      }
    }
  }
}

Check the existing site carefully after adding this file. theme.json generates CSS of its own, and it can override rules in your stylesheet. Fix conflicts now, while the rest of the theme is unchanged, and remove the matching add_theme_support( 'editor-color-palette' ) and add_theme_support( 'editor-font-sizes' ) calls, which theme.json replaces.

Fonts are part of the same file. The details of registering self-hosted fonts are in how to change fonts in a WordPress block theme using theme.json.

Step 2: Use Block Template Parts Inside the Classic Theme

Since WordPress 6.1, classic themes can use block-based template parts. This lets you rebuild the header and footer in blocks while the rest of the theme stays in PHP.

Enable the feature in functions.php:

<?php
// wp-content/themes/my-theme/functions.php
add_action( 'after_setup_theme', 'mytheme_setup' );

function mytheme_setup() {
add_theme_support( 'block-template-parts' );
add_theme_support( 'wp-block-styles' );
add_theme_support( 'editor-styles' );
add_editor_style( 'assets/css/editor.css' );
}

Create parts/header.html with the block version of your header:

<!-- wp-content/themes/my-theme/parts/header.html -->
<!-- wp:group {"align":"full","style":{"spacing":{"padding":{"top":"1.25rem","bottom":"1.25rem"}}},"layout":{"type":"constrained"}} -->
<div
  class="wp-block-group alignfull"
  style="padding-top:1.25rem;padding-bottom:1.25rem"
>
  <!-- wp:group {"align":"wide","layout":{"type":"flex","justifyContent":"space-between","flexWrap":"wrap"}} -->
  <div class="wp-block-group alignwide">
    <!-- wp:group {"layout":{"type":"flex","flexWrap":"nowrap"}} -->
    <div class="wp-block-group">
      <!-- wp:site-logo {"width":48} /-->
      <!-- wp:site-title {"level":0} /-->
    </div>
    <!-- /wp:group -->
    <!-- wp:navigation {"overlayMenu":"mobile"} /-->
  </div>
  <!-- /wp:group -->
</div>
<!-- /wp:group -->

Then replace the markup in header.php with a call to block_template_part(). Keep everything that must stay in PHP, especially wp_head() and wp_body_open():

<?php
// wp-content/themes/my-theme/header.php
?>
<!doctype html>
<html <?php language_attributes(); ?>>
<head>
<meta charset="<?php bloginfo( 'charset' ); ?>">
<meta name="viewport" content="width=device-width, initial-scale=1">
<?php wp_head(); ?>
</head>
<body <?php body_class(); ?>>
<?php wp_body_open(); ?>
<header class="site-header">
  <?php block_template_part( 'header' ); ?>
</header>

Repeat for the footer with parts/footer.html and block_template_part( 'footer' ) in footer.php, keeping wp_footer() before the closing body tag.

Administrators can now edit the header and footer visually from the template part screens under Appearance, while every PHP template keeps working. This step alone often delivers most of what clients wanted from "making the theme editable."

Step 3: Map PHP Template Tags to Blocks

Before converting full templates, it helps to know the block for each common template tag.

Classic PHPBlock
the_title()Post Title
the_content()Post Content
the_excerpt()Post Excerpt
the_post_thumbnail()Post Featured Image
the_date(), the_modified_date()Post Date
the_author()Post Author or Post Author Name
the_category(), the_tags()Post Terms
while ( have_posts() ) : the_post();Query Loop with Post Template
the_posts_pagination()Query Pagination
the_post_navigation()Post Navigation Link (previous and next)
comments_template()Comments
get_search_form()Search
the_archive_title()Query Title
get_template_part( 'template-parts/x' )Template Part, or a Pattern block
wp_nav_menu()Navigation
bloginfo( 'name' ), the_custom_logo()Site Title, Site Logo

Here is a typical classic single.php:

<?php
// wp-content/themes/my-theme/single.php
get_header();
?>
<main class="site-main">
<?php while ( have_posts() ) : the_post(); ?>
<article <?php post_class(); ?>>
  <h1><?php the_title(); ?></h1>
  <p class="meta"><?php echo get_the_date(); ?> &middot; <?php the_author(); ?></p>
  <?php the_post_thumbnail( 'large' ); ?>
  <?php the_content(); ?>
  <p class="tags"><?php the_tags( '', ', ' ); ?></p>
</article>
<?php the_post_navigation(); ?>
<?php comments_template(); ?>
<?php endwhile; ?>
</main>
<?php
get_footer();

And its block template equivalent:

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

<!-- wp:group {"tagName":"main","className":"site-main","layout":{"type":"constrained"}} -->
<main class="wp-block-group site-main">
  <!-- wp:post-title {"level":1} /-->

  <!-- wp:group {"className":"meta","layout":{"type":"flex","flexWrap":"wrap"}} -->
  <div class="wp-block-group meta">
    <!-- wp:post-date /-->
    <!-- wp:post-author-name /-->
  </div>
  <!-- /wp:group -->

  <!-- wp:post-featured-image {"sizeSlug":"large"} /-->
  <!-- wp:post-content {"layout":{"type":"constrained"}} /-->
  <!-- wp:post-terms {"term":"post_tag","separator":", "} /-->

  <!-- wp:group {"layout":{"type":"flex","justifyContent":"space-between"}} -->
  <div class="wp-block-group">
    <!-- wp:post-navigation-link {"type":"previous"} /-->
    <!-- wp:post-navigation-link /-->
  </div>
  <!-- /wp:group -->

  <!-- wp:comments -->
  <div class="wp-block-comments">
    <!-- wp:comments-title /-->
    <!-- wp:comment-template -->
    <!-- wp:comment-author-name /-->
    <!-- wp:comment-date /-->
    <!-- wp:comment-content /-->
    <!-- wp:comment-reply-link /-->
    <!-- /wp:comment-template -->
    <!-- wp:comments-pagination -->
    <!-- wp:comments-pagination-previous /-->
    <!-- wp:comments-pagination-next /-->
    <!-- /wp:comments-pagination -->
    <!-- wp:post-comments-form /-->
  </div>
  <!-- /wp:comments -->
</main>
<!-- /wp:group -->

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

Notice what is gone: get_header(), the_post(), and the while loop. On a single post, the Post blocks read the current post from context automatically. Also notice that you do not need wp_head() or the html element. WordPress wraps block templates in its own document shell and calls wp_head() and wp_footer() for you.

You rarely need to hand-write this markup. A faster workflow is to build the template visually in the editor, copy all blocks with Tools → Copy all blocks from the editor's options menu, and paste the result into the HTML file. Using the Gutenberg editor as your layout tool and the file as storage keeps the markup valid.

Converting Archive Loops

For archives, the Query Loop block replaces The Loop. Setting inherit to true tells it to use the main query for the current URL, exactly like the classic loop:

<!-- wp-content/themes/my-theme/templates/archive.html -->
<!-- wp:template-part {"slug":"header","tagName":"header"} /-->

<!-- wp:group {"tagName":"main","layout":{"type":"constrained"}} -->
<main class="wp-block-group">
  <!-- wp:query-title {"type":"archive"} /-->
  <!-- wp:term-description /-->

  <!-- wp:query {"queryId":1,"query":{"inherit":true}} -->
  <div class="wp-block-query">
    <!-- wp:post-template -->
    <!-- wp:post-featured-image {"isLink":true,"aspectRatio":"16/9"} /-->
    <!-- wp:post-title {"isLink":true,"level":2} /-->
    <!-- wp:post-excerpt /-->
    <!-- /wp:post-template -->

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

    <!-- wp:query-no-results -->
    <!-- wp:paragraph -->
    <p>No posts found.</p>
    <!-- /wp:paragraph -->
    <!-- /wp:query-no-results -->
  </div>
  <!-- /wp:query -->
</main>
<!-- /wp:group -->

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

Step 4: Move Dynamic PHP Output into Patterns

Block templates are static HTML. They cannot run PHP. Anything your classic templates computed on the fly needs another home:

  • Data that blocks already provide, such as dates, authors, and terms, becomes the matching block.
  • Small dynamic values at design time, such as the copyright year or translated strings, go into a pattern. Pattern files in patterns/*.php run PHP when WordPress registers them, and templates include them with the Pattern block.
  • Truly dynamic output per request, such as data from an external API or user-specific content, belongs in a custom block or an existing shortcode placed in a Shortcode block.

Here is a footer credit pattern that replaces <?php echo date( 'Y' ); ?> in footer.php:

<?php
/**
 * Title: Footer Credits
 * Slug: my-theme/footer-credits
 * Categories: footer
 * Inserter: no
 */
// wp-content/themes/my-theme/patterns/footer-credits.php
?>
<!-- wp:paragraph {"align":"center","fontSize":"small"} -->
<p class="has-text-align-center has-small-font-size">
<?php
echo esc_html(
  sprintf(
    /* translators: 1: Current year, 2: Site name. */
    __( '© %1$s %2$s. All rights reserved.', 'my-theme' ),
    wp_date( 'Y' ),
    get_bloginfo( 'name' )
  )
);
?>
</p>
<!-- /wp:paragraph -->

Reference it from parts/footer.html with <!-- wp:pattern {"slug":"my-theme/footer-credits"} /-->. One caveat: a pattern's PHP runs when the pattern is inserted or rendered from the theme. If a user customizes the footer in the Site Editor, the pattern's output is saved as static content, so the year stops updating. For values that must stay live, use a block that renders on the server.

Step 5: Replace Menus, Widgets, and Customizer Options

Menus

Block themes use the Navigation block instead of menu locations. Menus built in the Navigation block are stored as wp_navigation posts. When you add a Navigation block to the header, it can import an existing classic menu from its block settings, so the links do not need to be recreated. Check every location in your audit list and make sure each one is represented by a Navigation block in a template part. The classic approach is described in how to create a navigation menu in WordPress if you need to compare.

Widgets

Block themes have no widget areas, and the Appearance → Widgets screen disappears once the theme switches. For each widget area:

  1. Note what each widget does.
  2. Rebuild the area as a template part, such as parts/sidebar.html, using core blocks: Latest Posts, Categories, Search, Tag Cloud, Archives, Social Icons, and so on.
  3. For plugin widgets, look for the plugin's block version. If there is none, use its shortcode in a Shortcode block.
  4. Include the sidebar part in the templates that need it, usually inside a Columns block next to the main content.

Customizer Options

The Customizer is mostly hidden for block themes, and theme mods saved there are not used by block templates. Map each option:

Customizer optionBlock theme replacement
Colors and fontstheme.json presets and Styles in the Site Editor
Logo and site iconSite Logo block and Identity in the Site Editor
Layout toggles (sidebar on or off)Separate templates, or custom templates users can pick
Header or footer textEditable content in template parts
Additional CSSStyles → Additional CSS, or the theme stylesheet

Layout toggles are the biggest mindset change. Instead of a setting that switches the sidebar on and off in PHP, you offer two templates, such as Single Posts and Post with Sidebar, declared in theme.json under customTemplates.

Step 6: Simplify functions.php and Flip the Switch

Block themes enable several features automatically, so a converted functions.php is usually much shorter. WordPress adds support for post thumbnails, responsive embeds, editor styles, HTML5 markup, and automatic feed links to every block theme. Remove those calls and keep what is specific to your theme:

<?php
// wp-content/themes/my-theme/functions.php
add_action( 'wp_enqueue_scripts', 'mytheme_enqueue_styles' );

function mytheme_enqueue_styles() {
wp_enqueue_style(
  'my-theme-style',
  get_stylesheet_uri(),
  array(),
  wp_get_theme()->get( 'Version' )
);
}

add_action( 'after_setup_theme', 'mytheme_editor_styles' );

function mytheme_editor_styles() {
add_editor_style( 'style.css' );
}

// Keep custom image sizes, post types, and other site features
// here or, better, move them into a plugin.
add_image_size( 'card', 640, 400, true );

Content features such as custom post types and shortcodes belong in a plugin, not the theme. Moving them out during the conversion means a future theme change will not break your content. The same reasoning applies to custom post types registered by the old theme.

Update the style.css header to set a realistic minimum version:

/* wp-content/themes/my-theme/style.css */
/*
Theme Name: My Theme
Version: 2.0.0
Requires at least: 6.6
Requires PHP: 7.4
Text Domain: my-theme
*/

Finally, add templates/index.html. This is the switch. The finished structure looks like this:

my-theme/
├── assets/
│   └── fonts/
├── parts/
│   ├── footer.html
│   ├── header.html
│   └── sidebar.html
├── patterns/
│   └── footer-credits.php
├── styles/
├── templates/
│   ├── 404.html
│   ├── archive.html
│   ├── index.html
│   ├── page.html
│   ├── search.html
│   └── single.html
├── functions.php
├── screenshot.png
├── style.css
└── theme.json

Old PHP templates such as single.php can be deleted once their block equivalents exist. If a PHP template remains without a block equivalent, WordPress can still fall back to it for that request, which is useful during testing but confusing long-term.

Step 7: Test Before Going Live

Go through the site the way visitors and editors do:

  1. Every template in the hierarchy. Visit the front page, the posts page, a single post, a page, each custom page template, a category, a tag, an author archive, a search, and a 404 URL.
  2. The Site Editor. Open Appearance → Editor and check that every template and part loads without block validation errors.
  3. The block editor. Edit a post and confirm that colors, fonts, and widths match the front end.
  4. Plugins. Test forms, SEO plugins, ecommerce, and anything that outputs into the theme. Plugins that hooked into get_header, get_sidebar, get_footer, or the old theme's custom actions no longer fire, because block templates never call those functions.
  5. Performance. Block themes load only the CSS for blocks that appear on the page, so the result is often faster, but confirm with a page speed test.

The Create Block Theme plugin can help in this phase. It saves templates you refine in the Site Editor back into the theme files, which keeps the theme folder as the single source of truth.

Common Problems and Fixes

  • The site layout breaks as soon as templates/index.html is added. WordPress is now using block templates for every request. Build the missing templates, such as single.html and page.html, before adding index.html on the live site.
  • Changes to a template file have no effect. The template was customized in the Site Editor, so a database copy overrides the file. Reset the template in the Site Editor.
  • Styles from the old stylesheet fight with theme.json. Remove duplicated rules from style.css and move colors, fonts, and spacing into theme.json. Keep the stylesheet for things theme.json cannot express.
  • A plugin's output disappeared. It relied on a classic template hook. Check the plugin for a block, shortcode, or setting for block themes.
  • The header menu is empty. The Navigation block has no menu selected. Select it and import the classic menu, or choose an existing navigation menu.
  • "This block contains unexpected or invalid content." Hand-written markup does not match what the block saves. Rebuild the block in the editor and copy its markup, rather than editing HTML by hand.

Classic to Block Theme Conversion FAQ

WordPress checks for a templates/index.html file in the theme. If it exists, the theme is a block theme, the Site Editor becomes available, and block templates are used for rendering.

Yes. Add theme.json first, then enable block-template-parts support to rebuild the header and footer as block template parts, then convert templates one by one on staging. Adding templates/index.html is the final step that switches the theme into block mode.

No. Posts, pages, media, and users are not stored in the theme. What needs attention are theme-specific pieces such as widgets, Customizer settings, menu locations, and shortcodes the old theme provided.

Block themes have no widget areas, so the Widgets screen is removed. Rebuild each sidebar as a template part made of blocks, using block or shortcode versions of plugin widgets.

Yes, in functions.php and in pattern files, which run PHP when they are registered. Templates and template parts themselves are HTML and cannot contain PHP, so per-request dynamic output should come from blocks or shortcodes.

Convert when the design and custom features matter and only the editing experience needs to change. Switch to an existing block theme when you are planning a redesign anyway, because rebuilding an old design block by block takes more effort than adapting a modern theme.

Conclusion

Converting a classic theme to a block theme is less about rewriting everything and more about moving each responsibility to its new home. Design tokens move into theme.json, header and footer markup moves into block template parts, PHP templates become HTML templates built from theme blocks, menus become Navigation blocks, widget areas become template parts, and Customizer options become global styles or alternative templates.

The safest path is incremental: add theme.json, adopt block template parts inside the classic theme, convert templates on staging, audit plugins that depend on theme hooks, and add templates/index.html last. The result is a theme that editors can change visually, that matches the block editor on the front end, and that can use every new site-editing feature WordPress ships.

Here are some useful references for going deeper on block theme conversion:

  1. WordPress Developer Resources: Theme Handbook — templates, template parts, patterns, and theme.json for block themes.
  2. WordPress Developer Resources: Theme Structure — the standard folders and files in a block theme.
  3. WordPress Developer Resources: Global Settings and Styles — the theme.json reference for settings, styles, and presets.
  4. WordPress Developer Resources: block_template_part() — printing block template parts from a classic theme.
  5. WordPress.org Plugins: Create Block Theme — saves Site Editor changes into theme files and exports themes.
Tags :
Share :

Related Posts

WordPress optimization with specific recommended approach

WordPress optimization with specific recommended approach

Whether you run a high traffic WordPress installation or a small blog on a low cost shared host, you should optimize WordPress and your server to run

Continue Reading
Creating and Customizing WordPress Child Themes

Creating and Customizing WordPress Child Themes

Creating a child theme in WordPress is a best practice for making modifications to a theme. By using a child theme, you can update the parent theme w

Continue Reading
Understanding the Distinction Categories vs. Tags in WordPress

Understanding the Distinction Categories vs. Tags in WordPress

WordPress, a powerful content management system, offers a plethora of features to organize content effectively. Among these features, categories and

Continue Reading