Template anatomy: WordPress Page

How theme files are assembled when a visitor loads a static page · page.php

When WordPress serves a page it starts with index.php, which calls wp_head() — pulling in header.php. The matching template (page.php) retrieves content from the database. footer.php closes the document. functions.php underpins the whole site.

header.php via get_header()
<!DOCTYPE html>
<html <?php language_attributes();?>>
<head>
<meta charset='UTF-8'>
<meta name='viewport' ...>
<?php wp_head(); ?>
</head>
<body <?php body_class();?>>
<div id='wrapper'>
<header id='masthead'>
<h1 id='site-title'>
<?php bloginfo('name'); ?>
</h1>
<?php wp_nav_menu([
'theme_location'=>'primary'
]); ?>
</header>
page.php single page + loop
<?php get_header(); ?>
<main id='content'>
<div id='primary'>
<?php if (have_posts()) : ?>
<?php while (have_posts()) :
the_post(); ?>
<article <?php post_class(); ?>>
<h1>
<?php the_title(); ?>
</h1>
<div class='entry-content'>
<?php the_content(); ?>
</div>
</article>
<?php endwhile; ?>
<?php endif; ?>
</div>
</main>
footer.php closes page + wp_footer()
<footer id='colophon'>
<div class='sidebar'>
<?php dynamic_sidebar('sidebar-1'); ?>
</div>
<p class='site-info'>
<?php bloginfo('name'); ?>
</p>
</footer>
</div><!-- #wrapper -->
<?php wp_footer(); ?>
</body></html>
functions.phpregisters scripts, enqueues fonts & JS, theme support, navigation menus, widget areas, hooks
WordPress assembles
Assembled HTML in browser HTML output
<body class='page page-id-2 page-template-default'> ← body_class()
<div id='wrapper'>
<header id='masthead'> ← header.php
<h1 id='site-title'>My Site</h1>
<nav><ul><li><a href='/'>Home</a></li>…</ul></nav>
</header>
<main id='content'> ← page.php
<article class='page type-page'>
<h1>About Us</h1> ← the_title()
<div class='entry-content'>
<p>Welcome to our site…</p> ← the_content()
</div>
</article>
</main>
<footer id='colophon'> ← footer.php
<div class='sidebar'>…</div>
</footer>
</div>
</body>

Template anatomy: Category / Archive

category.php — The Loop outputs multiple posts using the_excerpt()

A category page lists multiple posts. WordPress checks the template hierarchy and uses category.php if present. The Loop runs once per post, outputting a title, date and excerpt for each. Note the_excerpt() — not the full content.

category-{slug}.php category-{id}.php category.php archive.php index.php ← WP checks in order, uses first match
category.php loops once per post
<?php get_header(); ?>
<main id='content'>
<h1><?php single_cat_title(); ?></h1>
<?php if ( have_posts() ) : ?>
<?php while ( have_posts() ) :
the_post(); ?>
<article <?php post_class(); ?>>
<h2>
<a href='<?php the_permalink();?>'>
<?php the_title(); ?></a>
</h2>
<p><?php the_date(); ?></p>
<?php the_excerpt(); ?>
</article>
<?php endwhile; ?>
<?php endif; ?>
</main>
<?php get_footer(); ?>

The Loop

Runs once per post in this category. Each iteration outputs one <article>.

  • get_header()loads header.php
  • single_cat_title()category name
  • have_posts()checks if posts remain
  • the_post()advances pointer, sets global post
  • the_permalink()post URL
  • the_title()post title from database
  • the_date()publication date
  • the_excerpt()summary — NOT the full content
  • get_footer()loads footer.php
outputs
Assembled HTML — category page (two posts) HTML output
<main id='content'>
<h1>Category: Tutorial</h1> ← single_cat_title()
<!-- Loop iteration 1 ────────────────────────── -->
<article class='post post-5 category-tutorial hentry'>
<h2><a href='/tutorial-files/'>Tutorial Files</a></h2> ← the_title()
<p>April 28, 2020</p> ← the_date()
<p>This tutorial walks you through…</p> ← the_excerpt()
</article>
<!-- Loop iteration 2 ────────────────────────── -->
<article class='post post-4 category-tutorial hentry'>
<h2><a href='/part-1/'>Part 1: The Design Prototype</a></h2>
<p>April 20, 2020</p>
<p>We take a look at the design…</p>
</article>
</main>

Template anatomy: Single Post

single.php — full content, meta, taxonomy, navigation, comments

A single post uses single.php. Like page.php it calls the_content() — but it also includes date/author meta, taxonomy terms, previous/next navigation, and comments_template(). The Loop runs but iterates only once.

single.php full post + comments
<?php get_header(); ?>
<main id='content'>
<?php if (have_posts()) : ?>
<?php while (have_posts()) :
the_post(); ?>
<article <?php post_class(); ?>>
<!-- ① post meta -->
<h1><?php the_title(); ?></h1>
<p><?php the_date(); ?>
· <?php the_author(); ?></p>
<!-- ② full content -->
<div class='entry-content'>
<?php the_content(); ?>
</div>
<!-- ③ categories & tags -->
<footer class='entry-footer'>
<?php the_category(', '); ?>
<?php the_tags(); ?>
</footer>
<!-- ④ prev/next nav -->
<?php previous_post_link(); ?>
<?php next_post_link(); ?>
</article>
<?php endwhile; ?>
<?php endif; ?>
</main>
<!-- ⑤ comments -->
<?php comments_template(); ?>
<?php get_footer(); ?>
  • Post meta

    • the_title() — title
    • the_date() — date
    • the_author() — author
  • Full content

    • the_content() — full body
    • Contrast: the_excerpt() in category.php
  • Taxonomy

    • the_category() — categories
    • the_tags() — tags
  • Post navigation

    • previous_post_link()
    • next_post_link()
  • Comments

    • comments_template() loads comments.php — form + list
HTML prototypeWordPress theme
<h1>Post Title</h1><?php the_title(); ?>
<p>13 Apr 2020</p><?php the_date(); ?>
<p>by Prisca</p><?php the_author(); ?>
<!-- body text --><?php the_content(); ?>
<p class='cats'>…</p><?php the_category(); ?>
<p class='tags'>…</p><?php the_tags(); ?>
<!-- prev/next --><?php previous/next_post_link(); ?>
<!-- comment form --><?php comments_template(); ?>

Template anatomy: 404 Error Page

404.php — displayed whenever WordPress cannot find a matching post or page

When a URL returns no content, WordPress loads 404.php. There is no Loop — there is no post to retrieve. Instead the template displays a static message and typically includes a search form so the visitor can try again. The form uses WordPress's built-in get_search_form() or a hand-coded <form> with action='/' and input[name=s].

404.php index.php ← only two options; 404.php is the dedicated template
404.php no loop — static message + search
<?php get_header(); ?>
<main id='content'>
<section class='error-404'>
<!-- ① static message — no loop -->
<h1>Page Not Found</h1>
<div class='page-content'>
<p>Nothing found at this address.</p>
<p>Try a search:</p>
<!-- ② search form -->
<?php get_search_form(); ?>
<!-- renders: -->
<form role='search' method='get' action='<?php echo esc_url(home_url('/')); ?>'>
<label for='s'>Search:</label>
<input type='search' id='s' name='s' value='<?php echo esc_attr( get_search_query() ); ?>'>
<button type='submit'>Go</button>
</form>
<!-- ③ optional: recent posts -->
<?php the_widget('WP_Widget_Recent_Posts'); ?>
</div>
</section>
</main>
<?php get_footer(); ?>
  • Static message

    • No Loop here — there is no post to retrieve
    • Hardcoded <h1> and explanation text
    • Body class will include error404
  • Search form

    • get_search_form() loads searchform.php if it exists, otherwise outputs the default WP form
    • Form posts to site root with name="s" — WP's search query parameter
    • the_search_query() pre-fills the field
  • Optional extras

    • the_widget() can add recent posts, categories, or tag cloud to help the visitor
outputs
Assembled HTML — 404 page HTML output
<body class='error404'> ← body_class() adds error404
<header id='masthead'>…</header> ← header.php
<main id='content'> ← 404.php
<section class='error-404'>
<h1>Page Not Found</h1> ← static, no WP function
<p>Nothing found at this address.</p>
<form role='search' method='get' action='/'> ← get_search_form()
<label for='s'>Search:</label>
<input type='search' name='s' value=''> ← name="s" is WP's search param
<button type='submit'>Go</button>
</form>
</section>
</main>
<footer id='colophon'>…</footer> ← footer.php
</body>

Template anatomy: Search Results

search.php — displays results for a visitor's query using the Loop

When a visitor submits a search (via the ?s= query parameter), WordPress loads search.php. Like category.php it uses the Loop to output multiple matching posts, each with title, date and excerpt. It also handles the empty-results case when no posts match.

search.php index.php ← search results have their own dedicated template
search.php search results + loop
<?php get_header(); ?>
<main id='content'>
<!-- ① search heading -->
<h1>Search results for:
<?php the_search_query(); ?></h1>
<!-- ② repeat search form -->
<?php get_search_form(); ?>
<?php if ( have_posts() ) : ?>
<?php while ( have_posts() ) :
the_post(); ?>
<article <?php post_class(); ?>>
<h2>
<a href='<?php the_permalink();?>'>
<?php the_title(); ?></a>
</h2>
<p><?php the_date(); ?></p>
<?php the_excerpt(); ?>
</article>
<?php endwhile; ?>
<!-- ③ no results fallback -->
<?php else : ?>
<p>No results found for
<?php the_search_query(); ?>.</p>
<?php get_search_form(); ?>
<?php endif; ?>
</main>
<?php get_footer(); ?>

The Loop + no-results fallback

Runs once per matching post. If no posts match, the else branch shows a message and a fresh search form.

  • get_header()loads header.php
  • the_search_query()outputs the search term (sanitised)
  • get_search_form()renders search form (repeat at top)
  • have_posts()true if any results found
  • the_post()advances loop pointer
  • the_permalink()link to full result
  • the_title()result post title
  • the_date()publication date
  • the_excerpt()content snippet
  • else / endifno-results fallback branch
  • get_footer()loads footer.php
outputs
Assembled HTML — search results page HTML output
<body class='search search-results'> ← body_class()
<main id='content'> ← search.php
<h1>Search results for: wordpress</h1> ← the_search_query()
<form role='search' method='get' action='/'>…</form> ← get_search_form()
<!-- Loop iteration 1 ───────────────────── -->
<article class='post hentry'>
<h2><a href='/part-2/'>Part 2: The Vanilla Theme</a></h2> ← the_title()
<p>April 13, 2020</p> ← the_date()
<p>Let's have a look at the vanilla theme…</p> ← the_excerpt()
</article>
<!-- OR if no results ───────────────────── -->
<p>No results found for: xyz</p>
<form role='search'>…</form> ← get_search_form() again
</main>