Skip to main content

Course Progress

Loading...

Template Parts and Includes

Duration: 50 minutes
Module 5: Session 2

Learning Objectives

  • Master WordPress template parts organization
  • Understand the difference between WordPress and PHP includes
  • Create reusable template components
  • Implement proper file organization strategies
  • Use locate_template() and load_template() functions
  • Build modular, maintainable themes

Introduction

Template parts are like LEGO blocks for your WordPress theme. Instead of repeating the same code in multiple files, you create reusable components that can be assembled in different ways. This makes your theme more maintainable, flexible, and follows the DRY (Don't Repeat Yourself) principle.

🧩
Modular Thinking
Think of template parts like components in a modern car factory - the same steering wheel component can be used in different car models. Similarly, the same content template can be used in your archive, search, and index pages!

Why Use Template Parts?

♻️

Reusability

Write once, use everywhere. Update in one place, changes reflect everywhere.

🎯

Maintainability

Easier to debug and update when code is organized in logical chunks.

📦

Organization

Keep your theme files clean and focused on their specific purpose.

Template Parts File Structure

Organize your template parts in a dedicated folder for better structure:

📁 my-theme/
📄 index.php
📄 single.php
📄 page.php
📁 template-parts/
📁 content/
📄 content.php // Default content template
📄 content-single.php // Single post content
📄 content-page.php // Page content
📄 content-search.php // Search results
📄 content-none.php // No content found
📄 content-excerpt.php // Excerpt view
📁 header/
📄 site-branding.php // Logo and site title
📄 navigation.php // Main navigation
📄 hero.php // Hero section
📁 footer/
📄 widgets.php // Footer widgets
📄 credits.php // Copyright info
📄 social.php // Social links
📁 post/
📄 meta.php // Post metadata
📄 author-bio.php // Author information
📄 related-posts.php // Related posts
📁 inc/ // PHP includes (non-template)
📄 customizer.php
📄 template-functions.php
📄 template-tags.php

Creating Template Parts

Let's create reusable template parts for common content patterns:

Content Template

template-parts/content/content.php

<?php
/**
 * Template part for displaying posts
 *
 * @package My_Theme
 */
?>

<article id="post-<?php the_ID(); ?>" <?php post_class(); ?>>
    <header class="entry-header">
        <?php
        if ( is_singular() ) :
            the_title( '<h1 class="entry-title">', '</h1>' );
        else :
            the_title( '<h2 class="entry-title"><a href="' . esc_url( get_permalink() ) . '" rel="bookmark">', '</a></h2>' );
        endif;

        if ( 'post' === get_post_type() ) :
            ?>
            <div class="entry-meta">
                <?php
                my_theme_posted_on();
                my_theme_posted_by();
                ?>
            </div>
        <?php endif; ?>
    </header>

    <?php my_theme_post_thumbnail(); ?>

    <div class="entry-content">
        <?php
        if ( is_singular() ) :
            the_content(
                sprintf(
                    wp_kses(
                        /* translators: %s: Name of current post. Only visible to screen readers */
                        __( 'Continue reading<span class="screen-reader-text"> "%s"</span>', 'my-theme' ),
                        array(
                            'span' => array(
                                'class' => array(),
                            ),
                        )
                    ),
                    wp_kses_post( get_the_title() )
                )
            );

            wp_link_pages(
                array(
                    'before' => '<div class="page-links">' . esc_html__( 'Pages:', 'my-theme' ),
                    'after'  => '</div>',
                )
            );
        else :
            the_excerpt();
        endif;
        ?>
    </div>

    <footer class="entry-footer">
        <?php my_theme_entry_footer(); ?>
    </footer>
</article>
No Content Template

template-parts/content/content-none.php

<?php
/**
 * Template part for displaying a message that posts cannot be found
 *
 * @package My_Theme
 */
?>

<section class="no-results not-found">
    <header class="page-header">
        <h1 class="page-title"><?php esc_html_e( 'Nothing Found', 'my-theme' ); ?></h1>
    </header>

    <div class="page-content">
        <?php
        if ( is_home() && current_user_can( 'publish_posts' ) ) :

            printf(
                '<p>' . wp_kses(
                    /* translators: 1: link to WP admin new post page. */
                    __( 'Ready to publish your first post? <a href="%1$s">Get started here</a>.', 'my-theme' ),
                    array(
                        'a' => array(
                            'href' => array(),
                        ),
                    )
                ) . '</p>',
                esc_url( admin_url( 'post-new.php' ) )
            );

        elseif ( is_search() ) :
            ?>

            <p><?php esc_html_e( 'Sorry, but nothing matched your search terms. Please try again with some different keywords.', 'my-theme' ); ?></p>
            <?php
            get_search_form();

        else :
            ?>

            <p><?php esc_html_e( 'It seems we can’t find what you’re looking for. Perhaps searching can help.', 'my-theme' ); ?></p>
            <?php
            get_search_form();

        endif;
        ?>
    </div>
</section>
Post Meta Template

template-parts/post/meta.php

<?php
/**
 * Template part for displaying post metadata
 *
 * @package My_Theme
 */
?>

<div class="entry-meta">
    <span class="posted-on">
        <span class="screen-reader-text"><?php esc_html_e( 'Posted on', 'my-theme' ); ?></span>
        <time class="entry-date published" datetime="<?php echo esc_attr( get_the_date( DATE_W3C ) ); ?>">
            <?php echo esc_html( get_the_date() ); ?>
        </time>
    </span>

    <span class="byline">
        <span class="screen-reader-text"><?php esc_html_e( 'by', 'my-theme' ); ?></span>
        <span class="author vcard">
            <a class="url fn n" href="<?php echo esc_url( get_author_posts_url( get_the_author_meta( 'ID' ) ) ); ?>">
                <?php echo esc_html( get_the_author() ); ?>
            </a>
        </span>
    </span>

    <?php if ( ! post_password_required() && ( comments_open() || get_comments_number() ) ) : ?>
        <span class="comments-link">
            <?php comments_popup_link( esc_html__( 'Leave a comment', 'my-theme' ) ); ?>
        </span>
    <?php endif; ?>

    <?php
    // Display categories
    $categories_list = get_the_category_list( esc_html__( ', ', 'my-theme' ) );
    if ( $categories_list ) :
        ?>
        <span class="cat-links">
            <span class="screen-reader-text"><?php esc_html_e( 'Categories', 'my-theme' ); ?></span>
            <?php echo $categories_list; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
        </span>
    <?php endif; ?>
</div>

PHP Include Methods Comparison

WordPress provides its own functions alongside PHP's native include methods. Understanding when to use each is crucial:

get_template_part()

WordPress Way (Recommended)

get_template_part( 'template-parts/content', 'single' );
✓ Pros:
  • Child theme friendly
  • Fallback system
  • WordPress hooks
✗ Cons:
  • No return value
  • Limited to templates

require/include

PHP Native

require get_template_directory() . '/inc/custom-functions.php';
✓ Pros:
  • Can return values
  • Direct control
  • Good for functions
✗ Cons:
  • Not child theme aware
  • No fallback

locate_template()

WordPress Utility

locate_template( 'template-parts/header/nav.php', true );
✓ Pros:
  • Child theme aware
  • Returns path
  • Flexible loading
✗ Cons:
  • More verbose
  • Manual loading

load_template()

WordPress Internal

load_template( locate_template( 'custom.php' ), false );
✓ Pros:
  • Sets up globals
  • WordPress context
✗ Cons:
  • Rarely needed directly
  • Complex usage

Advanced Template Part Techniques

Passing Variables to Template Parts (WordPress 5.5+)

<?php
// In your main template
$args = array(
    'class'       => 'featured-post',
    'show_meta'   => true,
    'show_thumb'  => true,
    'thumb_size'  => 'large'
);

get_template_part( 'template-parts/content', 'featured', $args );

// In template-parts/content-featured.php
$class = $args['class'] ?? '';
$show_meta = $args['show_meta'] ?? false;
$show_thumb = $args['show_thumb'] ?? false;
$thumb_size = $args['thumb_size'] ?? 'thumbnail';
?>

<article class="<?php echo esc_attr( $class ); ?>">
    <?php if ( $show_thumb && has_post_thumbnail() ) : ?>
        <div class="post-thumbnail">
            <?php the_post_thumbnail( $thumb_size ); ?>
        </div>
    <?php endif; ?>
    
    <?php if ( $show_meta ) : ?>
        <div class="entry-meta">
            <?php get_template_part( 'template-parts/post/meta' ); ?>
        </div>
    <?php endif; ?>
</article>

Conditional Template Part Loading

<?php
// Load different templates based on conditions

// Method 1: Using post format
$format = get_post_format() ?: 'standard';
get_template_part( 'template-parts/content', $format );
// Looks for: content-video.php, content-gallery.php, content-standard.php

// Method 2: Using custom logic
if ( is_sticky() && is_home() && ! is_paged() ) {
    get_template_part( 'template-parts/content', 'featured' );
} elseif ( in_category( 'news' ) ) {
    get_template_part( 'template-parts/content', 'news' );
} else {
    get_template_part( 'template-parts/content', get_post_type() );
}

// Method 3: Check if template exists before loading
$templates = array(
    'template-parts/content-' . get_post_type() . '.php',
    'template-parts/content.php'
);

if ( locate_template( $templates[0] ) ) {
    get_template_part( 'template-parts/content', get_post_type() );
} else {
    get_template_part( 'template-parts/content' );
}

Creating a Custom Template Part Function

<?php
// In functions.php or inc/template-functions.php

/**
 * Custom template part loader with return capability
 */
function my_theme_get_template_part( $slug, $name = null, $args = array(), $return = false ) {
    if ( $return ) {
        ob_start();
    }
    
    get_template_part( $slug, $name, $args );
    
    if ( $return ) {
        return ob_get_clean();
    }
}

/**
 * Load template part with custom wrapper
 */
function my_theme_content_wrapper( $type = 'default', $args = array() ) {
    $defaults = array(
        'wrapper_class' => 'content-wrapper',
        'container'     => true,
        'columns'       => 1
    );
    
    $args = wp_parse_args( $args, $defaults );
    
    if ( $args['container'] ) {
        echo '<div class="' . esc_attr( $args['wrapper_class'] ) . '">';
    }
    
    get_template_part( 'template-parts/content', $type, $args );
    
    if ( $args['container'] ) {
        echo '</div>';
    }
}

// Usage
my_theme_content_wrapper( 'grid', array(
    'wrapper_class' => 'content-grid',
    'columns'       => 3
) );

// Get template part as string
$content = my_theme_get_template_part( 'template-parts/content', 'cached', array(), true );
set_transient( 'cached_content_' . get_the_ID(), $content, HOUR_IN_SECONDS );

Child Theme Inheritance

Template parts work seamlessly with child themes, following WordPress's inheritance model:

Template Part Resolution Order

Child Theme
template-parts/content.php
✓ Found - Use this!
Parent Theme
template-parts/content.php
Fallback if not in child

This allows child themes to override specific template parts without modifying the entire template structure.

<?php
// Parent theme: index.php
get_template_part( 'template-parts/header/hero' );

// WordPress looks for (in order):
// 1. child-theme/template-parts/header/hero.php
// 2. parent-theme/template-parts/header/hero.php

// To force parent theme template (rare):
include get_template_directory() . '/template-parts/header/hero.php';

// To force child theme template:
include get_stylesheet_directory() . '/template-parts/header/hero.php';

Template Part Naming Conventions

WordPress Naming Patterns

Pattern: {general}-{specific}.php
content-single.php → Content for single posts
content-page.php → Content for pages
content-{post-type}.php → Content for custom post type
content-{post-format}.php → Content for post format
<?php
// Smart template part loading based on context

// For post formats
$format = get_post_format() ?: 'standard';
get_template_part( 'template-parts/content', $format );
// Loads: content-video.php, content-gallery.php, content-standard.php

// For custom post types
get_template_part( 'template-parts/content', get_post_type() );
// Loads: content-product.php, content-portfolio.php, content-post.php

// For page templates
$template_slug = get_page_template_slug();
$template_name = $template_slug ? str_replace( '.php', '', basename( $template_slug ) ) : 'default';
get_template_part( 'template-parts/page', $template_name );
// Loads: page-full-width.php, page-sidebar.php, page-default.php

Template Parts Best Practices

  • Keep them focused: Each template part should have a single, clear purpose
  • Use descriptive names: content-product-grid.php is better than content-2.php
  • Document with comments: Explain what each template part does and where it's used
  • Avoid deep nesting: Don't create template parts that call too many other template parts
  • Consider performance: Don't create tiny template parts for single lines of code
  • Use consistent structure: Organize in logical folders (content/, post/, page/, etc.)
  • Make them self-contained: Template parts should work independently when possible
  • Handle missing data gracefully: Always check if data exists before using it

Practice Exercise

Let's refactor a theme to use template parts effectively:

💻
Template Parts Challenge
  1. Create a template-parts folder structure in your theme
  2. Extract repeated article markup into content.php
  3. Create specialized content templates (content-single.php, content-page.php)
  4. Build reusable components for post metadata
  5. Create a no-content template with search form
  6. Implement conditional loading based on post format
  7. Pass arguments to template parts (WP 5.5+)
  8. Test with a child theme override

Additional Resources