Main Template Files
Learning Objectives
- Master the core template files in WordPress themes
- Understand the purpose and structure of each main template
- Learn how to properly implement header.php and footer.php
- Create effective sidebar.php templates
- Build specialized templates for different content types
- Implement proper WordPress hooks in templates
Introduction
Every WordPress theme is built from a collection of template files that work together like pieces of a puzzle. Each file has a specific role - think of them as specialized workers in a factory, each responsible for a particular part of the final product.
Template File Overview
Let's visualize how template files come together to create a complete page:
Site Header
Logo, Navigation, Search
Main Content Area
The Loop, Posts, Pages
The index.php File
The index.php is the most important template file - it's the ultimate fallback that must exist in every theme:
index.php REQUIRED
Purpose: The main template file and ultimate fallback for all content
Used for: Any content when no more specific template exists
Must include: The Loop, proper HTML structure
<?php
/**
* The main template file
*
* @package My_Theme
*/
get_header(); ?>
<div id="primary" class="content-area">
<main id="main" class="site-main">
<?php
if ( have_posts() ) :
// Check what type of content we're displaying
if ( is_home() && ! is_front_page() ) :
?>
<header>
<h1 class="page-title screen-reader-text">
<?php single_post_title(); ?>
</h1>
</header>
<?php
endif;
// Start the Loop
while ( have_posts() ) :
the_post();
/*
* Include the Post-Type-specific template for the content.
* If you want to override this in a child theme, then include a file
* called content-___.php (where ___ is the Post Type name) and that
* will be used instead.
*/
get_template_part( 'template-parts/content', get_post_type() );
endwhile;
// Navigation
the_posts_navigation();
else :
// No content found
get_template_part( 'template-parts/content', 'none' );
endif;
?>
</main>
</div>
<?php
get_sidebar();
get_footer();
The header.php File
The header.php contains everything from the DOCTYPE to the opening of your main content area:
Anatomy of header.php
1. DOCTYPE and Opening Tags
HTML5 DOCTYPE, language attributes, and opening html/head tags
2. Head Section
Meta tags, title, stylesheets (via wp_head())
3. Body Opening
Body tag with body_class(), site header, navigation
4. wp_body_open()
Hook for plugins/themes to inject code after body tag
<?php
/**
* The header for our theme
*
* @package My_Theme
*/
?>
<!DOCTYPE html>
<html <?php language_attributes(); ?>>
<head>
<meta charset="<?php bloginfo( 'charset' ); ?>">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="profile" href="https://gmpg.org/xfn/11">
<?php wp_head(); ?>
</head>
<body <?php body_class(); ?>>
<?php wp_body_open(); ?>
<div id="page" class="site">
<a class="skip-link screen-reader-text" href="#primary">
<?php esc_html_e( 'Skip to content', 'my-theme' ); ?>
</a>
<header id="masthead" class="site-header">
<div class="site-branding">
<?php
the_custom_logo();
if ( is_front_page() && is_home() ) :
?>
<h1 class="site-title">
<a href="<?php echo esc_url( home_url( '/' ) ); ?>" rel="home">
<?php bloginfo( 'name' ); ?>
</a>
</h1>
<?php
else :
?>
<p class="site-title">
<a href="<?php echo esc_url( home_url( '/' ) ); ?>" rel="home">
<?php bloginfo( 'name' ); ?>
</a>
</p>
<?php
endif;
$description = get_bloginfo( 'description', 'display' );
if ( $description || is_customize_preview() ) :
?>
<p class="site-description">
<?php echo $description; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
</p>
<?php endif; ?>
</div>
<nav id="site-navigation" class="main-navigation">
<button class="menu-toggle" aria-controls="primary-menu" aria-expanded="false">
<?php esc_html_e( 'Primary Menu', 'my-theme' ); ?>
</button>
<?php
wp_nav_menu(
array(
'theme_location' => 'menu-1',
'menu_id' => 'primary-menu',
)
);
?>
</nav>
</header>
<div id="content" class="site-content">
🔑 Critical Functions in header.php
language_attributes()- Outputs language attributes for the html tagbloginfo( 'charset' )- Gets the site's character encodingwp_head()- Essential hook for styles, scripts, and meta tagsbody_class()- Adds contextual classes to the body tagwp_body_open()- Hook right after opening body tag (WP 5.2+)
The footer.php File
The footer.php closes all the tags opened in header.php and adds footer content:
<?php
/**
* The template for displaying the footer
*
* @package My_Theme
*/
?>
</div><!-- #content -->
<footer id="colophon" class="site-footer">
<div class="site-info">
<a href="<?php echo esc_url( __( 'https://wordpress.org/', 'my-theme' ) ); ?>">
<?php
/* translators: %s: CMS name, i.e. WordPress. */
printf( esc_html__( 'Proudly powered by %s', 'my-theme' ), 'WordPress' );
?>
</a>
<span class="sep"> | </span>
<?php
/* translators: 1: Theme name, 2: Theme author. */
printf( esc_html__( 'Theme: %1$s by %2$s.', 'my-theme' ), 'my-theme', '<a href="https://example.com">Your Name</a>' );
?>
</div>
<?php if ( has_nav_menu( 'footer' ) ) : ?>
<nav class="footer-navigation" aria-label="<?php esc_attr_e( 'Footer Menu', 'my-theme' ); ?>">
<?php
wp_nav_menu(
array(
'theme_location' => 'footer',
'menu_class' => 'footer-menu',
'depth' => 1,
)
);
?>
</nav>
<?php endif; ?>
</footer>
</div><!-- #page -->
<?php wp_footer(); ?>
</body>
</html>
The sidebar.php File
Sidebars are widget-ready areas that typically appear alongside your main content:
sidebar.php
<?php
/**
* The sidebar containing the main widget area
*
* @package My_Theme
*/
if ( ! is_active_sidebar( 'sidebar-1' ) ) {
return;
}
?>
<aside id="secondary" class="widget-area">
<?php dynamic_sidebar( 'sidebar-1' ); ?>
</aside>
Registering in functions.php
function my_theme_widgets_init() {
register_sidebar( array(
'name' => __( 'Sidebar', 'my-theme' ),
'id' => 'sidebar-1',
'description' => __( 'Add widgets here.', 'my-theme' ),
'before_widget' => '<section id="%1$s" class="widget %2$s">',
'after_widget' => '</section>',
'before_title' => '<h2 class="widget-title">',
'after_title' => '</h2>',
) );
}
add_action( 'widgets_init', 'my_theme_widgets_init' );
Multiple Sidebars
You can create different sidebars for different sections:
Default sidebar
Shop pages sidebar
Blog sidebar
Footer widgets
<?php
// Call specific sidebars
get_sidebar(); // Loads sidebar.php
get_sidebar( 'shop' ); // Loads sidebar-shop.php
get_sidebar( 'blog' ); // Loads sidebar-blog.php
get_sidebar( 'footer' ); // Loads sidebar-footer.php
Content Template Files
These templates handle specific types of content:
single.php
Purpose: Display individual blog posts
Features: Full content, comments, metadata
<?php
get_header(); ?>
<main id="primary">
<?php
while ( have_posts() ) :
the_post();
get_template_part( 'template-parts/content', 'single' );
the_post_navigation();
// If comments are open or we have at least one comment
if ( comments_open() || get_comments_number() ) :
comments_template();
endif;
endwhile;
?>
</main>
<?php
get_sidebar();
get_footer();
page.php
Purpose: Display static pages
Features: Page content, optional comments
<?php
get_header(); ?>
<main id="primary">
<?php
while ( have_posts() ) :
the_post();
get_template_part( 'template-parts/content', 'page' );
// If comments are open or we have at least one comment
if ( comments_open() || get_comments_number() ) :
comments_template();
endif;
endwhile;
?>
</main>
<?php
get_sidebar();
get_footer();
archive.php
Purpose: Display post archives
Features: Archive title, post list
<?php
get_header(); ?>
<main id="primary">
<?php if ( have_posts() ) : ?>
<header class="page-header">
<?php
the_archive_title( '<h1 class="page-title">', '</h1>' );
the_archive_description( '<div class="archive-description">', '</div>' );
?>
</header>
<?php
while ( have_posts() ) :
the_post();
get_template_part( 'template-parts/content', get_post_type() );
endwhile;
the_posts_navigation();
else :
get_template_part( 'template-parts/content', 'none' );
endif;
?>
</main>
<?php
get_sidebar();
get_footer();
search.php
Purpose: Display search results
Features: Search query, results list
<?php
get_header(); ?>
<main id="primary">
<?php if ( have_posts() ) : ?>
<header class="page-header">
<h1 class="page-title">
<?php
printf( esc_html__( 'Search Results for: %s', 'my-theme' ),
'<span>' . get_search_query() . '</span>' );
?>
</h1>
</header>
<?php
while ( have_posts() ) :
the_post();
get_template_part( 'template-parts/content', 'search' );
endwhile;
the_posts_navigation();
else :
get_template_part( 'template-parts/content', 'none' );
endif;
?>
</main>
<?php
get_sidebar();
get_footer();
404.php
Purpose: Display 404 error page
Features: Error message, search form
<?php
get_header(); ?>
<main id="primary">
<section class="error-404 not-found">
<header class="page-header">
<h1 class="page-title">
<?php esc_html_e( 'Oops! That page can’t be found.', 'my-theme' ); ?>
</h1>
</header>
<div class="page-content">
<p><?php esc_html_e( 'It looks like nothing was found at this location. Maybe try a search?', 'my-theme' ); ?></p>
<?php get_search_form(); ?>
<?php the_widget( 'WP_Widget_Recent_Posts' ); ?>
</div>
</section>
</main>
<?php
get_footer();
comments.php
Purpose: Display comments section
Features: Comment list, comment form
<?php
/**
* The template for displaying comments
*/
// If the current post is protected by a password and
// the visitor has not yet entered the password, return early
if ( post_password_required() ) {
return;
}
?>
<div id="comments" class="comments-area">
<?php if ( have_comments() ) : ?>
<h2 class="comments-title">
<?php
$comment_count = get_comments_number();
if ( '1' === $comment_count ) {
printf( esc_html__( 'One thought on “%1$s”', 'my-theme' ),
'<span>' . wp_kses_post( get_the_title() ) . '</span>' );
} else {
printf(
esc_html( _nx( '%1$s thought on “%2$s”', '%1$s thoughts on “%2$s”', $comment_count, 'comments title', 'my-theme' ) ),
number_format_i18n( $comment_count ),
'<span>' . wp_kses_post( get_the_title() ) . '</span>'
);
}
?>
</h2>
<ol class="comment-list">
<?php
wp_list_comments( array(
'style' => 'ol',
'short_ping' => true,
) );
?>
</ol>
<?php
the_comments_navigation();
endif; // Check for have_comments()
// If comments are closed and there are comments
if ( ! comments_open() ) :
?>
<p class="no-comments">
<?php esc_html_e( 'Comments are closed.', 'my-theme' ); ?>
</p>
<?php
endif;
comment_form();
?>
</div>
Template File Flow
Understanding how these files work together is crucial:
Typical Page Load Sequence
Determines template
e.g., single.php
Loads header.php
Content output
Loads sidebar.php
Loads footer.php
Template Files Best Practices
- Always include wp_head() and wp_footer(): These are essential hooks
- Check if sidebars are active: Don't output empty sidebar containers
- Use proper escaping: esc_html(), esc_url(), esc_attr() for security
- Make templates translation-ready: Use __() and _e() functions
- Include skip links: For accessibility, add "Skip to content" links
- Use semantic HTML5: header, nav, main, aside, footer tags
- Keep templates DRY: Use get_template_part() for reusable sections
- Comment your code: Explain complex logic for future reference
Practice Exercise
Let's build a complete set of main template files: