Step-by-step guide to building a custom WordPress theme — PHP templates, theme.json, block patterns, and best practices for 2025.

Abdur Razzak
Full-Stack Web Developer
WordPress now offers two theme paradigms. Classic themes use PHP template files (header.php, page.php, single.php) and enqueue stylesheets with functions.php. Block themes (Full Site Editing) use HTML template files with block markup and a theme.json file for global design settings. For new projects in 2025, I recommend block themes — they give clients more editing flexibility and are where WordPress development is heading.
Create a folder in wp-content/themes/your-theme-name/. A minimal theme needs: style.css (with the Theme Name header comment), index.php or templates/index.html for block themes, and functions.php. For a classic theme, add header.php, footer.php, page.php, single.php, and archive.php for the main template hierarchy. Register your theme in style.css with the proper comment block so WordPress recognizes it.
The theme.json file is the heart of a block theme. It defines your color palette, font sizes, spacing scale, and layout constraints. These settings are available to the block editor in the site customizer, giving clients control over their design without touching code. Define your brand colors as named presets, set up a fluid typography scale, and lock down maximum content width so layouts stay consistent.
For portfolio sites, directories, or real estate, you need custom post types beyond posts and pages. Register them in functions.php using register_post_type(). For custom fields (extra data per post), use the Advanced Custom Fields (ACF) plugin for non-developers, or register_meta() with the REST API enabled for headless WordPress setups. Always create templates for your custom post types in the theme template hierarchy.
Never add stylesheets or scripts directly to header.php. Use wp_enqueue_style() and wp_enqueue_script() in your theme's functions.php to register and enqueue assets properly. This allows WordPress and plugins to manage load order and dependencies. Add the in_footer parameter to scripts where possible to defer them, improving LCP. For block themes, only enqueue what the full site editing system doesn't already handle.
WordPress themes are frequent attack vectors. Always escape output with esc_html(), esc_url(), esc_attr(), and wp_kses() before rendering dynamic data. Use nonces for form submissions and AJAX requests. Never use $_POST, $_GET, or $_REQUEST directly — always validate and sanitize. Keep WordPress, themes, and plugins updated. Use a security plugin like Wordfence for monitoring and blocking malicious traffic.