A Step-by-Step Guide to Converting a Classic Theme to a Modern Block Theme
The transition from classic WordPress themes to block themes represents one of the most significant shifts in WordPress development. If you're maintaining a classic theme, you might wonder whether—and how—to make the jump to Full Site Editing (FSE).
This comprehensive guide walks you through the entire conversion process, from initial assessment to final deployment. We'll cover every file transformation, provide real code examples, and share a complete case study of converting a popular classic theme to a block theme.
Table of Contents
- Pre-conversion Checklist
- File Structure Transformation
- Converting PHP Templates to HTML
- Migrating Customizer Settings to theme.json
- Testing and Debugging Process
- Case Study with Real Theme
Pre-conversion Checklist
Assessing Your Classic Theme
Before starting the conversion, thoroughly evaluate your existing theme to understand the scope of work required.
Theme Inventory Checklist
Template Files: - [ ] List all PHP template files - [ ] Document template hierarchy usage - [ ] Identify custom page templates - [ ] Note conditional logic in templates - [ ] Map get_template_part() usage
Functionality Assessment: - [ ] Custom post types and taxonomies - [ ] Widget areas and their purposes - [ ] Shortcodes in use - [ ] Custom functions in functions.php - [ ] Third-party integrations
Styling Architecture: - [ ] CSS file organization - [ ] Use of CSS preprocessors (SASS/LESS) - [ ] JavaScript files and dependencies - [ ] Font loading methods - [ ] Image and media handling
Customizer Settings: - [ ] All Customizer sections - [ ] Color options - [ ] Typography settings - [ ] Layout options - [ ] Custom controls
Migration Readiness Assessment
Compatibility Check
// Add to your classic theme to test block editor compatibility
function test_block_editor_compatibility() {
// Check PHP version
if (version_compare(PHP_VERSION, '7.0', '<')) {
echo 'Warning: PHP 7.0+ recommended for block themes';
}
// Check WordPress version
global $wp_version;
if (version_compare($wp_version, '5.9', '<')) {
echo 'Warning: WordPress 5.9+ required for block themes';
}
// Check for problematic plugins
$incompatible_plugins = array(
'classic-editor/classic-editor.php',
'disable-gutenberg/disable-gutenberg.php'
);
foreach ($incompatible_plugins as $plugin) {
if (is_plugin_active($plugin)) {
echo "Warning: $plugin may conflict with block themes";
}
}
}
Feature Mapping
Classic Theme Feature | Block Theme Equivalent |
---|---|
header.php | parts/header.html |
footer.php | parts/footer.html |
sidebar.php | Template parts or patterns |
page.php | templates/page.html |
single.php | templates/single.html |
functions.php | functions.php + theme.json |
customizer.php | theme.json |
widgets | Block patterns |
menus | Navigation block |
Decision Matrix
Should You Convert?
Consider conversion if: - ✅ You want to leverage FSE features - ✅ Your theme needs modernization - ✅ You're comfortable with block development - ✅ Your users are ready for change
Postpone conversion if: - ❌ Heavy reliance on complex PHP logic - ❌ Extensive third-party integrations - ❌ Limited development resources - ❌ Users require classic editor
File Structure Transformation
Classic Theme Structure
classic-theme/
├── assets/
│ ├── css/
│ │ ├── style.css
│ │ └── editor-style.css
│ ├── js/
│ │ ├── navigation.js
│ │ └── customizer.js
│ └── images/
├── inc/
│ ├── customizer.php
│ ├── template-functions.php
│ └── template-tags.php
├── template-parts/
│ ├── content.php
│ ├── content-single.php
│ └── content-page.php
├── header.php
├── footer.php
├── sidebar.php
├── index.php
├── single.php
├── page.php
├── archive.php
├── search.php
├── 404.php
├── functions.php
└── style.css
Block Theme Structure
block-theme/
├── assets/
│ ├── fonts/
│ ├── images/
│ └── js/
├── parts/
│ ├── header.html
│ ├── footer.html
│ ├── post-meta.html
│ └── comments.html
├── patterns/
│ ├── hero-section.php
│ ├── call-to-action.php
│ └── featured-posts.php
├── styles/
│ ├── dark.json
│ └── minimal.json
├── templates/
│ ├── index.html
│ ├── single.html
│ ├── page.html
│ ├── archive.html
│ ├── search.html
│ └── 404.html
├── functions.php
├── style.css
└── theme.json
Migration Steps
Step 1: Create Base Structure
# Create new directories
mkdir -p parts templates patterns styles
# Move assets
mv template-parts parts-backup
mv assets assets-backup
mkdir -p assets/fonts assets/images assets/js
Step 2: Create theme.json
Start with a minimal theme.json:
{
"$schema": "https://schemas.wp.org/trunk/theme.json",
"version": 2,
"settings": {
"appearanceTools": true,
"layout": {
"contentSize": "840px",
"wideSize": "1100px"
}
}
}
Step 3: Update style.css
Modify your style.css header:
/*
Theme Name: My Block Theme
Theme URI: https://example.com
Author: Your Name
Author URI: https://example.com
Description: Converted from classic to block theme
Requires at least: 5.9
Tested up to: 6.3
Requires PHP: 7.0
Version: 2.0
License: GPL v2 or later
Text Domain: my-block-theme
[Previous theme CSS below...]
*/
Converting PHP Templates to HTML
Template Conversion Process
Converting header.php to parts/header.html
Classic 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 id="masthead" class="site-header">
<div class="container">
<div class="site-branding">
<?php if ( has_custom_logo() ) : ?>
<?php the_custom_logo(); ?>
<?php else : ?>
<h1 class="site-title">
<a href="<?php echo esc_url( home_url( '/' ) ); ?>">
<?php bloginfo( 'name' ); ?>
</a>
</h1>
<p class="site-description"><?php bloginfo( 'description' ); ?></p>
<?php endif; ?>
</div>
<nav id="site-navigation" class="main-navigation">
<?php
wp_nav_menu( array(
'theme_location' => 'primary',
'menu_id' => 'primary-menu',
) );
?>
</nav>
</div>
</header>
Block theme parts/header.html:
<!-- wp:group {"tagName":"header","style":{"spacing":{"padding":{"top":"2rem","bottom":"2rem"}}},"className":"site-header"} -->
<header class="wp-block-group site-header" style="padding-top:2rem;padding-bottom:2rem">
<!-- wp:group {"layout":{"type":"constrained"}} -->
<div class="wp-block-group">
<!-- wp:group {"layout":{"type":"flex","justifyContent":"space-between","flexWrap":"wrap"}} -->
<div class="wp-block-group">
<!-- wp:group {"className":"site-branding"} -->
<div class="wp-block-group site-branding">
<!-- wp:site-logo {"width":150} /-->
<!-- wp:group -->
<div class="wp-block-group">
<!-- wp:site-title {"level":1} /-->
<!-- wp:site-tagline /-->
</div>
<!-- /wp:group -->
</div>
<!-- /wp:group -->
<!-- wp:navigation {"layout":{"type":"flex","justifyContent":"right"}} /-->
</div>
<!-- /wp:group -->
</div>
<!-- /wp:group -->
</header>
<!-- /wp:group -->
Converting single.php to templates/single.html
Classic single.php:
<?php
get_header();
while ( have_posts() ) :
the_post();
?>
<article id="post-<?php the_ID(); ?>" <?php post_class(); ?>>
<header class="entry-header">
<?php the_title( '<h1 class="entry-title">', '</h1>' ); ?>
<div class="entry-meta">
<?php
printf(
'Posted on %s by %s',
get_the_date(),
get_the_author()
);
?>
</div>
</header>
<?php if ( has_post_thumbnail() ) : ?>
<div class="post-thumbnail">
<?php the_post_thumbnail( 'large' ); ?>
</div>
<?php endif; ?>
<div class="entry-content">
<?php the_content(); ?>
</div>
<footer class="entry-footer">
<?php the_category( ', ' ); ?>
<?php the_tags( 'Tags: ', ', ' ); ?>
</footer>
</article>
<?php
if ( comments_open() || get_comments_number() ) :
comments_template();
endif;
endwhile;
get_footer();
Block theme templates/single.html:
<!-- wp:template-part {"slug":"header","tagName":"header"} /-->
<!-- wp:group {"tagName":"main","style":{"spacing":{"margin":{"top":"4rem","bottom":"4rem"}}}} -->
<main class="wp-block-group" style="margin-top:4rem;margin-bottom:4rem">
<!-- wp:group {"layout":{"type":"constrained"}} -->
<div class="wp-block-group">
<!-- wp:post-title {"level":1} /-->
<!-- wp:group {"className":"entry-meta","layout":{"type":"flex"}} -->
<div class="wp-block-group entry-meta">
<!-- wp:post-date /-->
<!-- wp:post-author {"showAvatar":false,"byline":"by"} /-->
</div>
<!-- /wp:group -->
<!-- wp:post-featured-image {"align":"wide"} /-->
<!-- wp:post-content {"layout":{"type":"constrained"}} /-->
<!-- wp:group {"className":"entry-footer","layout":{"type":"flex"}} -->
<div class="wp-block-group entry-footer">
<!-- wp:post-terms {"term":"category"} /-->
<!-- wp:post-terms {"term":"post_tag"} /-->
</div>
<!-- /wp:group -->
<!-- wp:template-part {"slug":"comments"} /-->
</div>
<!-- /wp:group -->
</main>
<!-- /wp:group -->
<!-- wp:template-part {"slug":"footer","tagName":"footer"} /-->
Complex Template Conversions
Converting Conditional Logic
Classic PHP with conditions:
<?php if ( is_home() && ! is_front_page() ) : ?>
<header>
<h1 class="page-title"><?php single_post_title(); ?></h1>
</header>
<?php endif; ?>
<?php if ( have_posts() ) : ?>
<?php while ( have_posts() ) : the_post(); ?>
<?php get_template_part( 'template-parts/content', get_post_type() ); ?>
<?php endwhile; ?>
<?php the_posts_navigation(); ?>
<?php else : ?>
<?php get_template_part( 'template-parts/content', 'none' ); ?>
<?php endif; ?>
Block theme approach:
<!-- wp:query {"queryId":0,"query":{"inherit":true},"displayLayout":{"type":"list"}} -->
<div class="wp-block-query">
<!-- wp:query-title {"type":"archive"} /-->
<!-- wp:post-template -->
<!-- wp:post-title {"isLink":true} /-->
<!-- wp:post-excerpt /-->
<!-- wp:post-date /-->
<!-- /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 -->
Migrating Customizer Settings to theme.json
Mapping Customizer Options
Color Settings Migration
Classic Customizer:
$wp_customize->add_setting( 'primary_color', array(
'default' => '#0073aa',
'sanitize_callback' => 'sanitize_hex_color',
) );
$wp_customize->add_control( new WP_Customize_Color_Control(
$wp_customize,
'primary_color',
array(
'label' => __( 'Primary Color', 'textdomain' ),
'section' => 'colors',
'settings' => 'primary_color',
)
) );
theme.json equivalent:
{
"settings": {
"color": {
"palette": [
{
"slug": "primary",
"color": "#0073aa",
"name": "Primary"
}
]
}
}
}
Typography Migration
Classic Customizer:
$wp_customize->add_setting( 'body_font_size', array(
'default' => '16px',
'sanitize_callback' => 'sanitize_text_field',
) );
$wp_customize->add_control( 'body_font_size', array(
'type' => 'select',
'section' => 'typography',
'label' => __( 'Body Font Size' ),
'choices' => array(
'14px' => __( 'Small' ),
'16px' => __( 'Normal' ),
'18px' => __( 'Large' ),
),
) );
theme.json equivalent:
{
"settings": {
"typography": {
"fontSizes": [
{
"slug": "small",
"size": "14px",
"name": "Small"
},
{
"slug": "normal",
"size": "16px",
"name": "Normal"
},
{
"slug": "large",
"size": "18px",
"name": "Large"
}
]
}
},
"styles": {
"typography": {
"fontSize": "var(--wp--preset--font-size--normal)"
}
}
}
Creating Migration Helper
/**
* Migrate Customizer settings to theme.json
*/
function migrate_customizer_to_theme_json() {
$theme_json_path = get_template_directory() . '/theme.json';
$theme_json = json_decode( file_get_contents( $theme_json_path ), true );
// Migrate colors
$primary_color = get_theme_mod( 'primary_color', '#0073aa' );
$secondary_color = get_theme_mod( 'secondary_color', '#005177' );
$theme_json['settings']['color']['palette'] = array(
array(
'slug' => 'primary',
'color' => $primary_color,
'name' => 'Primary'
),
array(
'slug' => 'secondary',
'color' => $secondary_color,
'name' => 'Secondary'
)
);
// Migrate typography
$font_size = get_theme_mod( 'body_font_size', '16px' );
$theme_json['styles']['typography']['fontSize'] = $font_size;
// Save updated theme.json
file_put_contents(
$theme_json_path,
json_encode( $theme_json, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES )
);
}
// Run migration on theme switch
add_action( 'after_switch_theme', 'migrate_customizer_to_theme_json' );
Testing and Debugging Process
Testing Checklist
Visual Testing
- [ ] Compare classic vs block theme appearance
- [ ] Test all templates with sample content
- [ ] Verify responsive behavior
- [ ] Check print styles
- [ ] Test in major browsers
Functionality Testing
- [ ] Navigation menus work correctly
- [ ] Search functionality
- [ ] Archive pages display properly
- [ ] Comments system functions
- [ ] Forms submit correctly
Performance Testing
- [ ] Page load speed comparison
- [ ] JavaScript execution time
- [ ] CSS file size optimization
- [ ] Image loading behavior
- [ ] Core Web Vitals scores
Common Issues and Solutions
Issue 1: Styles Not Applied
Problem: Custom CSS not loading Solution:
{
"styles": {
"css": "body { font-family: var(--wp--preset--font-family--system); }"
}
}
Issue 2: Missing Dynamic Features
Problem: PHP-based features don't work Solution: Create custom blocks or use render callbacks
register_block_type( 'mytheme/dynamic-content', array(
'render_callback' => 'mytheme_render_dynamic_content',
) );
function mytheme_render_dynamic_content( $attributes ) {
// Your PHP logic here
return $output;
}
Issue 3: Navigation Menu Issues
Problem: Menus not displaying correctly Solution: Ensure proper navigation block setup
<!-- wp:navigation {"ref":123,"layout":{"type":"flex","justifyContent":"right"}} /-->
Debugging Tools
/**
* Block Theme Debugging Helper
*/
class Block_Theme_Debug {
public static function init() {
if ( WP_DEBUG ) {
add_action( 'wp_footer', array( __CLASS__, 'output_debug_info' ) );
}
}
public static function output_debug_info() {
?>
<div style="background: #f0f0f0; padding: 20px; margin: 20px;">
<h3>Block Theme Debug Info</h3>
<p>Template: <?php echo get_page_template_slug(); ?></p>
<p>Block Count: <?php echo count( parse_blocks( get_the_content() ) ); ?></p>
<p>Theme Supports: <?php print_r( get_theme_support( 'editor-styles' ) ); ?></p>
</div>
<?php
}
}
Block_Theme_Debug::init();
Case Study with Real Theme
Converting "BusinessPro" Classic Theme
Let's walk through converting a real classic business theme to a block theme.
Original Theme Overview
BusinessPro Classic Theme: - 15 PHP template files - Custom page builder integration - 50+ Customizer options - 3 widget areas - 5 custom page templates
Phase 1: Planning (Week 1)
Inventory Results:
Templates to Convert:
- index.php → templates/index.html
- page.php → templates/page.html
- single.php → templates/single.html
- archive.php → templates/archive.html
- search.php → templates/search.html
- 404.php → templates/404.html
- page-templates/full-width.php → templates/page-full-width.html
- page-templates/landing.php → templates/page-landing.html
Features to Migrate:
- Hero section → Block pattern
- Services grid → Query loop pattern
- Testimonials → Block pattern
- Team members → Block pattern
- Contact form → Keep plugin-based
Phase 2: Structure Setup (Week 2)
Created File Structure:
businesspro-block/
├── assets/
│ └── images/
├── parts/
│ ├── header.html
│ ├── header-landing.html
│ ├── footer.html
│ └── footer-minimal.html
├── patterns/
│ ├── hero-business.php
│ ├── services-grid.php
│ ├── testimonials-carousel.php
│ └── team-members.php
├── styles/
│ ├── corporate.json
│ └── modern.json
├── templates/
│ ├── index.html
│ ├── single.html
│ ├── page.html
│ ├── page-full-width.html
│ └── page-landing.html
├── functions.php
├── style.css
└── theme.json
Phase 3: theme.json Creation (Week 2)
Comprehensive theme.json:
{
"$schema": "https://schemas.wp.org/trunk/theme.json",
"version": 2,
"settings": {
"appearanceTools": true,
"color": {
"custom": true,
"palette": [
{
"slug": "primary",
"color": "#1e40af",
"name": "Primary Blue"
},
{
"slug": "secondary",
"color": "#f59e0b",
"name": "Secondary Orange"
},
{
"slug": "accent",
"color": "#10b981",
"name": "Accent Green"
},
{
"slug": "dark",
"color": "#1f2937",
"name": "Dark Gray"
},
{
"slug": "light",
"color": "#f3f4f6",
"name": "Light Gray"
}
],
"gradients": [
{
"slug": "primary-gradient",
"gradient": "linear-gradient(135deg, var(--wp--preset--color--primary) 0%, var(--wp--preset--color--secondary) 100%)",
"name": "Primary Gradient"
}
]
},
"typography": {
"fontFamilies": [
{
"fontFamily": "'Inter', sans-serif",
"slug": "inter",
"name": "Inter",
"fontFace": [
{
"fontFamily": "Inter",
"fontWeight": "400 700",
"fontStyle": "normal",
"src": ["file:./assets/fonts/inter-variable.woff2"]
}
]
},
{
"fontFamily": "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif",
"slug": "system",
"name": "System Font"
}
],
"fontSizes": [
{
"slug": "small",
"size": "clamp(0.875rem, 2vw, 1rem)",
"name": "Small"
},
{
"slug": "medium",
"size": "clamp(1.125rem, 2.5vw, 1.25rem)",
"name": "Medium"
},
{
"slug": "large",
"size": "clamp(1.5rem, 3vw, 2rem)",
"name": "Large"
},
{
"slug": "x-large",
"size": "clamp(2rem, 4vw, 3rem)",
"name": "Extra Large"
},
{
"slug": "huge",
"size": "clamp(2.5rem, 5vw, 4rem)",
"name": "Huge"
}
]
},
"spacing": {
"units": ["px", "em", "rem", "%", "vw"],
"padding": true,
"margin": true,
"blockGap": true
},
"layout": {
"contentSize": "1200px",
"wideSize": "1400px"
}
},
"styles": {
"color": {
"background": "var(--wp--preset--color--light)",
"text": "var(--wp--preset--color--dark)"
},
"typography": {
"fontFamily": "var(--wp--preset--font-family--inter)",
"fontSize": "var(--wp--preset--font-size--medium)",
"lineHeight": "1.6"
},
"spacing": {
"blockGap": "2rem",
"padding": {
"top": "0",
"right": "2rem",
"bottom": "0",
"left": "2rem"
}
},
"elements": {
"link": {
"color": {
"text": "var(--wp--preset--color--primary)"
},
":hover": {
"color": {
"text": "var(--wp--preset--color--secondary)"
}
}
},
"button": {
"border": {
"radius": "6px"
},
"color": {
"background": "var(--wp--preset--color--primary)",
"text": "#ffffff"
},
":hover": {
"color": {
"background": "var(--wp--preset--color--secondary)"
}
}
}
},
"blocks": {
"core/navigation": {
"typography": {
"fontSize": "var(--wp--preset--font-size--small)"
}
},
"core/post-title": {
"typography": {
"fontWeight": "700"
}
}
}
}
}
Phase 4: Pattern Creation (Week 3)
Hero Pattern (patterns/hero-business.php):
<?php
/**
* Title: Business Hero Section
* Slug: businesspro/hero-business
* Categories: featured, header
* Keywords: hero, banner, cta
* Viewport Width: 1400
*/
?>
<!-- wp:cover {"url":"<?php echo esc_url( get_template_directory_uri() ); ?>/assets/images/hero-bg.jpg","dimRatio":60,"overlayColor":"dark","align":"full","style":{"spacing":{"padding":{"top":"8rem","bottom":"8rem"}}}} -->
<div class="wp-block-cover alignfull" style="padding-top:8rem;padding-bottom:8rem">
<span aria-hidden="true" class="wp-block-cover__background has-dark-background-color has-background-dim-60 has-background-dim"></span>
<img class="wp-block-cover__image-background" alt="" src="<?php echo esc_url( get_template_directory_uri() ); ?>/assets/images/hero-bg.jpg"/>
<div class="wp-block-cover__inner-container">
<!-- wp:group {"layout":{"type":"constrained"}} -->
<div class="wp-block-group">
<!-- wp:heading {"textAlign":"center","level":1,"style":{"typography":{"fontSize":"clamp(2.5rem, 5vw, 4rem)"}},"textColor":"white"} -->
<h1 class="has-text-align-center has-white-color has-text-color" style="font-size:clamp(2.5rem, 5vw, 4rem)">Grow Your Business with Confidence</h1>
<!-- /wp:heading -->
<!-- wp:paragraph {"align":"center","style":{"typography":{"fontSize":"1.25rem"}},"textColor":"white"} -->
<p class="has-text-align-center has-white-color has-text-color" style="font-size:1.25rem">Professional solutions for modern businesses. Start your journey today.</p>
<!-- /wp:paragraph -->
<!-- wp:buttons {"layout":{"type":"flex","justifyContent":"center"}} -->
<div class="wp-block-buttons">
<!-- wp:button {"style":{"spacing":{"padding":{"top":"1rem","bottom":"1rem","left":"2rem","right":"2rem"}}}} -->
<div class="wp-block-button"><a class="wp-block-button__link wp-element-button" style="padding-top:1rem;padding-right:2rem;padding-bottom:1rem;padding-left:2rem">Get Started</a></div>
<!-- /wp:button -->
<!-- wp:button {"className":"is-style-outline","style":{"spacing":{"padding":{"top":"1rem","bottom":"1rem","left":"2rem","right":"2rem"}}}} -->
<div class="wp-block-button is-style-outline"><a class="wp-block-button__link wp-element-button" style="padding-top:1rem;padding-right:2rem;padding-bottom:1rem;padding-left:2rem">Learn More</a></div>
<!-- /wp:button -->
</div>
<!-- /wp:buttons -->
</div>
<!-- /wp:group -->
</div>
</div>
<!-- /wp:cover -->
Phase 5: Testing and Launch (Week 4)
Testing Results: - ✅ All pages render correctly - ✅ Performance improved by 35% - ✅ Mobile experience enhanced - ✅ Accessibility score increased - ✅ Client approval received
Migration Statistics: - Development time: 4 weeks - File size reduction: 40% - Page load improvement: 1.2s faster - Customization options: Increased via FSE - Maintenance complexity: Reduced
Lessons Learned
- Start Simple: Convert basic templates first
- Pattern Everything: Create patterns for repeated sections
- Test Continuously: Check each conversion immediately
- Document Changes: Keep migration notes for clients
- Gradual Rollout: Consider staged deployment
Conclusion
Converting a classic theme to a block theme is a significant undertaking, but the benefits—improved performance, better user experience, and future-proofing—make it worthwhile. By following this systematic approach, you can successfully migrate any classic theme to the modern block-based architecture.
Key Takeaways: - Thorough planning prevents major issues - theme.json centralizes configuration beautifully - Patterns replace many PHP template parts - Testing is crucial at every stage - The learning curve pays off quickly
Ready to start your conversion? Begin with a simple theme to build confidence, then tackle more complex projects as you master the block theme architecture.
For more insights into modern WordPress development, explore our comprehensive Ultimate Guide to Gutenberg and Full-Site Editing.