Template Parts and Includes
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.
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:
Creating Template Parts
Let's create reusable template parts for common content patterns:
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>
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>
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' );
- Child theme friendly
- Fallback system
- WordPress hooks
- No return value
- Limited to templates
require/include
PHP Native
require get_template_directory() . '/inc/custom-functions.php';
- Can return values
- Direct control
- Good for functions
- Not child theme aware
- No fallback
locate_template()
WordPress Utility
locate_template( 'template-parts/header/nav.php', true );
- Child theme aware
- Returns path
- Flexible loading
- More verbose
- Manual loading
load_template()
WordPress Internal
load_template( locate_template( 'custom.php' ), false );
- Sets up globals
- WordPress context
- 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
template-parts/content.php
✓ Found - Use this!
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
<?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: