Skip to content

WordPress Theme Anatomy — Template Files, style.css and functions.php Explained

DodaTech Updated 2026-06-27 11 min read

In this tutorial, you'll learn the anatomy of a WordPress theme, including template files, style.css, functions.php, and the template hierarchy that powers WordPress theme development.

What You'll Learn

  • The required files every WordPress theme needs (style.css and index.php)
  • How style.css registers a theme (Theme Name header and metadata)
  • What functions.php does (enqueue scripts, theme support, custom functions)
  • The role of template files (header.php, footer.php, sidebar.php, index.php)
  • Template tags like get_header(), get_footer(), get_sidebar()
  • How The Loop works (while have_posts(): the_post())
  • The purpose of the template parts folder
  • The difference between theme root and child theme
  • How WordPress renders a page from request to output

Why It Matters

A WordPress theme is the foundation of your site's appearance. Without understanding its anatomy, you cannot customize layouts, fix display issues, or build your own themes. Knowing how template files work together, what each file does, and how WordPress chooses which file to load gives you complete control over your site's front end. This knowledge separates a user who installs themes from a developer who crafts them.

Real-World Use

When a client asks for a custom homepage layout, a different sidebar on blog posts, or a unique footer per page, you need to understand theme anatomy. For example, creating a landing page template requires copying page.php, renaming it, adding a template name header, and modifying the markup. Without knowing how templates load and inherit, you would be stuck modifying index.php for every page type.

Learning Path

flowchart LR
  A[Theme Basics] --> B[Theme Anatomy]
  B --> C[Installing Themes]
  C --> D[Full Site Editing]
  D --> E[Customizer]
  E --> F[Widgets]
  F --> G[Menus]
  G --> H[Child Themes]
  H --> I[Template Hierarchy]
  I --> J[CSS Customization]
  style B fill:#4a90d9,color:#fff

What Is a WordPress Theme?

A WordPress theme is a collection of files that controls the visual presentation and layout of your website. Think of it as the skin of your site — it determines how content looks, where elements appear, and how users navigate.

Technically, a theme lives in wp-content/themes/your-theme-name/. Every theme needs at least two files:

  1. style.css — The theme's identity card and stylesheet
  2. index.php — The fallback template (if no other template matches)

Without these two files, WordPress will not recognize your theme.

The style.css File

The style.css file serves two purposes:

  1. It tells WordPress the theme exists (via the comment header)
  2. It contains the theme's CSS styles

The header comment at the top of style.css is required. It must follow this exact format:

/*
Theme Name: My Custom Theme
Theme URI: https://example.com/my-custom-theme
Author: Your Name
Author URI: https://example.com
Description: A custom WordPress theme built for learning.
Version: 1.0.0
License: GPL v2 or later
Text Domain: my-custom-theme
*/

WordPress reads this header to display theme information in Appearance > Themes. Only Theme Name is strictly required, but you should always include all fields for proper identification.

Minimum style.css

/*
Theme Name: My Theme
*/

That is technically enough for WordPress to recognize your theme. But you also want your CSS to load, which brings us to functions.php.

The functions.php File

The functions.php file is a theme's brain. It runs on every page load and lets you add PHP features, enqueue styles and scripts, register widgets, and add theme support.

Unlike template files that output HTML, functions.php never outputs anything directly. It hooks into WordPress actions and filters.

Enqueuing Styles and Scripts

Never hardcode <link> or <script> tags in header.php. Always use wp_enqueue_style() and wp_enqueue_script() in functions.php:

<?php
function my_theme_scripts() {
    wp_enqueue_style( 'my-theme-style', get_stylesheet_uri(), array(), '1.0.0' );
    wp_enqueue_script( 'my-theme-navigation', get_template_directory_uri() . '/js/navigation.js', array(), '1.0.0', true );
}
add_action( 'wp_enqueue_scripts', 'my_theme_scripts' );

The third parameter of wp_enqueue_style() is an array of dependencies. The fourth is version (change it to bust cache). The fifth for scripts is whether to load in footer (true is almost always better for performance).

Adding Theme Support

Theme support enables WordPress features that are opt-in:

function my_theme_setup() {
    add_theme_support( 'post-thumbnails' );
    add_theme_support( 'custom-logo' );
    add_theme_support( 'html5', array( 'search-form', 'comment-form', 'comment-list', 'gallery', 'caption' ) );
    add_theme_support( 'title-tag' );
    add_theme_support( 'customize-selective-refresh-widgets' );
}
add_action( 'after_setup_theme', 'my_theme_setup' );

Common theme support features include post-thumbnails (featured images), custom-logo, title-tag (WordPress manages ), and html5.</p> <h2 id="template-files-overview">Template Files Overview</h2><p>Template files are PHP files that output HTML. Each template handles a specific part of the page.</p> <h3 id="headerphp">header.php</h3><p>Contains everything from <code><DOCTYPE html></code> to the opening <code><main></code> tag or closing <code></header></code>:</p> <div class="highlight"><pre style="line-height: 125%;"><span></span><!DOCTYPE html> <html <span style="color: #007020"><?php</span> language_attributes(); <span style="color: #007020">?></span>> <head> <meta charset="<span style="color: #007020"><?php</span> bloginfo( <span style="color: #4070a0">'charset'</span> ); <span style="color: #007020">?></span>"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <span style="color: #007020"><?php</span> wp_head(); <span style="color: #007020">?></span> </head> <body <span style="color: #007020"><?php</span> body_class(); <span style="color: #007020">?></span>> <span style="color: #007020"><?php</span> wp_body_open(); <span style="color: #007020">?></span> <header id="masthead" class="site-header"> <div class="site-branding"> <span style="color: #007020"><?php</span> the_custom_logo(); <span style="color: #007020">?></span> <h1 class="site-title"><a href="<span style="color: #007020"><?php</span> <span style="color: #007020; font-weight: bold">echo</span> esc_url( home_url( <span style="color: #4070a0">'/'</span> ) ); <span style="color: #007020">?></span>"><span style="color: #007020"><?php</span> bloginfo( <span style="color: #4070a0">'name'</span> ); <span style="color: #007020">?></span></a></h1> </div> </header> </pre></div> <p>Note <code><?php wp_head(); ?></code> — this action hook is required for plugins and WordPress core to inject scripts and styles. Forgetting it breaks almost everything.</p> <h3 id="footerphp">footer.php</h3><p>Contains the closing content and <code><?php wp_footer(); ?></code>:</p> <div class="highlight"><pre style="line-height: 125%;"><span></span><footer id="colophon" class="site-footer"> <div class="site-info"> <a href="<span style="color: #007020"><?php</span> <span style="color: #007020; font-weight: bold">echo</span> esc_url( __( <span style="color: #4070a0">'https://wordpress.org/'</span>, <span style="color: #4070a0">'textdomain'</span> ) ); <span style="color: #007020">?></span>"><span style="color: #007020"><?php</span> <span style="color: #007020">printf</span>( esc_html__( <span style="color: #4070a0">'Proudly powered by %s'</span>, <span style="color: #4070a0">'textdomain'</span> ), <span style="color: #4070a0">'WordPress'</span> ); <span style="color: #007020">?></span></a> </div> </footer> <span style="color: #007020"><?php</span> wp_footer(); <span style="color: #007020">?></span> </body> </html> </pre></div> <p><code><?php wp_footer(); ?></code> is also required — many plugins depend on it to enqueue footer scripts.</p> <h3 id="sidebarphp">sidebar.php</h3><p>Contains sidebar markup and calls <code>dynamic_sidebar()</code>:</p> <div class="highlight"><pre style="line-height: 125%;"><span></span><aside id="secondary" class="widget-area"> <span style="color: #007020"><?php</span> <span style="color: #007020; font-weight: bold">if</span> ( is_active_sidebar( <span style="color: #4070a0">'sidebar-1'</span> ) ) <span style="color: #666666">:</span> <span style="color: #007020">?></span> <span style="color: #007020"><?php</span> dynamic_sidebar( <span style="color: #4070a0">'sidebar-1'</span> ); <span style="color: #007020">?></span> <span style="color: #007020"><?php</span> <span style="color: #007020; font-weight: bold">endif</span>; <span style="color: #007020">?></span> </aside> </pre></div> <h3 id="indexphp">index.php</h3><p>The main template. It is the fallback — if no other template matches the current request, index.php is used. It typically contains The Loop:</p> <div class="highlight"><pre style="line-height: 125%;"><span></span><span style="color: #007020"><?php</span> <span style="color: #007020; font-weight: bold">if</span> ( have_posts() ) <span style="color: #666666">:</span> <span style="color: #007020; font-weight: bold">while</span> ( have_posts() ) <span style="color: #666666">:</span> the_post(); <span style="color: #007020">?></span> <article id="post-<span style="color: #007020"><?php</span> the_ID(); <span style="color: #007020">?></span>" <span style="color: #007020"><?php</span> post_class(); <span style="color: #007020">?></span>> <h2><a href="<span style="color: #007020"><?php</span> the_permalink(); <span style="color: #007020">?></span>"><span style="color: #007020"><?php</span> the_title(); <span style="color: #007020">?></span></a></h2> <div class="entry-content"> <span style="color: #007020"><?php</span> the_content(); <span style="color: #007020">?></span> </div> </article> <span style="color: #007020"><?php</span> <span style="color: #007020; font-weight: bold">endwhile</span>; the_posts_navigation(); <span style="color: #007020; font-weight: bold">else</span> <span style="color: #666666">:</span> <span style="color: #007020">?></span> <p><span style="color: #007020"><?php</span> esc_html_e( <span style="color: #4070a0">'No content found.'</span>, <span style="color: #4070a0">'textdomain'</span> ); <span style="color: #007020">?></span></p> <span style="color: #007020"><?php</span> <span style="color: #007020; font-weight: bold">endif</span>; <span style="color: #007020">?></span> </pre></div> <h2 id="template-tags">Template Tags</h2><p>Template tags are PHP functions WordPress provides to output or retrieve data. They are the building blocks of template files.</p> <h3 id="get_header-get_footer-get_sidebar">get_header(), get_footer(), get_sidebar()</h3><p>These include the corresponding template file. They are how templates stay modular:</p> <div class="highlight"><pre style="line-height: 125%;"><span></span>get_header(); // includes header.php get_footer(); // includes footer.php get_sidebar(); // includes sidebar.php </pre></div> <p>You can also pass a slug: <code>get_header( 'page' )</code> includes <code>header-page.php</code>.</p> <h3 id="content-template-tags">Content Template Tags</h3><div class="highlight"><pre style="line-height: 125%;"><span></span>the_title() // outputs post title the_content() // outputs post content (with page breaks) the_permalink() // outputs post URL the_excerpt() // outputs post excerpt the_category() // outputs category links the_tags() // outputs tag links the_author() // outputs author name the_date() // outputs post date the_post_thumbnail() // outputs featured image the_ID() // outputs post ID post_class() // outputs CSS classes for the post body_class() // outputs CSS classes for the body </pre></div> <h2 id="the-loop">The Loop</h2><p>The Loop is the core pattern in WordPress templates. It fetches posts from the database and displays them:</p> <div class="highlight"><pre style="line-height: 125%;"><span></span><span style="color: #007020"><?php</span> <span style="color: #007020; font-weight: bold">if</span> ( have_posts() ) <span style="color: #666666">:</span> <span style="color: #007020; font-weight: bold">while</span> ( have_posts() ) <span style="color: #666666">:</span> the_post(); <span style="color: #60a0b0; font-style: italic">// Display post content here</span> <span style="color: #007020; font-weight: bold">endwhile</span>; <span style="color: #007020; font-weight: bold">else</span> <span style="color: #666666">:</span> <span style="color: #60a0b0; font-style: italic">// No posts found</span> <span style="color: #007020; font-weight: bold">endif</span>; </pre></div> <p><code>have_posts()</code> checks if there are posts to display. <code>the_post()</code> sets up the global <code>$post</code> object and makes template tags like <code>the_title()</code> work.</p> <p>Every page that displays posts must use The Loop. This includes blog index, category archives, search results, and single posts.</p> <h2 id="template-parts-folder">Template Parts Folder</h2><p>Many modern themes organize reusable pieces into a <code>/template-parts/</code> folder:</p> <pre><code>theme/ template-parts/ header/ header-logo.php header-nav.php content/ content-post.php content-page.php content-none.php footer/ footer-widgets.php footer-credits.php </code></pre><p>Use <code>get_template_part()</code> to include them:</p> <div class="highlight"><pre style="line-height: 125%;"><span></span>get_template_part( 'template-parts/content/content', 'post' ); // Looks for template-parts/content/content-post.php </pre></div> <p>This keeps template files DRY (Don't Repeat Yourself) and makes customization easier.</p> <h2 id="theme-root-vs-child-theme">Theme Root vs Child Theme</h2><p>The <strong>theme root</strong> is <code>wp-content/themes/</code>. Each theme is a subdirectory:</p> <pre><code>wp-content/themes/ twenty-twenty-four/ (parent theme) my-child-theme/ (child theme, requires parent) my-custom-theme/ (standalone theme) </code></pre><p>A <strong>child theme</strong> inherits a parent theme's functionality while allowing overrides. It must have a <code>Template:</code> header in style.css pointing to the parent theme folder name. Child themes are the safest way to customize a third-party theme because updates to the parent do not overwrite your changes.</p> <p><a href="/cms/wordpress/">WordPress</a> child themes are covered in depth in a later tutorial, but the key concept is: the child's style.css loads after the parent's CSS, and if you copy a template file from the parent into the child with the same path, WordPress uses the child's version.</p> <h2 id="how-wordpress-renders-a-page">How WordPress Renders a Page</h2><p>Understanding the full request lifecycle helps you debug issues:</p> <ol> <li><strong>index.php</strong> — The front controller. All requests go through <code>index.php</code> in the WordPress root (not theme).</li> <li><strong>wp-blog-header.php</strong> — Loads WordPress core by including <code>wp-load.php</code>.</li> <li><strong>wp()</strong> — Sets up the main <code>$wp_query</code> object. This parses the URL, runs the query, and determines what type of page is being viewed.</li> <li><strong>Template Loader</strong> — Based on <code>$wp_query</code>, WordPress runs the template hierarchy to determine which template file to load. It calls <code>get_template_part()</code> or <code>include()</code> on the matched file.</li> <li><strong>Template executes</strong> — The template file calls <code>get_header()</code>, runs The Loop, displays content, calls <code>get_sidebar()</code> or <code>get_footer()</code>.</li> <li><strong>Shutdown</strong> — WordPress finishes, runs shutdown hooks, sends the response.</li> </ol> <p>In code terms:</p> <div class="highlight"><pre style="line-height: 125%;"><span></span>// In wp-blog-header.php (simplified) require_once __DIR__ . '/wp-load.php'; wp(); require_once ABSPATH . WPINC . '/template-loader.php'; </pre></div> <p><code>template-loader.php</code> is where the magic happens. It calls <code>get_template_part()</code> with the correct template based on the hierarchy.</p> <h2 id="required-theme-file-checklist">Required Theme File Checklist</h2><p>Add this checklist to verify your theme structure before uploading:</p> <pre><code>my-theme/ style.css [REQUIRED - theme header + styles] index.php [REQUIRED - fallback template] functions.php [OPTIONAL but recommended] header.php [OPTIONAL but standard] footer.php [OPTIONAL but standard] sidebar.php [OPTIONAL] screenshot.png [OPTIONAL - 1200x900 display in admin] template-parts/ [OPTIONAL - organized partials] </code></pre><h2 id="common-mistakes">Common Mistakes</h2><ol> <li><p><strong>Forgetting wp_head() or wp_footer()</strong> — This breaks plugin functionality, script enqueuing, and admin bar display. Always include them in header.php and footer.php.</p> </li> <li><p><strong>Hardcoding stylesheet links</strong> — Using <code><link rel="stylesheet" href="style.css"></code> instead of <code>wp_enqueue_style()</code> prevents <a href="/system-design/caching/">Caching</a>, dependency management, and child theme overrides.</p> </li> <li><p><strong>Using the_title() or the_content() outside The Loop</strong> — These template tags only work after <code>the_post()</code> has been called. Using them outside The Loop returns empty values.</p> </li> <li><p><strong>Missing text domain in translation functions</strong> — Functions like <code>__()</code>, <code>_e()</code>, <code>esc_html_e()</code> need a text domain as the second parameter matching the Text Domain in style.css.</p> </li> <li><p><strong>Editing theme files directly</strong> — Editing a third-party theme's files causes lost changes on update. Always use a child theme or create a custom theme.</p> </li> </ol> <h2 id="practice-questions">Practice Questions</h2><ol> <li><p>What are the two required files for any WordPress theme, and what does each one do?</p> </li> <li><p>Why must you use <code>wp_enqueue_style()</code> in functions.php instead of a hardcoded <code><link></code> tag in header.php?</p> </li> <li><p>What is the purpose of the <code>after_setup_theme</code> action hook, and what common features do you enable with <code>add_theme_support()</code>?</p> </li> </ol> <p><strong>Challenge</strong>: Create a minimal WordPress theme with just style.css, index.php, functions.php, header.php, and footer.php. Enqueue one stylesheet, add support for post-thumbnails and title-tag, and ensure the header contains wp_head() and the footer contains wp_footer(). Use get_header() and get_footer() in index.php. Test the theme activates without errors.</p> <h2 id="faq">FAQ</h2><details style="margin-bottom:12px;border:1px solid #e2e8f0;border-radius:10px;overflow:hidden"><summary style="cursor:pointer;padding:14px 18px;font-weight:600;font-size:1.05rem;background:#f8fafc;border-bottom:1px solid #e2e8f0;color:#1e293b">What happens if my theme has no index.php?</summary><div style="padding:14px 18px;color:#475569;line-height:1.7;background:#fff"><p>WordPress requires index.php for a theme to be valid. Without it, the theme does not appear in the admin panel. If you accidentally delete it, upload a new copy via FTP.</p> </div></details> <details style="margin-bottom:12px;border:1px solid #e2e8f0;border-radius:10px;overflow:hidden"><summary style="cursor:pointer;padding:14px 18px;font-weight:600;font-size:1.05rem;background:#f8fafc;border-bottom:1px solid #e2e8f0;color:#1e293b">Can I use multiple functions.php files?</summary><div style="padding:14px 18px;color:#475569;line-height:1.7;background:#fff"><p>No — a theme can only have one functions.php. To organize code, use <code>require_once()</code> or <code>include_once()</code> to split logic into separate PHP files in a subfolder (e.g., <code>/inc/customizer.php</code>).</p> </div></details> <details style="margin-bottom:12px;border:1px solid #e2e8f0;border-radius:10px;overflow:hidden"><summary style="cursor:pointer;padding:14px 18px;font-weight:600;font-size:1.05rem;background:#f8fafc;border-bottom:1px solid #e2e8f0;color:#1e293b">How do I add custom page templates?</summary><div style="padding:14px 18px;color:#475569;line-height:1.7;background:#fff"><p>Create a new PHP file in your theme (e.g., <code>tpl-landing.php</code>). At the top, add a comment: <code><?php /* Template Name: Landing Page */ ?></code>. Then assign this template via the Page edit screen under Page Attributes > Template.</p> </div></details> <h2 id="mini-project">Mini Project</h2><p>Build a mini-theme called "Anatomy Basics" with exactly five files:</p> <ol> <li><code>style.css</code> — With full theme header (Theme Name: Anatomy Basics, Author: Your Name, Version: 1.0, Text Domain: anatomy-basics)</li> <li><code>functions.php</code> — Enqueue style.css using <code>wp_enqueue_style()</code>, enable post-thumbnails and title-tag support</li> <li><code>header.php</code> — DOCTYPE, <code><head></code> with <code>wp_head()</code>, opening <code><body></code> with <code>body_class()</code>, site title linked to home</li> <li><code>footer.php</code> — <code>wp_footer()</code> and closing tags</li> <li><code>index.php</code> — <code>get_header()</code>, The Loop (display titles linked to permalinks), <code>get_footer()</code></li> </ol> <p>Activate this theme. Create a test post. Verify the title appears, the page renders without PHP errors, and the admin bar works. Add a child theme that overrides index.php to add a "Hello from child" message above the post title.</p> <h2 id="whats-next">What's Next</h2><p>Now that you understand theme anatomy, learn how to install and manage themes effectively. Then explore Full Site Editing for modern block-based themes. For deeper customization, study the template hierarchy to know exactly which file WordPress loads for each page type.</p> </div> <div class="hx:mt-16"></div> <div class="hx:mb-8 hx:grid hx:grid-cols-2 hx:gap-4 not-prose"> <a href="/cms/wordpress/16-comments-discussion/" class="hx:flex hx:flex-col hx:rounded-xl hx:border hx:border-gray-200 hx:p-4 hx:hover:border-primary-500 hx:transition-colors"> <span class="hx:text-xs hx:text-gray-500 hx:flex hx:items-center hx:gap-1">← Previous</span> <span class="hx:text-sm hx:font-medium hx:text-gray-700 hx:mt-1">WordPress Comments and Discussion — Complete Guide to Moderation, Spam and Engagement</span> </a> <a href="/cms/wordpress/18-installing-themes/" class="hx:flex hx:flex-col hx:rounded-xl hx:border hx:border-gray-200 hx:p-4 hx:hover:border-primary-500 hx:transition-colors hx:text-right"> <span class="hx:text-xs hx:text-gray-500 hx:flex hx:items-center hx:gap-1 hx:justify-end">Next →</span> <span class="hx:text-sm hx:font-medium hx:text-gray-700 hx:mt-1">WordPress Themes — How to Install, Manage and Switch Themes Safely</span> </a> </div> <div class="not-prose hx:mt-12 hx:p-6 hx:rounded-xl hx:border" style="background:linear-gradient(135deg,#fef2f2,#ffe4e6);border-color:#fecaca"> <div class="hx:flex hx:items-center hx:gap-3 hx:mb-3"> <div class="hx:w-10 hx:h-10 hx:rounded-lg hx:flex hx:items-center hx:justify-center hx:shrink-0" style="background:#ef4444;color:white"> <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/></svg> </div> <div> <p class="hx:text-sm hx:font-semibold hx:m-0" style="color:#991b1b">Built by the developers of DodaTech</p> <p class="hx:text-xs hx:m-0" style="color:#b91c1c">Doda Browser, DodaZIP & Durga Antivirus Pro</p> </div> </div> <div class="hx:flex hx:flex-wrap hx:gap-2 hx:mt-3"> <a href="/" class="hx:inline-flex hx:items-center hx:rounded-full hx:px-3 hx:py-1.5 hx:text-xs hx:font-medium hx:transition-all" style="background:#fff;color:#dc2626;border:1px solid #fecaca">Home</a> <a href="/cms/wordpress/" class="hx:inline-flex hx:items-center hx:rounded-full hx:px-3 hx:py-1.5 hx:text-xs hx:font-medium hx:transition-all" style="background:#fff;color:#dc2626;border:1px solid #fecaca">Browse Wordpress</a> <button onclick="window.print()" class="hx:inline-flex hx:items-center hx:rounded-full hx:px-3 hx:py-1.5 hx:text-xs hx:font-medium hx:cursor-pointer" style="background:#fff;color:#dc2626;border:1px solid #fecaca">Print</button> </div> </div> </main> </article> </div> <footer class="hextra-footer hx:bg-gray-100 hx:pb-[env(safe-area-inset-bottom)] hx:dark:bg-neutral-900 hx:print:bg-transparent"> <div class="hextra-custom-footer hextra-max-footer-width hx:mx-auto hx:pl-[max(env(safe-area-inset-left),1.5rem)] hx:pr-[max(env(safe-area-inset-right),1.5rem)] hx:text-gray-600 hx:dark:text-gray-400 hx:py-12"> <div class="hx:text-center hx:text-sm"> Built by the developers of <strong>Doda Browser</strong>, <strong>DodaZIP</strong>, and <strong>Durga Antivirus Pro</strong>.<br/> <span class="hx:text-xs">© 2026 DodaTech. All rights reserved.</span> </div> </div> </footer> <script defer src="/js/main.js"></script> <script defer src="https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.min.js"></script> <script>document.addEventListener('DOMContentLoaded',function(){mermaid.initialize({startOnLoad:true,theme:'neutral'})});</script> </body> </html>