WordPress Theme Development Tutorial: The Practical 2026 Guide

Honest answer before you start
Building a WordPress theme entirely from scratch in 2026 is almost always the wrong call – unless learning is explicitly the goal. For client work or production sites, start with Underscores (_s) for a classic PHP theme, or the block theme starter (create-block-theme plugin) for Full Site Editing. You’ll ship faster, maintain less, and get a better result. This tutorial covers the real process, all the approaches compared, and when each one actually makes sense.
I’ve built WordPress themes using every method available – raw from scratch, starter themes, page builders, FSE block themes. Each has a place, but the “from scratch” approach gets oversold in tutorials because it makes for a cleaner narrative. Reality is messier and more practical.
Whether you want to understand theme structure deeply, build something for a client, or sell on a marketplace, this guide will walk you through what actually matters and help you choose the right approach for your situation.
Which approach should you use in 2026?
WordPress theme development has fragmented significantly since the block editor arrived. Pick your path based on your goal, not on what’s most “pure.”
| Approach | Best For | Time to First Working Theme | Maintenance Overhead | My Take |
|---|---|---|---|---|
| From scratch (classic) | Learning how WP works | Days-weeks | High – you own all the plumbing | Only for education. Don’t ship client work this way. |
| Underscores / _s | Custom classic PHP themes | Hours | Medium – you build on proven scaffolding | The right choice for classic custom themes. Still relevant. |
| Block theme starter (FSE) | Modern Gutenberg-first themes | Hours | Low once you understand theme.json | The future. Use this for new projects if client is comfortable with full-site editing. |
| Page builder (Divi, Elementor, Bricks) | Non-developer clients; fast delivery | Minutes | Medium – builder dependency forever | Legitimate for the right context. Don’t apologize for using it. |
| WooCommerce child theme | E-commerce customization | Hours | Low – inherit parent updates | Correct approach for any WooCommerce site. |
Introduction to WordPress theme structure
Regardless of which approach you use, understanding the classic theme structure makes you a better developer. WordPress themes live in /wp-content/themes/your-theme-name/. The two absolutely required files are style.css (the theme header) and index.php (the fallback template). Everything else is optional but expected for a complete theme.
Here’s what a solid production-ready classic theme directory looks like:
| File / Folder | Purpose | Required? |
|---|---|---|
style.css | Theme header metadata (name, author, version) + base styles | Yes |
index.php | Fallback template for all content | Yes |
functions.php | Theme setup, enqueue scripts/styles, register menus, sidebars, custom post types | Strongly recommended |
header.php | Site header output; called by get_header() | No, but expected |
footer.php | Site footer; called by get_footer() | No, but expected |
sidebar.php | Sidebar widget area | No |
single.php | Single post template | No (falls back to index.php) |
page.php | Static page template | No (falls back to index.php) |
archive.php | Category, tag, date archives | No |
search.php | Search results page | No |
404.php | Not found page | No |
/assets/ | CSS, JS, images, fonts | No, but expected |
/template-parts/ | Reusable template fragments (content, cards, etc.) | No |
The style.css header block at the top of the file is what makes WordPress recognize your folder as a theme:
/*
Theme Name: My Custom Theme
Theme URI: https://example.com/
Author: Your Name
Author URI: https://example.com/
Description: A custom theme for...
Version: 1.0.0
License: GNU General Public License v2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html
Text Domain: my-custom-theme
*/
How to create a basic WordPress theme (the right way)
If you’re building a classic theme, here’s the setup I actually use – not the overly simplified version most tutorials show.
- Generate your starter with Underscores: Go to
underscores.me, enter your theme name, click “Advanced Options” to add a CSS preprocessor option if needed, then download the zip. This gives you a fully functional, well-structured theme in seconds. - Set up a local environment: Use Local by Flywheel, DDEV, or Lando. Do not develop on a live server. Seriously.
- Install your theme: Extract into
/wp-content/themes/and activate in wp-admin. - Set up your enqueue hooks: In
functions.php, usewp_enqueue_style()andwp_enqueue_script()– never<link>tags hardcoded in header.php. - Build templates top-down: Start with header.php and footer.php, then index.php, single.php, and page.php. Add archive.php and 404.php once the basics work.
- Test the template hierarchy: WordPress uses a specific fallback chain. Understanding it saves hours of debugging.
Adding stylesheets and scripts properly
The wrong way is putting <link rel="stylesheet"> directly in header.php. The right way is using the enqueue system in functions.php so WordPress can manage dependencies, versioning, and loading order.
function mytheme_enqueue_assets() {
// Main stylesheet
wp_enqueue_style(
'mytheme-style',
get_stylesheet_uri(),
array(),
wp_get_theme()->get('Version')
);
// Custom JS (defer it - don't block render)
wp_enqueue_script(
'mytheme-main',
get_template_directory_uri() . '/assets/js/main.js',
array(), // dependencies
'1.0.0',
array('strategy' => 'defer') // WP 6.3+ syntax
);
}
add_action('wp_enqueue_scripts', 'mytheme_enqueue_assets');
Two types of assets you’ll register: CSS stylesheets control layout and design, and JavaScript handles interactivity. Keep your JS deferred unless it must block rendering – the page speed difference is real.
WordPress template hierarchy: how WordPress decides which file to use
This is the part most tutorials skip that causes endless confusion. WordPress has a defined priority order for which template file loads for any given URL. Knowing this lets you target specific content types precisely.
| URL Type | Template Priority (first match wins) |
|---|---|
| Single post | single-{post-type}-{slug}.php → single-{post-type}.php → single.php → singular.php → index.php |
| Static page | page-{slug}.php → page-{id}.php → page.php → singular.php → index.php |
| Category archive | category-{slug}.php → category-{id}.php → category.php → archive.php → index.php |
| Custom post type archive | archive-{post-type}.php → archive.php → index.php |
| Search results | search.php → index.php |
| 404 not found | 404.php → index.php |
| Front page | front-page.php → home.php → index.php |
Bookmark the full template hierarchy diagram at developer.wordpress.org. You’ll reference it constantly until it’s second nature.
Creating WordPress theme templates
WordPress uses two types of template constructs you need to understand:
- Template files: PHP files that output specific sections (header.php, footer.php, single.php, etc.). Called via
get_header(),get_footer(),get_template_part(). - Template tags: PHP functions that output dynamic content within templates –
the_title(),the_content(),the_permalink(),get_the_date(), etc.
A minimal but functional index.php looks like this:
<?php get_header(); ?>
<main id="main" class="site-main">
<?php if ( have_posts() ) : ?>
<?php while ( have_posts() ) : the_post(); ?>
<article id="post-<?php the_ID(); ?>" <?php post_class(); ?>>
<h2><a href="<?php the_permalink(); ?>"><?php the_title(); ?></a></h2>
<?php the_excerpt(); ?>
</article>
<?php endwhile; ?>
<?php else : ?>
<p>No posts found.</p>
<?php endif; ?>
</main>
<?php get_footer(); ?>
Separate your template logic from your output where possible. The template-parts folder is your friend – use get_template_part('template-parts/content', 'post') to pull in reusable fragments.
Enhancing your theme with custom post types and taxonomies
Custom post types (CPTs) let you define new content models beyond posts and pages – products, portfolio items, team members, events, whatever your project needs. Register them in functions.php using register_post_type() hooked to init.
Here’s the workflow I follow:
- Identify what content types the site genuinely needs – don’t register CPTs speculatively.
- Register with
register_post_type(). Set'has_archive' => trueif you need an archive URL. Set'show_in_rest' => trueif you want block editor support (you do). - Register associated taxonomies with
register_taxonomy()linked to your CPT. - Create template files:
archive-{post-type}.phpandsingle-{post-type}.phpto control the display. - Flush rewrite rules once after registration (Settings → Permalinks → Save) – do not do this on every page load.
- Query posts using
WP_Querywhere needed with properposts_per_page,post_type, andtax_queryargs.
One thing I always do: register CPTs in a plugin, not the theme. If the theme changes, the content type survives. Registering in the theme is a common mistake beginners make.
Distributing your WordPress theme
Once your theme is complete and tested, you have three main distribution paths:
WordPress.org theme repository (free)
Free only, strict review process (Theme Check plugin is mandatory before submission), but massive exposure. Best for building reputation or supporting a freemium model. Expect 2-8 weeks review time and several rounds of feedback.
Theme marketplaces (paid)
ThemeForest (Envato) is the dominant paid marketplace – high volume, fierce competition, 30-50% commission. Creative Market has lower volume but more favorable split. Both require passing a quality review. The downside: your pricing flexibility is limited and customer support expectations are high.
Self-hosted / direct sales
Maximum margin, full control over pricing and licensing, but you’re responsible for discovery, payment processing, and support. Works well if you have an existing audience. Easy Digital Downloads or Freemius are the standard stacks for self-hosted WordPress product sales.
Page builders: when they’re the right choice
Page builders like Divi, Elementor, and Bricks Builder aren’t cheating. For non-technical clients who will be maintaining their own content, they’re often the most practical solution. The honest tradeoffs:
| Builder | Best For | Page Speed | Lock-in Risk | Developer Experience |
|---|---|---|---|---|
| Bricks Builder | Developer-led custom builds | Best of the three | Medium | Excellent – CSS grid, dynamic data, clean output |
| Elementor | Client self-management; large plugin ecosystem | Heavier | High | Decent but can get messy |
| Divi (Elegant Themes) | Non-technical users; subscription model | Medium | High | Sufficient but proprietary |
My personal preference for client work: Bricks Builder for anything where I control the build long-term. Elementor if the client insists on managing everything themselves and the team is already familiar with it. I’d steer new projects away from Divi in 2026 – Bricks has taken that niche and does it better.
Block themes and Full Site Editing: the 2026 reality
Block themes (FSE) use theme.json for global styles and HTML template files instead of PHP. They’re the official WordPress direction – every new default theme since Twenty Twenty-Two has been a block theme.
Should you use FSE for new projects? My current position: yes for smaller sites and anything that would have previously used a page builder. Not yet for complex WooCommerce stores or sites with heavy custom PHP logic – the tooling isn’t quite there. Use the create-block-theme plugin to export a working block theme from an existing site’s customizations.
Frequently asked questions
Do I need to know PHP to develop a WordPress theme?
For classic PHP themes, yes – you need at least intermediate PHP to work with template tags, the Loop, and functions.php hooks. For block themes (FSE), you can get surprisingly far with HTML, CSS, and JSON alone. For page builders, PHP knowledge is optional. Pick your approach based on your existing skills and project requirements.
What is the minimum file requirement for a WordPress theme?
Technically just style.css (with the theme header comment block) and index.php. In practice, a production theme needs at minimum: style.css, index.php, functions.php, header.php, footer.php, single.php, and page.php. Anything less and you’re relying entirely on fallbacks.
Should I build a child theme or a standalone theme?
Child themes are for customizing an existing parent theme without losing changes on updates. If you’re making small modifications to Twenty Twenty-Four or a third-party theme, use a child theme. If you’re building something custom from scratch (or from Underscores), build a standalone theme – there’s no parent to inherit from.
How do I register custom post types correctly?
Register custom post types in a plugin, not the theme – so your content model survives a theme switch. Use register_post_type() hooked to init. Always set 'show_in_rest' => true to enable block editor support. After registering, flush rewrite rules once by visiting Settings → Permalinks and clicking Save.
Can I sell a WordPress theme I developed?
Yes. If your theme is based on WordPress core (GPL-licensed), your theme’s PHP code must also be GPL. CSS and JavaScript in themes are dual-licensed (GPL + copyright), meaning you can sell and restrict redistribution of design assets. ThemeForest, Creative Market, and self-hosted options like Easy Digital Downloads or Freemius are the common sales channels.
{ “@context”: “https://schema.org”, “@type”: “FAQPage”, “mainEntity”: [ { “@type”: “Question”, “name”: “Do I need to know PHP to develop a WordPress theme?”, “acceptedAnswer”: { “@type”: “Answer”, “text”: “For classic PHP themes, yes – intermediate PHP is needed for template tags, the Loop, and functions.php hooks. For block themes (FSE), you can work primarily with HTML, CSS, and JSON. For page builders, PHP knowledge is optional. Choose based on your skills and project type.” } }, { “@type”: “Question”, “name”: “What is the minimum file requirement for a WordPress theme?”, “acceptedAnswer”: { “@type”: “Answer”, “text”: “Technically just style.css (with the theme header comment block) and index.php. In practice, a production theme needs at minimum: style.css, index.php, functions.php, header.php, footer.php, single.php, and page.php.” } }, { “@type”: “Question”, “name”: “Should I build a child theme or a standalone theme?”, “acceptedAnswer”: { “@type”: “Answer”, “text”: “Child themes are for customizing an existing parent theme without losing changes on updates. If you’re making small modifications to an existing theme, use a child theme. If you’re building something custom from scratch or from Underscores, build a standalone theme.” } }, { “@type”: “Question”, “name”: “How do I register custom post types correctly?”, “acceptedAnswer”: { “@type”: “Answer”, “text”: “Register custom post types in a plugin, not the theme – so your content model survives a theme switch. Use register_post_type() hooked to init. Always set ‘show_in_rest’ to true to enable block editor support. After registering, flush rewrite rules once by visiting Settings > Permalinks and clicking Save.” } }, { “@type”: “Question”, “name”: “Can I sell a WordPress theme I developed?”, “acceptedAnswer”: { “@type”: “Answer”, “text”: “Yes. If your theme is based on WordPress core (GPL-licensed), the PHP code must also be GPL. CSS and JavaScript are dual-licensed, meaning you can restrict redistribution of design assets. ThemeForest, Creative Market, and self-hosted options like Easy Digital Downloads or Freemius are common sales channels.” } } ] }


