Template Parts

get_header, get_footer and get_template_part load reusable template fragments, with $args for passing data since WordPress 5.5.

Syntaxget_template_part( string $slug, string|null $name = null, array $args = array() ): void|false

Template parts are template fragments loaded from inside another template, so markup used on several pages lives in one file. get_header(), get_footer() and get_sidebar() are dedicated wrappers for the three conventional parts; get_template_part() handles everything else.

Naming and lookup

get_template_part( 'template-parts/content', 'excerpt' ) looks for template-parts/content-excerpt.php and falls back to template-parts/content.php. The child theme is searched before the parent, so a child overrides a part by copying the path. The same rule applies to the wrappers: get_header( 'shop' ) tries header-shop.php, then header.php.

Passing data

Since WordPress 5.5 all four functions take a third (or second) $args array, exposed inside the part as $args. Before 5.5 the idiom was set_query_var() plus get_query_var(), which still works and which you will meet in older themes.

What trips people up

The part is loaded through a function, so it does not inherit local variables from the calling template — only globals such as $post, and whatever you put in $args. Parts are included with require, not require_once, which is what lets you call the same part on every iteration of the loop.

Example

Example · php
<?php
/**
 * single.php — one post.
 */

get_header(); // header.php
?>

<main id="primary">
<?php
while ( have_posts() ) :
	the_post();

	/*
	 * Looks for template-parts/content-{format}.php first, e.g.
	 * content-video.php, then falls back to template-parts/content.php.
	 * get_post_format() returns false for the standard format, and a false
	 * $name simply means 'no specialised template'.
	 */
	get_template_part(
		'template-parts/content',
		get_post_format(),
		array(
			'show_meta'    => true,   // available as $args['show_meta']
			'heading_level' => 'h1',
		)
	);

	if ( comments_open() || get_comments_number() ) {
		comments_template();
	}
endwhile;
?>
</main>

<?php
get_sidebar(); // sidebar.php
get_footer();  // footer.php

When to use it

  • Rendering the same post card on the archive, search and related-posts templates
  • Giving one part two variants without duplicating it — content.php and content-video.php
  • Passing a heading level or CTA label into a shared part instead of using a global
  • Overriding a parent theme's part from a child theme by copying the file path

More examples

The part itself, reading $args safely

wp_parse_args() gives the part usable defaults so it still renders when called without arguments — from an archive template, say. Note that a variable set immediately above the get_template_part() call is not visible here; only globals and $args cross the boundary.

Example · php
<?php
/**
 * template-parts/content.php
 *
 * $args is defined by get_template_part() since WP 5.5. It is NOT defined
 * when the file is reached any other way, so always default it.
 */

$args = wp_parse_args(
	$args ?? array(),
	array(
		'show_meta'     => false,
		'heading_level' => 'h2',
	)
);

$tag = in_array( $args['heading_level'], array( 'h1', 'h2', 'h3' ), true )
	? $args['heading_level']
	: 'h2';
?>
<article <?php post_class(); ?>>
	<<?php echo $tag; ?>><?php the_title(); ?></<?php echo $tag; ?>>

	<?php if ( $args['show_meta'] ) : ?>
		<p><?php echo esc_html( get_the_date() ); ?></p>
	<?php endif; ?>

	<?php the_content(); ?>
</article>

$args shorthand and the set_query_var longhand

Both forms deliver the same value to the part. set_query_var() writes onto the global WP_Query object, so the value leaks to every later template and every secondary loop — prefer $args in new code, but recognise the old form when reading a theme you did not write.

Example · php
<?php
// WP 5.5+ shorthand.
get_template_part( 'template-parts/card', null, array( 'cta' => 'Read more' ) );
// inside the part:  echo esc_html( $args['cta'] );

// The pre-5.5 longhand, still valid and still common in older themes.
set_query_var( 'cta', 'Read more' );
get_template_part( 'template-parts/card' );
// inside the part:  echo esc_html( get_query_var( 'cta' ) );

// The wrappers take $args too (WP 5.5+).
get_header( 'shop', array( 'compact' => true ) );
get_footer( null, array( 'minimal' => true ) );

Lower-level lookup with locate_template

locate_template() returns the first existing file from a list, searching the child theme before the parent. Reach for it when you need to know whether a part exists before committing to markup around it, or when building your own get_template_part()-style helper in a plugin, where the theme's directories are not yours to assume.

Example · php
<?php
// get_header() is a wrapper: it fires the 'get_header' action, then resolves
// header-{$name}.php before header.php across child then parent theme.
// When you need the resolution without the hooks, use locate_template.

$file = locate_template(
	array( 'template-parts/pricing-' . $plan . '.php', 'template-parts/pricing.php' ),
	false // $load: return the path instead of including it
);

if ( $file ) {
	load_template( $file, false, array( 'plan' => $plan ) ); // $require_once = false
} else {
	// Genuine fallback — get_template_part() would have failed silently here.
	echo '<p>' . esc_html__( 'Pricing unavailable.', 'mytheme' ) . '</p>';
}

Discussion

  • Be the first to comment on this lesson.