How to Build a WordPress Theme From Scratch

Khadijah Maulion Masorong·September 26, 2026
How to Build a WordPress Theme From Scratch (2026) — build wordpress theme
Udemy US - CPS

Building a WordPress theme from scratch looks intimidating until you see it broken into the right sequence. Here’s the direct answer: plan your file structure, create a style.css header and a functions.php file, build the template hierarchy starting with header.php, footer.php, and index.php, add page and post templates, enable theme support features, enqueue your scripts and styles correctly, then test everything before launch. That’s the whole arc.

Capcut

I’ve built custom WordPress themes for client projects for years, and the method below is the same one I use on real builds, not a recycled checklist.

This guide walks through each step in order with copy-paste-ready code, so you can go from an empty folder to a working theme in one sitting. If you already have a Figma file or static HTML mockup you want turned into WordPress, head to the Figma to WordPress conversion guide instead. But if you want to understand WordPress theme architecture at the code level, keep reading.

What You Need Before You Start Building a Theme

You don’t need to be a senior developer to build your first WordPress theme, but you do need specific baseline skills and setup. Skipping this step is the number one reason first-time theme builds stall out halfway through.

Technical prerequisites

A WordPress theme is fundamentally a folder of PHP templates styled with CSS and enhanced with JavaScript. You should be comfortable with:

  • HTML: structuring markup semantically (headers, nav, articles, footers)
  • CSS: styling layout, typography, and responsiveness
  • Basic PHP: reading template tags, conditionals, and loops (you don’t need to be a PHP expert, but you need to read it without hesitation)
  • JavaScript basics: helpful if you plan to enqueue custom scripts or interactivity

If PHP is the gap, focus your first learning hours there. HTML and CSS transfer directly; PHP is what’s genuinely new for most designers moving into theme development.

Local development environment setup

Never build a theme directly on a live server. Set up a local WordPress install using a tool like Local by WP Engine or a Docker-based stack, install a fresh copy of WordPress, and activate a blank starter theme so you have a clean slate. This lets you break things safely, which you will, especially the first time you touch functions.php.

Classic theme vs. block theme: which to build

WordPress supports two theme architectures. Choosing the right one before you write a single file saves hours of rework later.

Feature Classic Theme Block Theme
Templates written in PHP (header.php, footer.php, index.php) HTML with block markup, stored in /templates
Styling control style.css and functions.php theme.json and style.css
Site Editor support Limited or none Full Site Editor (Full Site Editing) support
Learning curve Familiar if you know PHP Requires learning theme.json schema and block templates
Best for Developers who want granular PHP control Sites built primarily with the block editor

The WordPress Developer Handbook covers both architectures in detail. This guide focuses on the classic theme approach, since it’s still the most common entry point for developers learning theme structure and makes the template hierarchy easiest to understand.

Prerequisites Checklist

  • Comfortable reading basic PHP syntax and conditionals
  • Local WordPress install running (not a live production site)
  • Code editor installed (VS Code, Sublime Text, or similar)
  • Decided: classic theme or block theme
  • Backup of any existing site before testing on it

Step 1: Plan the Theme’s File and Folder Structure

Every WordPress theme lives in its own folder inside /wp-content/themes/. WordPress only requires two files to recognize a folder as a valid theme: style.css and index.php. Everything else is optional but expected in any real-world build.

File Required? Purpose
style.css Yes Theme metadata and main stylesheet
index.php Yes Fallback template for all content
functions.php Strongly recommended Theme setup, hooks, enqueueing
header.php Recommended Site header, opens <html> and <head>
footer.php Recommended Site footer, closes tags
screenshot.png Recommended Preview image in the WordPress admin
single.php Optional Template for individual blog posts
page.php Optional Template for static pages
archive.php Optional Template for post archives
404.php Optional Custom "not found" page

Recommended folder organization

A clean, scalable folder structure looks like this:

my-custom-theme/
├── style.css
├── index.php
├── functions.php
├── header.php
├── footer.php
├── page.php
├── single.php
├── archive.php
├── 404.php
├── screenshot.png
├── /assets
│   ├── /css
│   ├── /js
│   └── /images
├── /template-parts
│   ├── content.php
│   └── content-page.php
└── /inc
    ├── enqueue.php
    └── theme-setup.php

The /assets folder keeps your compiled CSS and JS separate from PHP logic. The /template-parts folder holds reusable chunks you pull into templates with get_template_part(), which keeps individual template files short and readable. The /inc folder is where I split functions.php logic into smaller files once a theme grows past a few hundred lines, since one giant functions.php becomes unmanageable fast on real client builds.

Step 2: Set Up style.css and functions.php

These two files are the backbone of every classic WordPress theme. Get them wrong and nothing else works.

Writing the style.css header comment block

WordPress reads a specific comment block at the top of style.css to identify your theme in the admin dashboard. Without it, WordPress won’t recognize the folder as a theme.

/*
Theme Name: My Custom Theme
Theme URI: https://example.com/my-custom-theme
Author: Your Name
Author URI: https://example.com
Description: A custom-built WordPress theme for learning the fundamentals of theme development.
Version: 1.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
*/

Everything below this comment block is standard CSS. WordPress parses these header fields to display the theme name, description, and version in Appearance > Themes.

Creating a minimal functions.php

functions.php acts like a plugin that’s built into your theme. It’s where you hook into WordPress core to add features, register menus, and load your assets. Write it as pure PHP, not wrapped in closing and opening tags.

<?php
// Theme setup
function my_theme_setup() {
    add_theme_support( 'title-tag' );
    add_theme_support( 'post-thumbnails' );
    add_theme_support( 'custom-logo' );
    register_nav_menus( array(
        'primary' => __( 'Primary Menu', 'my-custom-theme' ),
    ) );
}
add_action( 'after_setup_theme', 'my_theme_setup' );

That’s a working, minimal functions.php. From here you’ll add enqueueing (Step 6) and any custom logic your site needs.

Step 3: Build the Template Hierarchy (header, footer, index)

The template hierarchy is WordPress’s internal decision tree for choosing which PHP file renders any given request. Understanding it is the single most important concept in classic theme development.

How WordPress chooses which template to load

When a visitor requests a single blog post, WordPress checks in this order:

  1. single-{post-type}-{slug}.php
  2. single-{post-type}.php
  3. single.php
  4. singular.php
  5. index.php (final fallback, always present)

The same logic applies to pages, archives, categories, tags, search results, and 404s, each with its own hierarchy chain that always terminates at index.php. The official Theme Handbook’s template hierarchy documentation has the full diagram for every content type and is worth bookmarking as a reference while you build.

Creating header.php and footer.php

header.php opens the HTML document and typically includes the <head> section, site branding, and navigation:

<!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(); ?>>

footer.php closes what header.php opened and always calls wp_footer(), where WordPress and plugins hook in scripts:

    <?php wp_footer(); ?>
</body>
</html>

Never skip wp_head() or wp_footer(). Plugins depend on these hooks firing, and a large share of "my plugin isn’t working" support tickets trace back to a theme missing one of these calls.

Creating index.php as the fallback template

index.php is the only template file WordPress absolutely requires beyond style.css, and it’s the final fallback in every hierarchy chain. A basic version pulls in your header, runs the Loop, and pulls in your footer:

<?php get_header(); ?>

<main id="main-content">
    <?php if ( have_posts() ) : while ( have_posts() ) : the_post(); ?>
        <article id="post-<?php the_ID(); ?>" <?php post_class(); ?>>
            <h2><?php the_title(); ?></h2>
            <div><?php the_content(); ?></div>
        </article>
    <?php endwhile; endif; ?>
</main>

<?php get_footer(); ?>

have_posts() and the_post() together form what WordPress calls "the Loop," the mechanism every template uses to fetch and display content from the database.

Watching someone build this out live helps it click faster than reading code alone. LearnWebCode’s walkthrough covers this exact process from a blank folder to a working theme:

Step 4: Create Page and Post Templates

Once your fallback templates work, you can build more specific templates for pages and posts so different content types can look and behave differently.

Custom page templates

A custom page template is a PHP file with a special comment at the top that makes it selectable from the Page Attributes panel in the editor:

<?php
/* Template Name: Full Width Page */
get_header();
?>
<main class="full-width">
    <?php while ( have_posts() ) : the_post(); the_content(); endwhile; ?>
</main>
<?php get_footer(); ?>

Save this as page-full-width.php and it appears as "Full Width Page" in the template dropdown for any page in the admin.

single.php and archive.php for posts

single.php controls how one individual blog post displays. archive.php controls how a list of posts (by category, tag, date, or author) displays. Both are applied automatically by the template hierarchy, unlike page templates which editors select by hand.

Aspect Page Templates Post Templates (single.php, archive.php)
Assignment Manually selected per page in the editor Automatically applied by the template hierarchy
Naming convention page-{slug}.php or custom-named with a Template Name comment single.php, single-{post-type}.php, archive.php
Typical use case Landing pages, contact pages, unique layouts Blog posts, custom post type archives
Reusability One template can serve many different pages Applies globally to all posts of that type unless overridden

Step 5: Add Theme Support Features and Custom Functions

WordPress core ships with a long list of features that themes must explicitly opt into. This is where add_theme_support() comes in.

Using add_theme_support (post thumbnails, menus, title-tag)

Each call tells WordPress "yes, this theme supports this feature," which unlocks admin UI and template functions that would otherwise be unavailable.

Common add_theme_support Features

  • title-tag – lets WordPress manage the <title> tag automatically
  • post-thumbnails – enables featured images
  • custom-logo – enables the Site Identity logo uploader
  • html5 – modernizes markup for search forms, comments, galleries
  • custom-background – enables a background color or image customizer option
  • automatic-feed-links – adds RSS feed links to the head
  • align-wide – allows wide and full-width block alignment (useful even in classic themes using the block editor for content)

Registering navigation menus and widget areas

Menus and widget areas (sidebars) need registration in functions.php before they can be assigned in the admin:

function my_theme_widgets_init() {
    register_sidebar( array(
        'name'          => __( 'Main Sidebar', 'my-custom-theme' ),
        'id'            => 'sidebar-1',
        'before_widget' => '<div class="widget">',
        'after_widget'  => '</div>',
        'before_title'  => '<h3>',
        'after_title'   => '</h3>',
    ) );
}
add_action( 'widgets_init', 'my_theme_widgets_init' );

Once registered, both menus and widget areas show up automatically under Appearance in the dashboard, ready for a site owner to populate without touching code.

Step 6: Enqueue Styles and Scripts Properly

This step trips up more first-time theme builders than any other. It’s also the one client project I most often get called in to fix, usually a theme where every stylesheet was hardcoded straight into header.php.

Why not to hardcode <link>/<script> tags

Hardcoding a <link rel="stylesheet"> or <script src=""> tag directly into header.php bypasses WordPress’s dependency management system entirely. That means no version control for cache-busting, no way for plugins to detect and avoid loading duplicate libraries like jQuery, and no clean way to conditionally load scripts only where they’re needed. It works in a demo. It causes conflicts on a real site with plugins installed.

Using wp_enqueue_style and wp_enqueue_script correctly

Both functions must run inside a function hooked to wp_enqueue_scripts, never called directly in the global scope of a template file:

function my_theme_scripts() {
    wp_enqueue_style(
        'my-theme-style',
        get_stylesheet_uri(),
        array(),
        '1.0'
    );

    wp_enqueue_script(
        'my-theme-script',
        get_template_directory_uri() . '/assets/js/main.js',
        array(),
        '1.0',
        true
    );
}
add_action( 'wp_enqueue_scripts', 'my_theme_scripts' );

The fourth parameter in wp_enqueue_script() is the version number (helps with cache busting after updates), and the fifth (true) loads the script in the footer rather than the head, which is best practice for performance since it doesn’t block page rendering.

On one client rebuild, the previous developer had hardcoded four <script> tags directly in header.php, including a duplicate copy of jQuery already bundled with WordPress core. It silently broke a contact form plugin because two versions of jQuery were fighting for the same namespace. Switching everything to properly enqueued, deduplicated assets fixed it in under ten minutes. Enqueueing isn’t a formality, it’s what keeps your theme compatible with the plugin ecosystem.

Step 7: Test, Debug, and Prepare for Launch

A theme that "looks right" on your screen isn’t the same as a theme that’s ready to launch. This step is where amateur builds and production-ready builds diverge.

Enabling WP_DEBUG and checking for errors

Add this to your local wp-config.php (never on a live production site) to surface hidden PHP notices and warnings:

define( 'WP_DEBUG', true );
define( 'WP_DEBUG_LOG', true );
define( 'WP_DEBUG_DISPLAY', false );

This logs errors to /wp-content/debug.log instead of displaying them on screen, so you can review issues without exposing them to site visitors.

Cross-browser and responsiveness testing

Check your theme in at least Chrome, Firefox, and Safari, plus a real mobile device if you can, not just a browser’s device-emulation mode. Test:

  • Navigation menu behavior on mobile (does it collapse correctly?)
  • Featured images and post thumbnails at different screen widths
  • Widget areas and footer columns stacking properly on small screens
  • Forms and interactive JavaScript elements functioning without console errors

Common first-theme problems and fixes

Problem Likely Cause Fix
Blank white screen PHP fatal error, often a missing semicolon or unclosed bracket Enable WP_DEBUG and check debug.log
Theme not appearing in Appearance > Themes Missing or malformed style.css header comment Verify the comment block syntax exactly matches the required format
Styles not loading CSS hardcoded instead of enqueued, or wrong file path in wp_enqueue_style Confirm the path with get_stylesheet_uri() or get_template_directory_uri()
Menu not showing Menu registered in functions.php but not assigned to a location in Appearance > Menus Assign the menu location in the admin
Featured image not displaying Missing add_theme_support(‘post-thumbnails’) or missing the_post_thumbnail() call in the template Add both the theme support call and the template function

Pre-Launch QA Checklist

  • WP_DEBUG enabled and debug.log reviewed, no unresolved notices
  • All templates tested: home, single post, page, archive, search, 404
  • Menus and widget areas assigned and displaying correctly
  • All scripts and styles loading via enqueue, none hardcoded
  • Tested on at least two browsers and one real mobile device
  • screenshot.png added for the admin theme preview
  • Text domain consistent across all translatable strings

To see a fuller theme built end to end with more advanced patterns, freeCodeCamp’s full-length course walks through an entire custom build:

Should You Build a Theme From Scratch or Hire a Developer?

Building your own theme is a genuinely good way to learn WordPress’s architecture and gives you total control over markup, performance, and functionality. It also takes real time, and every hour spent debugging enqueue conflicts is an hour not spent on the actual business the site supports.

Build it yourself if: you already know PHP fundamentals, you want to learn theme architecture deeply, the site is a portfolio or learning project, and you have time to troubleshoot.

Use a starter theme if: you want a head start on boilerplate (menu registration, basic enqueueing, accessibility markup already handled) without giving up customization control. Underscores (_s) from Automattic is a well-known example of this approach.

Hire a developer if: the site is client-facing or revenue-generating, you need it done reliably on a timeline, or the design requirements go beyond what you’re confident building and maintaining yourself.

If your business needs a theme that reflects real design intent rather than a generic template, it’s worth reading what actually defines a custom WordPress theme before deciding, and comparing custom themes against off-the-shelf templates for your specific situation. For teams that would rather hand this off entirely, custom WordPress theme development work at Ivory Miracle picks up exactly where this tutorial leaves off, building a production-grade theme around your actual content and brand rather than a generic starter. And if you’re not building the theme yourself but need someone who is, this guide to hiring a freelance WordPress developer walks through vetting and scoping the work.

Frequently Asked Questions

Can ChatGPT build a WordPress website?

ChatGPT and similar tools can generate PHP snippets, suggest functions.php code, and help debug errors, but they can’t independently create, upload, activate, and configure a working theme inside your WordPress install. Treat them as a coding assistant for individual files, not a replacement for understanding the template hierarchy yourself.

Which theme builder is best for WordPress?

This depends on your goals. If you want full code control, building a classic or block theme from scratch gives you the most flexibility. If you’d rather use a visual page builder, Gutenberg’s Site Editor for block themes is the option built directly into WordPress core, per the WordPress Developer Handbook.

What is the easiest WordPress theme to use for beginners?

For non-developers who don’t want to write any code, a well-supported block theme configured through the Site Editor is generally the most approachable path, since it uses a visual interface rather than PHP templates.

Are WordPress themes free?

Many are. The WordPress.org theme directory hosts thousands of free, reviewed themes that meet WordPress’s Theme Review Guidelines. Premium themes with additional features or support are sold separately by third-party developers and marketplaces.

What files are required to build a WordPress theme?

Only two files are technically required: style.css with a valid theme header comment block, and index.php as the fallback template. Every other file, including functions.php, header.php, and footer.php, is optional but expected in any real-world theme.

What is the WordPress template hierarchy?

The WordPress template hierarchy is the ordered set of rules WordPress follows to decide which template file renders a given page, checking specific file names first (like single-post.php) and falling back to more general ones (like index.php) if a more specific match doesn’t exist.

Do I need to know PHP to build a WordPress theme?

For a classic theme, yes, at least at a basic level, since templates are written in PHP mixed with HTML. Block themes reduce the PHP requirement significantly by relying on theme.json and block markup instead, making them more approachable if PHP is a hard blocker for you.

How long does it take to build a WordPress theme from scratch?

A simple classic theme with the core templates covered in this guide takes a few days to two weeks for someone comfortable with PHP and CSS, working part-time. More complex themes with custom post types, multiple page templates, and full accessibility and performance testing take longer.

What is a child theme and when should I use one?

A child theme inherits all the functionality and styling of a parent theme while letting you override specific templates or styles in its own folder, without editing the parent theme’s files directly. Use one whenever you’re customizing an existing theme (yours or a third-party one) so your changes survive parent theme updates.

Can I build a WordPress theme without coding?

Yes, through block themes and the Site Editor, which let you assemble templates visually using blocks and patterns rather than writing PHP. It’s a fundamentally different workflow from the classic theme process in this guide, and it trades some code-level control for accessibility to non-developers.

Getting Started

Building a WordPress theme from scratch is one of those skills that feels overwhelming right up until the moment your first index.php successfully renders a post. After that, the template hierarchy stops being an abstract diagram and becomes something you can predict and control. Start with the file structure, get style.css and functions.php right, and build outward one template at a time. That’s the whole method, and it scales from a weekend learning project to a full client theme without changing the fundamentals.

Sources

Related: custom WordPress theme development

Conversation

2 comments

Good work gets better when people bring thoughtful questions, ideas, and perspective.

Leave a considered reply

Your email stays private. Required fields are marked *.