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.jsonreplaces scattered CSS custom properties andadd_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 Theme | Block 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() calls | Block patterns registered in /patterns/ directory |
| Custom page templates via PHP file + comment header | Template 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:
One line replaces get_header() in every template file
<!-- wp:template-part {"slug":"header","tagName":"header"} /-->
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.contentSizeandsettings.layout.wideSizeinstead of addingadd_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 Widget | Block Equivalent |
|---|---|
| Recent Posts | wp:latest-posts |
| Categories | wp:categories |
| Tag Cloud | wp:tag-cloud |
| Search | wp:search |
| Text / HTML Widget | wp:html or wp:paragraph |
| Image Widget | wp:image |
| Navigation Menu | wp:navigation |
| Custom HTML | wp:html |
| RSS Feed | wp:rss |
| Calendar | wp: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.csswith 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.jsonso 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.htmlrender correctly? - Visit a single page – does
page.htmlapply? - Visit the blog index – does
index.htmlorhome.htmlshow correctly? - Visit a category archive – does
archive.htmlapply? - Visit a tag archive – same
- Visit a search results page – does
search.htmlexist? - Trigger a 404 – does
404.htmlrender? - 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 File | Block Theme Equivalent | Notes |
|---|---|---|
index.php | templates/index.html | Required. WordPress falls back to this if no other template matches. |
single.php | templates/single.html | Single post template |
page.php | templates/page.html | Single page template |
archive.php | templates/archive.html | Archives (categories, tags, custom taxonomy) |
search.php | templates/search.html | Search results |
404.php | templates/404.html | Not found page |
front-page.php | templates/front-page.html | Homepage override |
header.php | parts/header.html | Referenced with wp:template-part |
footer.php | parts/footer.html | Referenced with wp:template-part |
sidebar.php | parts/sidebar.html or inline pattern | No direct equivalent – use template part or block pattern |
functions.php | theme.json + functions.php (slimmed) | Visual config moves to JSON, behavioral logic stays in PHP |
template-parts/*.php | patterns/*.php | Auto-registered in WP 6.0+ via file header comments |
style.css | style.css | Still 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.
