How to Create A Custom Theme In WordPress Step By Step Guide
- 16 Sep 2025
- 0 Comments
Building a WordPress theme from scratch is a great way to learn how WordPress works, get full control over your site’s markup and styles, and tailor the site to your exact needs. This guide walks you through everything — from a minimal theme skeleton to more advanced features (customizer, widgets, internationalization, testing, and packaging). Follow the steps below and you’ll have a clean, production-ready theme ready to extend.
Step 1: Overview & prerequisites
What you need
- Local WordPress environment (LocalWP, MAMP, XAMPP, Docker, etc.).
- Code editor (VS Code, PhpStorm, Sublime).
- Basic HTML, CSS, PHP and WordPress fundamentals.
- Optional: Git, Node.js (for build tooling), image editor.
Goal of this guide
- Create a classic (PHP) theme with recommended files.
- Register scripts/styles, menus, widget areas.
- Add template parts, customizer support, i18n, and best practices.
- Quick notes on block (Full Site Editing) themes.
Step 2: Create the theme folder & minimal required files
1: In your WP install create a new directory:
wp-content/themes/my-custom-theme
2: Create these minimal files:
style.css
— theme header + base styles (required for WP to detect the theme)index.php
— fallback templatefunctions.php
— theme setup & asset registrationscreenshot.png
— optional thumbnail shown in WP admin
a) style.css (required header)
/*
Theme Name: My Custom Theme
Theme URI: https://example.com
Author: Your Name
Author URI: https://example.com
Description: A clean custom theme
Version: 1.0
License: GNU General Public License v2
Text Domain: my-custom-theme
*/
b) index.php (very small fallback)
<?php get_header(); ?>
<main id="content">
<?php if ( have_posts() ) : 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>
<div class="entry-content"><?php the_excerpt(); ?></div>
</article>
<?php endwhile; else : ?>
<p><?php esc_html_e( 'No posts found', 'my-custom-theme' ); ?></p>
<?php endif; ?>
</main>
<?php get_sidebar(); ?>
<?php get_footer(); ?>
Step 3: Basic theme setup (functions.php
)
functions.php
is where you declare theme supports (title tag, thumbnails), register menus, enqueue assets, and load translations.
<?php
if ( ! function_exists( 'cv_setup' ) ) {
function mct_setup() {
load_theme_textdomain( 'my-custom-theme', get_template_directory() . '/languages' );
add_theme_support( 'title-tag' );
add_theme_support( 'post-thumbnails' );
add_theme_support( 'html5', array( 'search-form', 'comment-form', 'comment-list', 'gallery', 'caption' ) );
add_theme_support( 'custom-logo' );
register_nav_menus( array(
'primary' => __( 'Primary Menu', 'my-custom-theme' ),
) );
}
}
add_action( 'after_setup_theme', 'cv_setup' );
function cv_enqueue_assets() {
wp_enqueue_style( 'cv-style', get_stylesheet_uri(), array(), wp_get_theme()->get('Version') );
// Example: enqueue a JS file (defer/block if necessary)
// wp_enqueue_script( 'cv-main', get_template_directory_uri() . '/assets/js/main.js', array(), '1.0', true );
}
add_action( 'wp_enqueue_scripts', 'cv_enqueue_assets' );
function cv_widgets_init() {
register_sidebar( array(
'name' => __( 'Sidebar', 'my-custom-theme' ),
'id' => 'sidebar-1',
'before_widget' => '<section id="%1$s" class="widget %2$s">',
'after_widget' => '</section>',
'before_title' => '<h3 class="widget-title">',
'after_title' => '</h3>',
) );
}
add_action( 'widgets_init', 'cv_widgets_init' );
Notes :
- Always prefix function names to avoid collisions (
mct_
above). - Use
wp_get_theme()->get('Version')
for cache busting. - Keep logic minimal; complex features can go into separate files (e.g.,
inc/
).
Step 4: Split templates — header, footer, sidebar, template-parts
Create modular template files so your theme is maintainable.
a) 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 class="site-header">
<div class="site-branding">
<?php if ( function_exists( 'the_custom_logo' ) ) the_custom_logo(); ?>
<a class="site-title" href="<?php echo esc_url( home_url('/') ); ?>"><?php bloginfo('name'); ?></a>
</div>
<nav class="site-navigation">
<?php wp_nav_menu( array( 'theme_location' => 'primary' ) ); ?>
</nav>
</header>
b) footer.php
<footer class="site-footer">
<p>© <?php echo date('Y'); ?> <?php bloginfo('name'); ?></p>
</footer>
<?php wp_footer(); ?>
</body>
</html>
c) sidebar.php (widget area)
<aside id="secondary" class="widget-area">
<?php dynamic_sidebar( 'sidebar-1' ); ?>
</aside>
d) template-parts/content.php (use with get_template_part()
)
<article id="post-<?php the_ID(); ?>" <?php post_class(); ?>>
<header class="entry-header">
<h2 class="entry-title"><a href="<?php the_permalink(); ?>"><?php the_title(); ?></a></h2>
</header>
<div class="entry-content">
<?php the_excerpt(); ?>
</div>
</article>
Use get_template_part( 'template-parts/content' );
inside index.php
, archive.php
, etc.
Step 5: Create core templates (single, page, archive, 404)
single.php
— single post display (usethe_content()
and comments).page.php
— static pages.archive.php
— category/tag/date archives.search.php
and404.php
.
a) single.php
<?php get_header(); ?>
<main>
<?php if ( have_posts() ) : while ( have_posts() ) : the_post(); ?>
<article <?php post_class(); ?>>
<h1><?php the_title(); ?></h1>
<?php the_content(); ?>
</article>
<?php endwhile; endif; ?>
</main>
<?php get_sidebar(); ?>
<?php get_footer(); ?>
Step 6: Enqueue scripts/styles properly & asset organization
Best practice
- Put CSS, JS in
assets/css/
andassets/js/
. - Use
wp_enqueue_script()
andwp_enqueue_style()
. - Don’t output
<link>
/<script>
manually in header/footer.
Example of enqueueing compiled CSS / JS:
function mct_enqueue_assets() {
wp_enqueue_style( 'cv-style', get_stylesheet_directory_uri() . '/assets/css/style.css', array(), filemtime( get_template_directory() . '/assets/css/style.css' ) );
wp_enqueue_script( 'cv-main', get_template_directory_uri() . '/assets/js/main.js', array('jquery'), filemtime( get_template_directory() . '/assets/js/main.js' ), true );
}
add_action( 'wp_enqueue_scripts', 'mct_enqueue_assets' );
Using filemtime()
is a simple cache-busting technique during development.
Step 7: Menus, Custom Logo & Theme Customizer
Register menu (already done in functions.php
). Add support for custom-logo
and optionally create Customizer settings:
function cv_customize( $wp_customize ) {
$wp_customize->add_section( 'cv_header' , array( 'title' => __( 'Header', 'my-custom-theme' ) ) );
$wp_customize->add_setting( 'cv_phone', array( 'default' => '' ) );
$wp_customize->add_control( 'mct_phone', array(
'label' => __( 'Contact Phone', 'my-custom-theme' ),
'section' => 'cv_header',
'type' => 'text',
) );
}
add_action( 'customize_register', 'cv_customize' );
Step 8: Internationalization (i18n)
Make strings translatable using __()
and _e()
with your textdomain.
- Wrap strings in
__( 'Text', 'my-custom-theme' )
- Add
load_theme_textdomain( 'my-custom-theme', get_template_directory() . '/languages' );
inafter_setup_theme
. - Provide
.pot
files and translations inlanguages/
.
Step 9: Security & escaping
Always escape output and sanitize inputs:
esc_html()
,esc_attr()
,esc_url()
,wp_kses_post()
for HTML.- Use
wp_nonce_field()
andcheck_admin_referer()
in forms. - Use prepared statements when querying DB directly (avoid direct
$_GET
into SQL).
Example:
echo '<a href="'. esc_url( get_permalink() ) .'">'. esc_html( get_the_title() ) .'</a>';
Step 10: Debugging & development tips
- Enable
WP_DEBUG
inwp-config.php
:
define( 'WP_DEBUG', true );
define( 'WP_DEBUG_LOG', true );
define( 'WP_DEBUG_DISPLAY', false );
- Use Query Monitor plugin for performance and DB queries.
- Inspect markup with browser devtools and Lighthouse.
Conclusion
Creating a WordPress theme is rewarding and gives you control over site presentation and performance. Start small: scaffold a minimal theme, then iterate — add templates, widgets, customization, accessibility, and finally polish with build tools and testing. If your project is an eCommerce store or product-heavy site, consider separating UI (theme) from business logic (plugin) — keep search, recommendation, and product logic inside plugins for portability.
No Comment Found Yet !