Template Parts
get_header, get_footer and get_template_part load reusable template fragments, with $args for passing data since WordPress 5.5.
get_template_part( string $slug, string|null $name = null, array $args = array() ): void|falseTemplate 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
<?php
/**
* single.php — one post.
*/
get_header(); // header.php
<main id="primary">
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>
get_sidebar(); // sidebar.php
get_footer(); // footer.phpWhen 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.
<?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.
<?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.
<?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