Migrating Classic Themes to Block Themes: A Developer’s Complete Guide

If you built WordPress sites for any length of time, you know classic PHP themes inside out. The get_header() call at the top, the Loop, a functions.php that grew to 800 lines by year three. It works. It has always worked. But Full Site Editing is not a gimmick you can ignore any longer – it is the direction WordPress is moving, and block themes are the vehicle. This guide walks through a real migration: what actually changes, what stays the same, and where developers get tripped up.


Why Migrate at All

The honest answer is not “because Gutenberg said so.” The real reasons are practical.

Classic themes put layout logic inside PHP files. When a client wants to change the header, they need a developer. With a block theme and the Site Editor, a non-developer can move the logo, swap the navigation menu, or restructure the footer without touching a single file. That reduction in support tickets alone justifies the migration cost for most agencies.

Beyond the client-facing argument, block themes benefit developers too. No more juggling wp_enqueue_scripts for every little color variable – theme.json handles design tokens globally. Template files are plain HTML with block markup, readable by anyone on the team. Patterns replace repetitive get_template_part() calls with reusable block compositions you can share across sites.

  • Global Styles give non-developers real layout control without code changes
  • theme.json replaces scattered CSS custom properties and add_theme_support() calls
  • HTML templates are easier to version-control and review than PHP templates
  • Block patterns replace repeated get_template_part() calls across post types
  • Style variations let you ship multiple visual themes from one codebase
  • No jQuery dependency by default – block themes ship lighter

Understanding the Structural Shift

Before touching any code, get clear on what is actually changing. The mental model is the biggest hurdle.

In a classic theme, PHP templates are the source of truth. The browser receives HTML that PHP assembled on the server. In a block theme, HTML template files contain block markup. WordPress parses that markup, renders each block with its registered PHP callback, and sends the result to the browser. The output is similar but the pipeline is different.

Classic ThemeBlock Theme
header.php + get_header()parts/header.html + wp:template-part
functions.php with add_theme_support()theme.json settings section
sidebar.php + register_sidebar()Block patterns with wp:widget-area or dedicated template parts
style.css enqueued via wp_enqueue_style()style.css (still present) + theme.json styles
get_template_part() callsBlock patterns registered in /patterns/ directory
Custom page templates via PHP file + comment headerTemplate files in /templates/ directory

The key insight: you are not removing PHP from WordPress. Core blocks still use PHP render callbacks. You are removing PHP from your theme layer and replacing it with structured HTML that the block editor can read and the Site Editor can modify.


Phase 1 – Converting PHP Templates to Block HTML Templates

This is the most hands-on phase. Each PHP template in your classic theme maps to an HTML template file in the block theme’s /templates/ directory.

Template Hierarchy Still Applies

Good news: WordPress’s template hierarchy did not change. single.html handles single posts. archive.html handles archives. 404.html handles not-found pages. The same filename conventions you already know carry over. You just drop the .php extension and replace PHP calls with block markup.

Here is what a classic index.php looks like, and its block theme equivalent:

Classic index.php:

Block theme index.html:

Notice what disappeared: no PHP, no Loop, no conditional checks for post existence. The wp:query block handles the loop. wp:post-template replaces while ( have_posts() ). wp:query-no-results replaces the else branch.

Template Parts: Replacing header.php, footer.php, sidebar.php

Classic themes use get_header(), get_footer(), and get_sidebar() to pull in shared template pieces. Block themes use template parts stored in /parts/.

Create a /parts/header.html file. Inside it, place your header block markup – site logo, navigation block, any header group structure you need. Then reference it from any template:

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

One line replaces get_header() in every template file

The tagName attribute sets the semantic HTML element. Use "header" for the site header, "footer" for the site footer, "aside" for a sidebar area. WordPress wraps the template part output in that element.

For sidebars specifically: if your classic theme registered widget areas with register_sidebar(), you have two options. You can keep a widget area block (wp:widget-area) that still renders classic widgets, or you can replace the sidebar entirely with a block pattern containing wp:latest-posts, wp:tag-cloud, and other equivalent blocks. The second approach is cleaner for new sites but requires migrating any custom widgets.

Phase 2 – Building theme.json from functions.php

This phase often surprises developers. A lot of what lived in functions.php – color palettes, font sizes, layout widths, theme support declarations – moves directly into theme.json. The file is JSON, not PHP, and it is declarative: you describe what your theme supports and what defaults it uses.

Here is a before/after view. The classic functions.php setup:

And the equivalent theme.json (using schema version 3, current as of WordPress 6.6+):

What theme.json Handles That functions.php Used To

  • Color palettes: Define colors in settings.color.palette – they appear in the editor color pickers automatically
  • Font sizes: Define in settings.typography.fontSizes – shows up in the Typography panel, supports fluid/clamp values
  • Spacing scale: Define in settings.spacing.spacingSizes – appears in block spacing controls
  • Layout widths: Set settings.layout.contentSize and settings.layout.wideSize instead of adding add_theme_support( 'align-wide' ) and hardcoding a pixel value
  • Typography settings: Control whether users can change font family, font size, line height via settings.typography.* flags
  • Default styles: Set base body colors, link styles, and element styles in styles.*

What Still Stays in functions.php

Not everything moves to JSON. Some things still need PHP:

  • Custom post types and taxonomies (register_post_type(), register_taxonomy())
  • REST API endpoints
  • Hook-based customizations that alter query behavior
  • Plugin integrations that require PHP callbacks
  • Custom block variations, block styles registered with PHP
  • Enqueuing any scripts or styles that are not handled by theme.json

Think of theme.json as handling visual configuration, and functions.php as handling behavioral logic. The file does not disappear – it just shrinks significantly.


Phase 3 – Converting Widgets to Blocks

Widget areas were a classic theme staple. If your site used them heavily, this phase takes the most planning.

Inventory Your Widgets First

Before touching anything, list every widget area your classic theme registered and what widgets are active in each. Go to Appearance > Widgets and screenshot the current state. This is your migration checklist.

Most common classic widgets have direct block equivalents:

Classic WidgetBlock Equivalent
Recent Postswp:latest-posts
Categorieswp:categories
Tag Cloudwp:tag-cloud
Searchwp:search
Text / HTML Widgetwp:html or wp:paragraph
Image Widgetwp:image
Navigation Menuwp:navigation
Custom HTMLwp:html
RSS Feedwp:rss
Calendarwp:calendar

Handling Custom Widgets

If your classic theme had custom widgets – a “Featured Product” widget, a newsletter signup widget, an author bio box – you need to convert those to blocks. The path depends on complexity:

  • Simple display widgets: Convert to a block pattern. A “Featured Product” box is just a group block with an image, heading, paragraph, and button inside it.
  • Dynamic widgets (pull from the database): Register a proper block with a PHP render callback, or use a server-side rendered block. The Create Block Theme plugin does not do this for you – it requires actual block development.
  • Third-party plugin widgets: Most plugin authors have already shipped block versions of their widgets. Check the plugin’s changelog before building a replacement from scratch.

Phase 4 – Pattern Conversion

Patterns in block themes are the replacement for get_template_part() calls. They are composable, reusable, and editor-accessible – your clients can insert them from the block inserter without touching code.

WordPress 6.0 introduced auto-registration for patterns. Drop any PHP file into the /patterns/ directory at the root of your theme. WordPress reads the file header comment and registers the pattern automatically – no register_block_pattern() call needed.

Pattern file header format:

Synced vs. Unsynced Patterns

WordPress 6.3 introduced synced patterns (previously called reusable blocks). A synced pattern is stored in the database and propagates changes everywhere it is used. An unsynced pattern is a template that gets copied when inserted – each instance is independent.

Use synced patterns for global elements like CTAs, promotional banners, or contact blocks that should stay consistent sitewide. Use unsynced patterns for layout starters that each page customizes independently.


Using the Create Block Theme Plugin

The Create Block Theme plugin (from the WordPress.org themes team) is the fastest way to bootstrap a block theme or export customizations. Install it, then use Appearance > Create Block Theme to:

  • Create a new blank block theme: Generates the required file structure – templates/index.html, parts/header.html, parts/footer.html, theme.json, style.css with the required header comment
  • Create a child block theme from the current active theme: Useful if you are starting from an existing block theme like Twenty Twenty-Four and want to layer customizations
  • Save Global Styles changes to theme.json: If you spent time in Appearance > Editor > Styles tweaking colors and typography, use this to persist those changes back to theme.json so they are part of the theme file rather than database overrides
  • Export the theme as a ZIP: Produces a distributable theme package with all your Site Editor customizations baked in

One important nuance: Global Styles customizations saved via the Site Editor are stored in the database (in the wp_posts table as a wp_global_styles post). When you export with Create Block Theme, it merges those database values back into theme.json. This means your exported theme works on a fresh WordPress install without needing a database migration.


Testing Checklist Before Go-Live

Run through this checklist before switching the live site to the block theme. Most issues surface here rather than in production if you test thoroughly.

Template Coverage

  • Visit a single post – does single.html render correctly?
  • Visit a single page – does page.html apply?
  • Visit the blog index – does index.html or home.html show correctly?
  • Visit a category archive – does archive.html apply?
  • Visit a tag archive – same
  • Visit a search results page – does search.html exist?
  • Trigger a 404 – does 404.html render?
  • Check any custom post type archives and singles – do their templates exist?

Content Integrity

  • Open 5 posts from different authors and dates – does the post meta display correctly?
  • Check posts with featured images – do they render and link correctly?
  • Check posts without featured images – is the fallback behavior acceptable?
  • View posts with embedded videos, galleries, or audio – do media blocks still render?
  • Check comment sections on posts with comments enabled

Navigation and Header/Footer

  • Does the primary navigation menu display and all items link correctly?
  • Does the mobile navigation work (hamburger/responsive menu)?
  • Are footer links and footer widget content present?
  • Does the site logo appear and link to the homepage?

Forms and Dynamic Content

  • Test any contact forms (Contact Form 7, WPForms, Gravity Forms) – do they render?
  • Test WooCommerce pages if applicable – shop, product, cart, checkout
  • Test any membership pages (MemberPress, Restrict Content Pro)
  • Check search functionality end-to-end – search input, results page, no-results state

Performance and Technical

  • Run Lighthouse on the homepage – compare to the classic theme baseline
  • Check the browser console for JavaScript errors
  • Validate HTML output with the W3C Validator on key pages
  • Confirm structured data (Schema.org) is still present if you had it in the classic theme
  • Verify Open Graph meta tags are rendering (usually via a plugin, not theme-dependent)
  • Check RSS feed at /feed/ – content should be intact

The template hierarchy did not change. Your filename conventions carry directly from classic to block themes – just swap .php for .html and PHP calls for block markup.


Common Pitfalls and How to Avoid Them

1. Forgetting the Required Style.css Header

Every WordPress theme – classic or block – needs a style.css with the theme header comment. This is where WordPress reads the Theme Name, Author, Version, and other metadata. Block themes still need this file. What changes is that you no longer need to enqueue it manually – WordPress handles that. But the file and its header must exist or the theme will not appear in Appearance > Themes.

2. Hardcoding Colors and Sizes in CSS

In a classic theme it was normal to put color values directly in style.css. In a block theme, colors defined in theme.json get auto-generated as CSS custom properties: --wp--preset--color--primary. If you also hardcode those same colors in CSS, you get two sources of truth that drift apart. Define colors once in theme.json, then reference the generated custom properties everywhere in CSS.

3. Using Classic Sidebars Without a Plan

Block themes can still register widget areas and use the wp:widget-area block. But this is a compatibility shim, not the intended approach. If you plan to use the Site Editor for layout control, widget areas break the editing experience because they are not editable in the Site Editor. Commit to one approach: classic widgets (simpler migration, worse editing experience) or block patterns/template parts (more migration work, full Site Editor compatibility).

4. Missing the index.html Fallback

WordPress requires at least one template file: templates/index.html. Without it, the theme will not activate. This is the block theme equivalent of the classic requirement for index.php. Every other template is optional and falls back to this one if not present. Start here before adding any other templates.

5. Global Styles Overriding Your theme.json

When a user saves customizations in Appearance > Editor > Styles, those changes are stored in the database and override theme.json values. This trips up developers who edit theme.json and then wonder why their changes are not appearing on the site. Check Global Styles in the database first. Use the “Reset to defaults” option in the Styles panel to clear database overrides and start fresh from theme.json.

6. Plugin Incompatibility

Some plugins hook into classic theme action hooks like get_header, get_footer, or template-specific hooks. Block themes do not fire these hooks in the same way because there are no PHP templates calling them. Run the Health Check plugin and review your active plugins against block theme compatibility lists before the migration. Most major plugins are block-compatible now, but some older or niche plugins may need updates or replacements.


Migration Strategy: Child Theme Bridge vs. Full Rewrite

For complex sites, a direct full rewrite carries a lot of risk. Consider a bridge approach instead.

The Child Block Theme Bridge

If you are migrating an existing site running on a classic theme that has a popular block theme successor (Storefront to a WooCommerce block theme, for example), you can create a child block theme. The parent provides the base template structure; your child theme customizes without touching the parent. This gives you a block theme architecture while maintaining a fallback and requiring less from-scratch work.

Staged Rollout

For large sites with many post types, a staged approach works well. Migrate the front-end templates first (header, footer, blog index, single posts). Leave the WooCommerce templates or BuddyPress templates until the core migration is stable. Use the theme switcher on a staging server. Run both themes in parallel – the new block theme on staging, the classic theme on production – and compare until you are confident.


Quick Reference: Classic to Block Theme File Map

Classic FileBlock Theme EquivalentNotes
index.phptemplates/index.htmlRequired. WordPress falls back to this if no other template matches.
single.phptemplates/single.htmlSingle post template
page.phptemplates/page.htmlSingle page template
archive.phptemplates/archive.htmlArchives (categories, tags, custom taxonomy)
search.phptemplates/search.htmlSearch results
404.phptemplates/404.htmlNot found page
front-page.phptemplates/front-page.htmlHomepage override
header.phpparts/header.htmlReferenced with wp:template-part
footer.phpparts/footer.htmlReferenced with wp:template-part
sidebar.phpparts/sidebar.html or inline patternNo direct equivalent – use template part or block pattern
functions.phptheme.json + functions.php (slimmed)Visual config moves to JSON, behavioral logic stays in PHP
template-parts/*.phppatterns/*.phpAuto-registered in WP 6.0+ via file header comments
style.cssstyle.cssStill required. Header comment still needed. No longer manually enqueued.

Where to Go From Here

Once the core migration is done and your testing checklist passes, the block theme world opens up considerably. Style variations let you ship multiple visual themes from one codebase – dark mode, high contrast, seasonal variants – all as JSON files in a /styles/ directory. Block bindings (introduced in WordPress 6.5) let you pull dynamic data into block attributes without a custom block. Interactivity API blocks add client-side state without jQuery.

For deeper coverage of what is possible with theme.json once you have the migration done, the Brndle blog covers fluid typography, color systems, and spacing scales in detail. Start there once the structural migration is stable.


Ready to build your block theme from scratch?

Brndle publishes practical guides on block theme development, theme.json deep dives, and FSE patterns. Browse the full library to find guides matched to where you are in the migration process.

Scroll to Top