Last updated 3 months ago

Custom templating allows developers to control every aspect of how queries are displayed.

The Loop

The output of any custom query can be templated easily. By default, there is a few built in templates that will work out of the box with any theme. However, a certain amount of styling may be required. The real power is in the ability to define and use custom templates, as well as redefine how the default templates behave.

The templates are based on the default loop. The default loop is included after the template files, and contains all of the actions necessary for virtually any type of output. However, the inclusion of the default loop can be disabled using the wp_query_include_loop filter.

The default loop uses the normalized template name to define each action. The template name is normalized by replacing .php, spaces, dashes, and slashes with underscores. So a template specified as "My Custom Template" produces the action "my_custom_template". A template specified as "includes/my-custom-template.php" will produce the action "includes_my_custom_template".

do_action( "wp_query_{$template_name}_setup", $template_name, $context, $query, $atts );
* Begin our main loop
do_action( "wp_query_before_{$template_name}_loop", $template_name, $context, $query, $atts );
if ( $query->have_posts() ) :
do_action( "wp_query_before_{$template_name}_while", $template_name, $context, $query, $atts );
while ( $query->have_posts() ) : $query->the_post();
do_action( "wp_query_{$template_name}_content", $template_name, $context, $query, $atts );
do_action( "wp_query_after_{$template_name}_while", $template_name, $context, $query, $atts );
do_action( "wp_query_after_{$template_name}_loop", $template_name, $context, $query, $atts );
* End our main loop
do_action( "wp_query_{$template_name}_teardown", $template_name, $context, $query, $atts );

Example Templates

Default Loop

An example template that outputs an ordered list, with post titles and a link. Since the template is included for each instance of the shortcode, it's important to wrap each function in a function_exists check. Otherwise multiple instances of the same template could cause errors.

* Template name: My Custom Template
* Open the unordered list
if( !function_exists( 'my_custom_template_before_while' ) ) {
function my_custom_template_before_while( $template_name, $context, $query, $atts ) {
echo '<ul class="my-custom-list">';
add_action( 'wp_query_before_my_custom_template_while', 'my_custom_template_before_while', 10, 4 );
* Output the content for each post
if( !function_exists( 'my_custom_template_content' ) ) {
function my_custom_template_content( $template_name, $context, $query, $atts ) {
printf( '<li><a href="%s">%s</a></li>', get_the_permalink(), get_the_title() );
add_action( 'wp_query_my_custom_template_content', 'my_custom_template_content', 10, 4 );
* Close the unordered list
if( function_exists( 'my_custom_template_after_while' ) ) {
function my_custom_template_after_while( $template_name, $context, $query, $atts ) {
echo '</ul>';
add_action( 'wp_query_after_my_custom_template_while', 'my_custom_template_after_while', 10, 4 );

Custom Loop

An example of a template using a custom loop, instead of the default loop. Some developers may be more comfortable with this more familiar syntax.

<?php add_filter( 'wp_query_include_loop', '__return_false' ); ?>
<?php if ( $query->have_posts() ) : ?>
<ul class="my-custom-template">
<?php while ( $query->have_posts() ) : $query->the_post(); ?>
<li><a href="<?php the_permalink(); ?>"><?php the_title(); ?></a></li>
<?php endwhile; ?>
<?php endif; ?>