Views: 14
Learn how to make your WordPress block theme production ready with critical bug fixes, security hardening, and performance optimization. Complete guide to preparing Versana v1.0.0 for release.
Introduction
In previous episode 26, we have designed a professional landing page without any custom CSS code. In this episode of our WordPress block theme development series, we’re taking Versana from 85% complete to 100% WordPress block theme production ready. We’ll fix a critical options save bug, implement security hardening, streamline features for v1.0.0, and ensure our theme meets all WordPress.org submission requirements.
By the end of this tutorial, you’ll have a bulletproof, WordPress block theme production ready for public release.
What We’ll Cover
- Identifying production readiness issues
- Fixing the critical theme options save bug
- Implementing security best practices
- Streamlining features for v1.0.0 release
- Completing theme.json with core block styles
- Adding essential accessibility features
- Creating required WordPress.org files
- Testing for production deployment
Prerequisites
- Completed Episodes 1-26 of the Versana theme series
- WordPress 6.0+ installed
- Code editor (VS Code recommended)
- Local development environment
- Basic understanding of PHP and WordPress theme development
Part 1: Assessing Production Readiness
The Production Readiness Checklist
Before releasing any WordPress block theme production ready for public use, you need to ensure:
- ✅ No critical bugs
- ✅ Security measures implemented
- ✅ All required files present
- ✅ Code follows WordPress standards
- ✅ Performance optimized
- ✅ Accessibility compliant
- ✅ Tested on fresh WordPress install
Current Status Analysis
Our Versana theme scored 85% production ready with these critical issues:
Critical Issues Found:
- Theme options save bug (settings reset when saving different tabs)
- Missing security nonces on forms
- Missing required files (readme.txt, LICENSE, screenshot.png)
- Too many “coming soon” features
- Incomplete core block styles in theme.json
Let’s fix each one systematically.
Part 2: Fixing the Critical Options Save Bug
The Problem
When users saved settings in one tab of Theme Options, settings from other tabs would reset to defaults. This is a critical bug that makes the theme unusable.
Root Cause Analysis
The bug was in inc/theme-options/options-sanitize.php:
// WRONG - Creates empty array, losing all existing options
function versana_sanitize_options( $input ) {
$sanitized = array(); // ❌ Starts with empty array
// Only processes fields from current form submission
// All other tab settings get lost!
return $sanitized;
}
The Solution
To make our WordPress block theme production ready, we need to preserve existing options:
/**
* Sanitize all theme options - FIXED VERSION
*
* @param array $input Raw input from form
* @return array Sanitized options
*/
function versana_sanitize_options( $input ) {
// ✅ CRITICAL FIX: Start with existing options
$existing_options = get_option( 'versana_theme_options', array() );
$sanitized = $existing_options; // Preserve all existing values
// Verify nonce for security
if ( ! isset( $_POST['versana_options_nonce'] ) ||
! wp_verify_nonce( $_POST['versana_options_nonce'], 'versana_save_options' ) ) {
add_settings_error(
'versana_options',
'versana_nonce_error',
__( 'Security verification failed. Please try again.', 'versana' ),
'error'
);
return $existing_options;
}
// Check if reset was requested
if ( isset( $_POST['versana_reset_options'] ) ) {
add_settings_error(
'versana_options',
'versana_reset_success',
__( 'Theme options have been reset to defaults.', 'versana' ),
'updated'
);
return versana_get_default_options();
}
// Now update only the fields from current tab
// Boolean fields (checkboxes)
$boolean_fields = array(
'enable_sticky_header',
'enable_back_to_top',
'lazy_load_images',
'disable_emojis',
'disable_embeds',
'remove_query_strings',
);
// Determine which tab is being saved
$is_header_tab = isset( $input['header_layout'] );
$is_footer_tab = isset( $input['footer_copyright'] );
$is_performance_tab = isset( $input['lazy_load_images'] ) ||
isset( $_POST['_wp_http_referer'] ) &&
strpos( $_POST['_wp_http_referer'], 'tab=performance' ) !== false;
// Update only fields from the current tab
foreach ( $boolean_fields as $field ) {
if (
( $field === 'enable_sticky_header' && $is_header_tab ) ||
( $field === 'enable_back_to_top' && $is_footer_tab ) ||
( in_array( $field, ['lazy_load_images', 'disable_emojis', 'disable_embeds', 'remove_query_strings'] ) && $is_performance_tab )
) {
$sanitized[ $field ] = isset( $input[ $field ] ) ? (bool) $input[ $field ] : false;
}
}
// Select fields with allowed values
$select_fields = array(
'header_layout' => array( 'default', 'centered' ),
'blog_layout' => array( 'list', '2col', '3col' ),
'blog_sidebar_position' => array( 'left', 'right', 'none' ),
'archive_layout' => array( 'inherit', 'list', '2col', '3col' ),
);
foreach ( $select_fields as $field => $allowed_values ) {
if ( isset( $input[ $field ] ) ) {
$value = sanitize_text_field( $input[ $field ] );
$sanitized[ $field ] = in_array( $value, $allowed_values, true ) ?
$value :
( $existing_options[ $field ] ?? $allowed_values[0] );
}
}
// Text fields
$text_fields = array(
'footer_copyright',
'google_analytics_id',
'facebook_pixel_id',
'google_tag_manager_id',
);
foreach ( $text_fields as $field ) {
if ( isset( $input[ $field ] ) ) {
$sanitized[ $field ] = sanitize_text_field( $input[ $field ] );
}
}
// Custom CSS
if ( isset( $input['custom_css'] ) ) {
$sanitized['custom_css'] = wp_strip_all_tags( $input['custom_css'] );
}
// Scripts (only for administrators)
if ( current_user_can( 'unfiltered_html' ) ) {
if ( isset( $input['header_scripts'] ) ) {
$sanitized['header_scripts'] = $input['header_scripts'];
}
if ( isset( $input['footer_scripts'] ) ) {
$sanitized['footer_scripts'] = $input['footer_scripts'];
}
}
// Add success message
add_settings_error(
'versana_options',
'versana_save_success',
__( 'Settings saved successfully.', 'versana' ),
'updated'
);
return apply_filters( 'versana_sanitize_options', $sanitized, $input );
}
Testing the Fix
To verify your WordPress block theme production ready status:
- Go to Appearance → Theme Options
- Navigate to Header tab
- Enable “Sticky Header” → Click Save
- Navigate to Footer tab
- Disable “Back to Top” → Click Save
- Return to Header tab
- Verify: “Sticky Header” should still be enabled ✅
If settings persist across tabs, the bug is fixed!
Part 3: Security Hardening
Security is critical for any WordPress block theme production ready for public release.
Adding Nonce Verification
Update inc/theme-options/options-page.php to add nonce field:
<form method="post" action="options.php">
<?php
settings_fields( 'versana_options' );
// CRITICAL: Add nonce field for security
wp_nonce_field( 'versana_save_options', 'versana_options_nonce' );
// Render active tab content
// ...
submit_button();
?>
</form>
The nonce is automatically verified in our updated versana_sanitize_options() function.
Proper Capability Checks
Ensure only authorized users can access theme options:
function versana_render_options_page() {
// Verify user has proper permissions
if ( ! current_user_can( 'edit_theme_options' ) ) {
wp_die( __( 'You do not have sufficient permissions to access this page.', 'versana' ) );
}
// Rest of the function...
}
Escaping Output
All output must be properly escaped for a WordPress block theme production ready:
// Good - Properly escaped
echo esc_html( $option_value );
echo esc_attr( $attribute_value );
echo esc_url( $url_value );
// Bad - Not escaped
echo $option_value; // ❌ Security risk!
Part 4: Streamlining Features for v1.0.0
Removing “Pro” Features
For a clean v1.0.0 release, we’re removing these features (to be added in v2.0):
Removed Features:
- ❌ Breadcrumbs
- ❌ Dark mode toggle
- ❌ Header search
- ❌ Header CTA button
- ❌ Reading time
- ❌ Social share buttons
- ❌ Author bio box
- ❌ Related posts
- ❌ Table of contents
Why remove them?
- They were placeholder options with no functionality
- Better to launch with working features than promises
- Can be added properly in v2.0 with extensibility hooks
Simplified Default Options
Update inc/theme-options/options-defaults.php:
/**
* Get default theme options - V1.0.0 Simplified
*
* @return array Default options array
*/
function versana_get_default_options() {
$defaults = array(
// Header Tab
'header_layout' => 'default',
'enable_sticky_header' => false,
// Footer Tab
'enable_back_to_top' => true,
'footer_copyright' => '© {year} {site_name}. All rights reserved.',
// Blog Tab
'blog_layout' => 'list',
'blog_sidebar_position' => 'right',
'archive_layout' => 'inherit',
// Performance Tab
'lazy_load_images' => true,
'disable_emojis' => false,
'disable_embeds' => false,
'remove_query_strings' => false,
// Integrations Tab
'google_analytics_id' => '',
'facebook_pixel_id' => '',
'google_tag_manager_id' => '',
'header_scripts' => '',
'footer_scripts' => '',
// Advanced Tab
'custom_css' => '',
);
return apply_filters( 'versana_default_options', $defaults );
}
This focused feature set makes our WordPress block theme production ready with only working, tested features.
Part 5: Completing theme.json
Adding Core Block Styles
A truly WordPress block theme production ready must style all core WordPress blocks.
Update your theme.json to include:
{
"styles": {
"blocks": {
"core/paragraph": {
"spacing": {
"margin": {
"top": "0",
"bottom": "var(--wp--preset--spacing--sm)"
}
}
},
"core/list": {
"spacing": {
"margin": {
"top": "0",
"bottom": "var(--wp--preset--spacing--sm)"
},
"padding": {
"left": "var(--wp--preset--spacing--md)"
}
}
},
"core/quote": {
"border": {
"width": "0 0 0 4px",
"color": "var(--wp--preset--color--primary)",
"style": "solid"
},
"spacing": {
"padding": {
"left": "var(--wp--preset--spacing--md)"
},
"margin": {
"top": "var(--wp--preset--spacing--lg)",
"bottom": "var(--wp--preset--spacing--lg)"
}
},
"typography": {
"fontStyle": "italic"
}
},
"core/code": {
"color": {
"background": "var(--wp--preset--color--neutral-200)",
"text": "var(--wp--preset--color--neutral-900)"
},
"border": {
"radius": "4px"
},
"spacing": {
"padding": "var(--wp--preset--spacing--sm)"
},
"typography": {
"fontFamily": "var(--wp--preset--font-family--system-mono)",
"fontSize": "var(--wp--preset--font-size--sm)"
}
},
"core/preformatted": {
"color": {
"background": "var(--wp--preset--color--neutral-200)",
"text": "var(--wp--preset--color--neutral-900)"
},
"border": {
"radius": "4px"
},
"spacing": {
"padding": "var(--wp--preset--spacing--md)",
"margin": {
"top": "var(--wp--preset--spacing--md)",
"bottom": "var(--wp--preset--spacing--md)"
}
},
"typography": {
"fontFamily": "var(--wp--preset--font-family--system-mono)",
"fontSize": "var(--wp--preset--font-size--sm)"
}
},
"core/table": {
"spacing": {
"margin": {
"top": "var(--wp--preset--spacing--md)",
"bottom": "var(--wp--preset--spacing--md)"
}
}
},
"core/separator": {
"color": {
"text": "var(--wp--preset--color--neutral-300)"
},
"spacing": {
"margin": {
"top": "var(--wp--preset--spacing--lg)",
"bottom": "var(--wp--preset--spacing--lg)"
}
}
},
"core/image": {
"spacing": {
"margin": {
"top": "0",
"bottom": "var(--wp--preset--spacing--md)"
}
}
},
"core/gallery": {
"spacing": {
"margin": {
"top": "var(--wp--preset--spacing--md)",
"bottom": "var(--wp--preset--spacing--lg)"
}
}
}
}
}
}
Why This Matters
Complete block styling ensures:
- Consistent design across all content
- Better user experience in Site Editor
- Professional appearance out-of-the-box
- Easier color scheme changes
Part 6: Essential CSS for Production
While theme.json handles most styling, some elements need CSS for a WordPress block theme production ready.
Form Elements Styling
Add to style.css:
/**
* Form Elements - Not fully handled by theme.json
*/
input[type="text"],
input[type="email"],
input[type="url"],
input[type="tel"],
input[type="number"],
input[type="search"],
input[type="password"],
textarea,
select {
padding: 0.75rem 1rem;
border: 1px solid var(--wp--preset--color--neutral-300);
border-radius: 4px;
font-size: var(--wp--preset--font-size--base);
font-family: inherit;
line-height: 1.5;
background-color: var(--wp--preset--color--neutral-100);
color: var(--wp--preset--color--neutral-900);
transition: border-color 0.2s ease, box-shadow 0.2s ease;
max-width: 100%;
width: 100%;
}
input[type="text"]:focus,
input[type="email"]:focus,
input[type="url"]:focus,
input[type="tel"]:focus,
input[type="number"]:focus,
input[type="search"]:focus,
input[type="password"]:focus,
textarea:focus,
select:focus {
outline: none;
border-color: var(--wp--preset--color--primary);
box-shadow: 0 0 0 3px rgba(26, 115, 232, 0.1);
}
textarea {
min-height: 120px;
resize: vertical;
}
select {
cursor: pointer;
appearance: none;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8'%3E%3Cpath fill='%23424242' d='M6 8L0 0h12z'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: right 1rem center;
background-size: 12px;
padding-right: 2.5rem;
}
Accessibility Enhancements
/**
* Screen Reader Text
*/
.screen-reader-text {
clip: rect(1px, 1px, 1px, 1px);
clip-path: inset(50%);
position: absolute !important;
height: 1px;
width: 1px;
overflow: hidden;
word-wrap: normal !important;
}
.screen-reader-text:focus {
background-color: var(--wp--preset--color--neutral-100);
border-radius: 3px;
box-shadow: 0 0 2px 2px rgba(0, 0, 0, 0.6);
clip: auto !important;
clip-path: none;
color: var(--wp--preset--color--primary);
display: block;
font-size: var(--wp--preset--font-size--sm);
font-weight: 600;
height: auto;
left: 5px;
line-height: normal;
padding: 15px 23px 14px;
text-decoration: none;
top: 5px;
width: auto;
z-index: 100000;
}
/**
* Focus Visible Styles
*/
:focus-visible {
outline: 2px solid var(--wp--preset--color--primary);
outline-offset: 2px;
}
/**
* Skip to Content Link
*/
.skip-link {
position: absolute;
top: -40px;
left: 0;
background: var(--wp--preset--color--primary);
color: var(--wp--preset--color--neutral-100);
padding: var(--wp--preset--spacing--sm) var(--wp--preset--spacing--md);
text-decoration: none;
z-index: 10000;
font-weight: 600;
border-radius: 0 0 4px 0;
}
.skip-link:focus {
top: 0;
}
/**
* Reduced Motion Support
*/
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}
Part 7: Creating Required Files
readme.txt
Create readme.txt in theme root:
=== Versana ===
Contributors: codoplex
Requires at least: 6.0
Tested up to: 6.5
Requires PHP: 7.4
Stable tag: 1.0.0
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html
Tags: blog, full-site-editing, block-patterns, block-styles, custom-colors, translation-ready, accessibility-ready
A modern, performance-focused WordPress block theme optimized for bloggers.
== Description ==
Versana is a lightweight, versatile full site editing (FSE) block theme designed specifically for bloggers and content creators who value performance, flexibility, and professional design.
= Key Features =
* Full Site Editing (FSE) support
* Multiple blog layouts (list, 2-column, 3-column)
* Flexible sidebar positions
* Sticky header option
* Performance optimized
* Analytics integration
* Translation ready
* Accessibility ready
== Installation ==
1. Upload the theme files to `/wp-content/themes/versana`
2. Activate the theme through WordPress admin
3. Go to Appearance → Theme Options to configure
4. Use the Site Editor to customize templates and styles
== Changelog ==
= 1.0.0 =
* Initial release
* Full Site Editing support
* Multiple blog layouts
* Performance optimizations
* Analytics integration
LICENSE File
Create LICENSE (no extension):
Versana WordPress Theme, (C) 2024 CODOPLEX
Versana is distributed under the terms of the GNU GPL v2 or later.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
screenshot.png
Requirements for WordPress block theme production ready:
- Size: 1200 x 900 pixels
- Format: PNG
- Content: Homepage showing theme in action
- Quality: High resolution, professional appearance
How to create:
- Set up demo site with sample content
- Take screenshot of homepage
- Crop/resize to 1200x900px
- Save as
screenshot.pngin theme root
Part 8: Testing for Production
Testing Checklist
Functionality Tests
- [ ] Theme options save correctly across all tabs
- [ ] All blog layouts work (list, 2col, 3col)
- [ ] Sidebar positions work (left, right, none)
- [ ] Sticky header functions properly
- [ ] Back to top button appears and works
- [ ] All footer templates display correctly
- [ ] Analytics codes insert properly
Design Tests
- [ ] Responsive on all devices (desktop, tablet, mobile)
- [ ] All core blocks styled consistently
- [ ] Forms look professional
- [ ] Colors cascade properly from theme.json
- [ ] Typography scales correctly
Technical Tests
- [ ] No PHP errors (enable WP_DEBUG)
- [ ] No JavaScript console errors
- [ ] Theme Check plugin passes
- [ ] HTML validates
- [ ] Performance score 90+ on PageSpeed Insights
Accessibility Tests
- [ ] Keyboard navigation works
- [ ] Focus indicators visible
- [ ] Screen reader text present
- [ ] Color contrast meets WCAG AA
- [ ] Skip links functional
Running Theme Check
Install and run the Theme Check plugin:
# Via WordPress admin:
# Plugins → Add New → Search "Theme Check"
# Install and activate
# Go to Appearance → Theme Check
# Click "Check it!"
Fix any REQUIRED errors before considering your WordPress block theme production ready.
Part 9: Performance Optimization
Lazy Loading Images
Enable in Theme Options:
// In options-output.php
if ( versana_get_option( 'lazy_load_images' ) ) {
add_filter( 'wp_lazy_loading_enabled', '__return_true' );
}
Removing Unnecessary Scripts
// Disable emojis if option enabled
function versana_disable_emojis() {
if ( ! versana_get_option( 'disable_emojis' ) ) {
return;
}
remove_action( 'wp_head', 'print_emoji_detection_script', 7 );
remove_action( 'admin_print_scripts', 'print_emoji_detection_script' );
remove_action( 'wp_print_styles', 'print_emoji_styles' );
remove_action( 'admin_print_styles', 'print_emoji_styles' );
remove_filter( 'the_content_feed', 'wp_staticize_emoji' );
remove_filter( 'comment_text_rss', 'wp_staticize_emoji' );
remove_filter( 'wp_mail', 'wp_staticize_emoji_for_email' );
}
add_action( 'init', 'versana_disable_emojis' );
// Disable embeds if option enabled
function versana_disable_embeds() {
if ( ! versana_get_option( 'disable_embeds' ) ) {
return;
}
global $wp;
$wp->public_query_vars = array_diff( $wp->public_query_vars, array( 'embed' ) );
remove_action( 'rest_api_init', 'wp_oembed_register_route' );
remove_filter( 'oembed_dataparse', 'wp_filter_oembed_result', 10 );
remove_action( 'wp_head', 'wp_oembed_add_discovery_links' );
remove_action( 'wp_head', 'wp_oembed_add_host_js' );
}
add_action( 'init', 'versana_disable_embeds', 9999 );
Conditional Asset Loading
Ensure assets only load when needed:
function versana_enqueue_dynamic_assets() {
// Load header CSS/JS only if needed
$sticky_enabled = versana_get_option( 'enable_sticky_header' );
$header_layout = versana_get_option( 'header_layout', 'default' );
if ( $sticky_enabled || $header_layout !== 'default' ) {
wp_enqueue_style( 'versana-header',
get_template_directory_uri() . '/assets/css/header.css',
array(),
wp_get_theme()->get( 'Version' )
);
}
if ( $sticky_enabled ) {
wp_enqueue_script( 'versana-header',
get_template_directory_uri() . '/assets/js/header.js',
array(),
wp_get_theme()->get( 'Version' ),
true
);
}
}
add_action( 'wp_enqueue_scripts', 'versana_enqueue_dynamic_assets' );
Part 10: Final Production Checklist
Pre-Release Verification
✅ File Structure
├── assets/ (CSS & JS properly organized)
├── inc/ (PHP includes properly namespaced)
├── parts/ (Template parts complete)
├── patterns/ (Block patterns functional)
├── templates/ (All required templates present)
├── functions.php (Clean, well-documented)
├── style.css (Proper header, minimal CSS)
├── theme.json (Complete block styles)
├── readme.txt (Detailed, accurate)
├── LICENSE (GPL v2 or later)
└── screenshot.png (1200x900px)
✅ Code Quality
- All functions prefixed with versana_
- All text strings use 'versana' text domain
- All output properly escaped
- All input properly sanitized
- No PHP warnings or notices
- No JavaScript console errors
✅ Testing Complete
- Fresh WordPress install tested
- All features tested
- Multiple browsers tested
- Mobile responsiveness verified
- Accessibility tested
- Performance optimized
✅ Documentation
- readme.txt complete
- Code comments present
- Changelog updated
- Installation instructions clear
Conclusion
Congratulations! You now have a fully WordPress block theme production ready for release. We’ve:
- ✅ Fixed the critical options save bug
- ✅ Implemented security hardening with nonces
- ✅ Streamlined features for clean v1.0.0 release
- ✅ Completed theme.json with all core block styles
- ✅ Added essential CSS for forms and accessibility
- ✅ Created all required WordPress.org files
- ✅ Optimized performance
- ✅ Tested thoroughly
What’s Next?
Immediate:
- Submit to WordPress.org theme directory
- Monitor initial user feedback
- Fix any reported bugs promptly
Version 1.1.0 (2-3 months):
- Minor improvements based on feedback
- Additional block patterns
- Bug fixes
Version 2.0.0 (6 months):
- Add back “pro” features with proper hooks
- Dark mode toggle
- Breadcrumbs system
- Social share buttons
- Reading time calculator
- WooCommerce integration
Key Takeaways
Making a WordPress block theme production ready requires:
- Bug-free core functionality – No critical bugs
- Security first – Nonces, sanitization, escaping
- Feature focus – Working features over promises
- Complete styling – All blocks styled consistently
- Accessibility – Keyboard nav, screen readers, contrast
- Performance – Minimal CSS, lazy loading, conditional assets
- Documentation – Clear readme, proper licensing
- Thorough testing – Multiple scenarios, devices, browsers
Resources
- WordPress Theme Handbook: https://developer.wordpress.org/themes/
- Block Editor Handbook: https://developer.wordpress.org/block-editor/
- Theme Review Guidelines: https://make.wordpress.org/themes/handbook/review/
- Accessibility Handbook: https://make.wordpress.org/accessibility/handbook/
Download Episode Files
All files from this episode are available in the github repository:
- Fixed PHP files
- Enhanced theme.json
- Complete style.css
- readme.txt template
- Testing checklist
Frequently Asked Questions
Q: How long does WordPress.org theme review take? A: Typically 2-4 weeks for first submission, faster for updates.
Q: Can I add pro features later? A: Yes! Version 2.0 will add advanced features with proper extensibility.
Q: Is the theme mobile-responsive? A: Yes, all layouts are fully responsive and tested on multiple devices.
Q: Does this work with page builders? A: It’s designed for the WordPress block editor (Gutenberg). Page builders may conflict.
Q: Can I translate the theme? A: Yes! It’s fully translation-ready with proper text domain usage.
Next Episode: We’ll cover WordPress.org submission process and handling theme reviews.
This is Episode 27 of our WordPress Block Theme Development series. Building Versana, a modern, performance-focused block theme from scratch.
Series Navigation:
- Previous: Episode 26 – WordPress Block Theme Landing Page Design
- Next: Episode 28 – WordPress.org Submission
Leave a Reply