This guide is for you who have a custom post type (CPT) and want an advanced search page that searches for results within this CPT. In the search form you can set up different inputs for searching within different fields; post title, post content, any type of custom meta or by custom taxonomy.

In this post we’ll assume we have a custom post type for books, an attached custom taxonomy for book genre, and a bunch of custom meta; author, year published, ISBN number and a checkbox whether or not the book is in stock. The custom post type and parameters are completely up to you to fit to your needs, the code below will simply try to cover most of the bases.

What we will make

We will create a custom post type for books, and a custom hierarchical taxonomy attached to it for genre. In addition each book has custom fields for author(s), published year, ISBN number, and a checkbox whether or not to include books that are out of stock. As for the search page itself we will make a page template where most of our code will reside. The page will start by rendering a custom form at the start; showing all possible parameters to filter the results. Below it all book results will appear in a list. We decide a number of results per page and add pagination at the bottom if the number exceeds this.

This is the possible filter parameters we will create for our advanced search template:

  • Text input for searching any string within post title and post content.
  • Dropdown for choosing a genre. Allows choosing a term or leave it at “Any”.
  • Input for typing author name which searches in custom meta. Loosely matching which means entering “Mark” will return all authors whose name is or contains “Mark”.
  • Number input for entering year the book was published which searches in custom meta. Loosely matching which means entering e.g. “20” will match with any book published in 1920 or any year starting with 20.
  • Input for entering ISBN (international book ID) which searches in custom meta. Returns only precise matches.
  • Checkbox whether or not to include out of stock books, yet another custom meta. As default this is unchecked, which means results only show books that are in stock.

The form is set up to use the GET method, which means any searched parameters will be appended to the page in the URL, in the form of “?book-search=world&year-published=2016&book-author=mark“. The other option if you want to avoid having “ugly URLs” is using Javascript and AJAX. But there are some downsides with this. First and foremost it will not be possible to bookmark a search with specific search parameters “pre-filled”. Imagine that you elsewhere on your site want to link directly to your custom search page by a specific author. You could then make the link go to your search page, append “?author=mark“, and thus clicking the link will lead directly to the results for that author. This is not possible to achieve with AJAX.

The search parameters are mutually inclusive. This means that combining for example year “2011” and author “Some guy” will only return books that matches BOTH of these. If we also specify “funny” in the general search text input we will only get returns that matches all three of these. Said in different words; we will use AND logic. This is the most common method to filter search results.

This tutorial won’t include styling so this part is up to you. Below is an example of how this could look like with some basic styling.

A note on pagination with a custom query

If you want pagination on a custom query within a single page, there’s a few things to be aware of. A query’s pagination can be generated with the WordPress functions the_posts_pagination(), paginate_links(), or the two next_post_link() and previous_post_link(). However these are coded to work with the global wp_query object (which for a page template is the page itself), and not a custom query.

There are a few workarounds, such as writing a pagination function yourself. Or you could use the action pre_get_posts and manipulate the wp_query object. Unfortunately this method is too late to affect pagination functions. Another alternative is skipping pagination alltogether and simply show all posts. This could be an option if you don’t have a lot of posts, but if you are making an advanced custom search template – I assume you do have quite a few posts.

What we will do in this guide is a kind of “hackish” method. We will within the page template override the wp_query object with our custom query so that the loop and pagination functions work as expected. This method is what I’ve had most success with.

Without further ado, let’s start coding!

Setting up custom post type, taxonomy and meta fields

The first step is defining the custom post type we want to create an advanced search template for. If you already have set up a custom post type, or wish to implement it for posts or pages, you can skip right ahead to the next part.

We are defining a custom post type book with a hierarchical custom taxonomy book_category. I won’t go into detail explaining how to create custom post types and taxonomies here. If you are interested in learning more I have a post that goes into detail about this.

Put this code anywhere in your theme’s functions.php or plugin code:

add_action('init', function() {
	register_post_type('book', [
		'label' => __('Books', 'txtdomain'),
		'public' => true,
		'menu_position' => 5,
		'menu_icon' => 'dashicons-book',
		'supports' => ['title', 'editor', 'thumbnail', 'author', 'revisions', 'comments'],
		'show_in_rest' => true,
		'rewrite' => ['slug' => 'book'],
		'taxonomies' => ['book_category'],
		'labels' => [
			'singular_name' => __('Book', 'txtdomain'),
			'add_new_item' => __('Add new book', 'txtdomain'),
			'new_item' => __('New book', 'txtdomain'),
			'view_item' => __('View book', 'txtdomain'),
			'not_found' => __('No books found', 'txtdomain'),
			'not_found_in_trash' => __('No books found in trash', 'txtdomain'),
			'all_items' => __('All books', 'txtdomain'),
			'insert_into_item' => __('Insert into book', 'txtdomain')
		],
	]);

	register_taxonomy('book_category', ['book'], [
		'label' => __('Book Category', 'txtdomain'),
		'hierarchical' => true,
		'rewrite' => ['slug' => 'book-category'],
		'show_admin_column' => true,
		'show_in_rest' => true,
		'labels' => [
			'singular_name' => __('Book Category', 'txtdomain'),
			'all_items' => __('All Book Categories', 'txtdomain'),
			'edit_item' => __('Edit Book Category', 'txtdomain'),
			'view_item' => __('View Book Category', 'txtdomain'),
			'update_item' => __('Update Book Category', 'txtdomain'),
			'add_new_item' => __('Add New Book Category', 'txtdomain'),
			'new_item_name' => __('New Book Category Name', 'txtdomain'),
			'search_items' => __('Search Book Categories', 'txtdomain'),
			'parent_item' => __('Parent Book Category', 'txtdomain'),
			'parent_item_colon' => __('Parent Book Category:', 'txtdomain'),
			'not_found' => __('No Book Categories found', 'txtdomain'),
		]
	]);
});

This will result in a custom post type with a taxonomy attached to it in admin.

Setting up the custom post meta is a bit up to you – either handle it manually with add_meta_box()or use the plugin Advanced Custom Fields (ACF) which is perfect for this kind of work. I’ll use ACF to set up the fields programatically, like so:

if (function_exists('acf_add_local_field_group')) {
	add_action('acf/init', function() {
		$fields = [
			[
				'key' => 'field_author',
				'label' => __('Author(s)', 'txtdomain'),
				'name' => 'book_author',
				'type' => 'textarea',
				'rows' => 3,
				'new_lines' => 'wpautop',
			],
			[
				'key' => 'field_year_published',
				'label' => __('Year published', 'txtdomain'),
				'name' => 'year_published',
				'type' => 'number',
			],
			[
				'key' => 'field_isbn',
				'label' => __('ISBN', 'txtdomain'),
				'name' => 'isbn',
				'type' => 'text',
			],
			[
				'key' => 'field_in_stock',
				'label' => __('Stock status', 'txtdomain'),
				'name' => 'in_stock',
				'type' => 'true_false',
				'message' => __('In stock', 'txtdomain'),
				'default_value' => 1,
			],
		];

		acf_add_local_field_group([
			'key' => 'group_book_fields',
			'title' => __('Book Details', 'txtdomain'),
			'fields' => $fields,
			'label_placement' => 'top',
			'menu_order' => 0,
			'style' => 'default',
			'position' => 'normal',
			'location' => [
				[
					[
						'param' => 'post_type',
						'operator' => '==',
						'value' => 'book'
					]
				]
			],
		]);
	});
}

You are of course welcome to set up the ACF fields using ACF’s admin GUI. But there are several benefits of adding them by code. For instance ensuring that you keep the same fields wherever you activate your theme or plugin. If you set the fields up in admin, you need to remember to export and import them if you switch WordPress site.

Please note the meta names; e.g. book_author, year_published, and so on. You’ll refer to these when we create the custom query on the advanced search template.

With the ACF plugin and the code above editing a single book would look like this:

With this we are all good to create as many book posts we want. The next step is creating the page template for our search.

Creating the page template

Let’s create the page template where we’ll put our advanced search template in. Make a copy of your theme’s single.php or page.php and rename it to for example template-booksearch.php. The HTML is up to you, but the reason we make a copy of single or page is because they are probably the closest template in terms of layout.

At the very top of the page template we signify that this is a page template by writing “Template Name” and its name within comment block. Doing this will make sure we can select the page template when we edit a page.

<?php /* Template Name: Advanced Book Search */
get_header(); ?>
...

The sections we need to code in our template are as follows, in this order:

  1. Store all variables previously submitted by the form, using get_query_var(). Also need to store current page
  2. Render the search form with a submit button for performing a search
  3. Reset the wp_query object
  4. Set up the arguments for a new WP_Query() depending on searched parameters, and run the query. Assign the custom query to the wp_query object
  5. Loop through the results and display them. Also render pagination links
  6. Reset the wp_query object back to what it was

The reason we need to store all searched variables and current page early is because these variables will be lost once we reset the wp_query object in step 3. We also need the variables for our search form, to populate the fields.

In your template decide the place you want to output your custom search, and let’s start at the top:

1. Get all searched variables

We get ahold of GET parameters (from the URL) with get_query_var(). For example; if we have a parameter ?book-author=benjamin in the URL, using get_query_var('book-author') would return the string ‘benjamin‘. Fetching all variables can be done like so:

$search_string = get_query_var('book-search');
$author = get_query_var('book-author');
$category = get_query_var('book-category');
$year = get_query_var('year-published');
$isbn = get_query_var('isbn');
$out_of_stock = get_query_var('out-of-stock');

But these are all custom parameters that are not part of WordPress standard GET parameters. WordPress will ignore any GET parameters it does not know, so calling these will always return an empty string. We need to tell WordPress to allow each of these GET parameters. We do this by filtering query_vars. In your functions.php, add this as well:

functions.php
add_filter('query_vars', function($vars) {
	$vars[] = 'book-search';
	$vars[] = 'book-author';
	$vars[] = 'book-category';
	$vars[] = 'year-published';
	$vars[] = 'isbn';
	$vars[] = 'out-of-stock';
	return $vars;
});

Now our get_query_var()‘s should be able to fetch the GET parameters. If they are not set, it returns an empty string.

We also need to fetch current page before we mess up the wp_query object. The current page is a hidden GET parameter called paged. We’ll fetch it the same way as our other GET parameters, but we will set it to default to 1 if it’s empty.

$paged = (get_query_var('paged')) ? get_query_var('paged') : 1;

Note: As paged is a standard WordPress parameter, we do not need to add paged to the query_vars filter.

2. Render the search form

The search form will simply be a <form> with the necessary inputs and a form submit button. The HTML is completely up to you, in the example below I’m adding the inputs in a simple list. I won’t include any styling in this tutorial, this is up to you. You can choose to style the search form to reside the top of the results, or at the side – vertically downwards along with the search results.

Note: To shorten and modularize the advanced search template you could separate out the search form part in a separate template file and include it with get_template_part(). But for this tutorial’s simplicity’s sake I’ll include everything inside the one template file itself.

The form itself needs to be of method GET, and the action needs to point to the page we’re at. To do this we access the global $post object and gets the permalink from that. At the end we add a submit button that will submit the form.

global $post;
?>
<form method="GET" action="<?php echo get_permalink($post->ID); ?>">
	<ul class="book-search-form">

	</ul>
	<input type="submit" value="<?php _e('Search', 'txtdomain'); ?>" />
</form>
<?php

Within the unordered list we’ll add a fitting input for each of our possible search parameters. Adjust the HTML and the search parameters to fit your needs:

global $post;
?>
<form method="GET" action="<?php echo get_permalink($post->ID); ?>">
	<ul class="book-search-form">
		<li>
			<label for="book-search"><?php _e('Search...', 'txtdomain'); ?></label>
			<input type="text" id="book-search" name="book-search" value="<?php echo $search_string; ?>" />
		</li>
		<li>
			<label for="book-category"><?php _e('Genre', 'txtdomain'); ?></label>
			<?php 
			wp_dropdown_categories([
				'taxonomy' => 'book_category',
				'name' => 'book-category',
				'id' => 'book-category',
				'value_field' => 'slug',
				'selected' => $category,
				'show_option_none' => __('Any genre', 'txtdomain'),
				'option_none_value' => '',
				'hierarchical' => true,
				'hide_if_empty' => false,
			]);
			?>
		</li>
		<li>
			<label for="book-author"><?php _e('Author', 'txtdomain'); ?></label>
			<input type="text" id="book-author" name="book-author" value="<?php echo $author; ?>" />
		</li>
		<li>
			<label for="year-published"><?php _e('Year published', 'txtdomain'); ?></label>
			<input type="number" id="year-published" name="year-published" value="<?php echo $year; ?>" />
		</li>
		<li>
			<label for="isbn"><?php _e('ISBN', 'txtdomain'); ?></label>
			<input type="text" id="isbn" name="isbn" value="<?php echo $isbn; ?>" />
		</li>
		<li>
			<input type="checkbox" id="out-of-stock" name="out-of-stock" value="out-of-stock" <?php checked($out_of_stock, 'out-of-stock'); ?> /><label for="out-of-stock"><?php _e('Include out of stock', 'txtdomain'); ?></label>
		</li>
	</ul>
	<input type="submit" value="<?php _e('Search', 'txtdomain'); ?>" />
</form>
<?php

Please take note of the name attributes; they are what will appear in the URL when the form is submitted. They must correspond to the query_vars and get_query_var() we have defined earlier!

The above code starts by rendering a text input for the general text search. We set the value to the previously searched variable we fetched using get_query_var(). This ensures that the input doesn’t clear after we’ve done a search.

The next parameter is a dropdown of genre from our custom taxonomy. To render this we utilize the function wp_dropdown_categories(). Take a look at the documentation page to see why we add all these parameters to adjust the dropdown to our needs. It’s important that the taxonomy is set to our custom taxonomy, name attribute is correct andselected is set to the value of the previously searched variable. We also set the term values to be their slugs instead of term IDs. It looks better with ?book-category=fiction instead of ?book-category=42. We also activate a “none” option for adding an “Any genre” option.

Following that we render another text input for searching for author, a number input for year published, a text input for ISBN number, and finally a checkbox that is as default unchecked to include out of stock books.

3. Reset the wp_query

The next step is a little hack in order to ensure that pagination works for our custom query. We simply store the current $wp_query in some variable and then set it to null. Later, in step 6 we will reset it from the $tmp_wpquery variable.

$tmp_wpquery = $wp_query;
$wp_query = null;

4. Set up arguments and run custom query

This part is all about performing a new query. We start by setting up the most basic arguments, and then we conditionally add parameters depending on what was found in previously searched variables (from our get_query_var()s).

$args = [
	'post_type' => 'book',
	'posts_per_page' => 20,
	'paged' => $paged
];

$meta_query = [];
$tax_query = [];

if (!empty($search_string)) {
	$args['s'] = $search_string;
}

if (!empty($category)) {
	$tax_query[] = [
		'taxonomy' => 'book_category',
		'field' => 'slug',
		'terms' => $category
	];
}

if (!empty($author)) {
	$meta_query[] = [
		'key' => 'book_author',
		'value' => $author,
		'compare' => 'LIKE'
	];
}

if (!empty($year)) {
	$year = (int) $year;
	$meta_query[] = [
		'key' => 'year_published',
		'value' => $year,
		'compare' => 'LIKE'
	];
}

if (!empty($isbn)) {
	$meta_query[] = [
		'key' => 'isbn',
		'value' => $isbn,
	];
}

if (empty($out_of_stock)) {
	$meta_query[] = [
		'key' => 'in_stock',
		'value' => true,
	];
}

if (!empty($meta_query)) {
	$args['meta_query'] = $meta_query;
}
if (!empty($tax_query)) {
	$args['tax_query'] = $tax_query;
}

// Perform query and assign it to wp_query
$books = new WP_Query($args);
$wp_query = $books;

The above code is basically building a WP_Query with parameters. The documentation page for WP_Query is a great resource for figuring out how to build a query.

The very first arguments make sure we query books only, and that we are properly informing about which page we’re currently at – from the paged variable we fetched earlier. The number of posts per page is up to you, I’ve simply set it to 20.

Note that adding ‘compare‘ to ‘LIKE‘ will make WordPress search for anything that contains the given string. I did not add this to the ISBN search because for this field I want it to find results that match perfectly.

The important thing is right at the end, when we actually perform the query, and assign that query to the wp_query variable.

5. Loop through the query results and render pagination

This step is really easy. All we need is a standard loop and for each we render the book post however we’d like. This part is completely up to you.

The code below shows a basic example of looping through the results, calling get_template_part() for each post. After the loop the_posts_pagination() is used to render pagination links. If the query returned no posts, a simple paragraph with a text is displayed.

if (have_posts()) { 
	
	while (have_posts()) : the_post();
		get_template_part('content', 'book');
	endwhile;

	the_posts_pagination([
		'mid_size' => 2,
		'prev_text' => __('« Previous', 'txdomain'),
		'next_text' => __('Next »', 'txdomain')
	]);
} else {
	?><p class="no-posts"><?php _e('No books found.', 'txdomain'); ?></p><?php
}

Adjust the HTML and output to fit your needs. The above code expects a template file in the theme named content-book.php for rendering a single book in the loop. I won’t show you how to render each post as this is something you most likely already have control over.

6. Reset the wp_query back to what it was

The final step is resetting the wp_query object to what we stored earlier in step 3. We set it to null first to ensure it’s reset.

$wp_query = null;
$wp_query = $tmp_wpquery;

Conclusion and final result

And that was it! You should now have a fully functional advanced custom search template. I hope this has been some help to you – the code has been written as general as possible to make it easy for you to adjust to your needs. Perhaps you want different types of parameters, or your custom post type is for movies or something else!

With some basic styling it can easily look something like this:

Here’s the final code; the functions.php part and the template:

functions.php
// Custom post type for books and custom taxonomy
add_action('init', function() {
	register_post_type('book', [
		'label' => __('Books', 'txtdomain'),
		'public' => true,
		'menu_position' => 5,
		'menu_icon' => 'dashicons-book',
		'supports' => ['title', 'editor', 'thumbnail', 'author', 'revisions', 'comments'],
		'show_in_rest' => true,
		'rewrite' => ['slug' => 'book'],
		'taxonomies' => ['book_category'],
		'labels' => [
			'singular_name' => __('Book', 'txtdomain'),
			'add_new_item' => __('Add new book', 'txtdomain'),
			'new_item' => __('New book', 'txtdomain'),
			'view_item' => __('View book', 'txtdomain'),
			'not_found' => __('No books found', 'txtdomain'),
			'not_found_in_trash' => __('No books found in trash', 'txtdomain'),
			'all_items' => __('All books', 'txtdomain'),
			'insert_into_item' => __('Insert into book', 'txtdomain')
		],
	]);

	register_taxonomy('book_category', ['book'], [
		'label' => __('Book Category', 'txtdomain'),
		'hierarchical' => true,
		'rewrite' => ['slug' => 'book-category'],
		'show_admin_column' => true,
		'show_in_rest' => true,
		'labels' => [
			'singular_name' => __('Book Category', 'txtdomain'),
			'all_items' => __('All Book Categories', 'txtdomain'),
			'edit_item' => __('Edit Book Category', 'txtdomain'),
			'view_item' => __('View Book Category', 'txtdomain'),
			'update_item' => __('Update Book Category', 'txtdomain'),
			'add_new_item' => __('Add New Book Category', 'txtdomain'),
			'new_item_name' => __('New Book Category Name', 'txtdomain'),
			'search_items' => __('Search Book Categories', 'txtdomain'),
			'parent_item' => __('Parent Book Category', 'txtdomain'),
			'parent_item_colon' => __('Parent Book Category:', 'txtdomain'),
			'not_found' => __('No Book Categories found', 'txtdomain'),
		]
	]);
});

// ACF for custom meta fields
if (function_exists('acf_add_local_field_group')) {
	add_action('acf/init', function() {
		$fields = [
			[
				'key' => 'field_author',
				'label' => __('Author(s)', 'txtdomain'),
				'name' => 'book_author',
				'type' => 'textarea',
				'rows' => 3,
				'new_lines' => 'wpautop',
			],
			[
				'key' => 'field_year_published',
				'label' => __('Year published', 'txtdomain'),
				'name' => 'year_published',
				'type' => 'number',
			],
			[
				'key' => 'field_isbn',
				'label' => __('ISBN', 'txtdomain'),
				'name' => 'isbn',
				'type' => 'text',
			],
			[
				'key' => 'field_in_stock',
				'label' => __('Stock status', 'txtdomain'),
				'name' => 'in_stock',
				'type' => 'true_false',
				'message' => __('In stock', 'txtdomain'),
				'default_value' => 1,
			],
		];

		acf_add_local_field_group([
			'key' => 'group_book_fields',
			'title' => __('Book Details', 'txtdomain'),
			'fields' => $fields,
			'label_placement' => 'top',
			'menu_order' => 0,
			'style' => 'default',
			'position' => 'normal',
			'location' => [
				[
					[
						'param' => 'post_type',
						'operator' => '==',
						'value' => 'book'
					]
				]
			],
		]);
	});
}

// Allow custom GET parameters
add_filter('query_vars', function($vars) {
	$vars[] = 'book-search';
	$vars[] = 'book-author';
	$vars[] = 'book-category';
	$vars[] = 'year-published';
	$vars[] = 'isbn';
	$vars[] = 'out-of-stock';
	return $vars;
});
template-booksearch.php
<?php 
/* Template Name: Advanced Book Search */
get_header(); ?>

<main class="content">
	<?php 

	// Store variables
	$paged = (get_query_var('paged')) ? get_query_var('paged') : 1;
	$search_string = get_query_var('book-search');
	$author = get_query_var('book-author');
	$category = get_query_var('book-category');
	$year = get_query_var('year-published');
	$isbn = get_query_var('isbn');
	$out_of_stock = get_query_var('out-of-stock');

	// Search form
	global $post;
	?><form method="GET" action="<?php echo get_permalink($post->ID); ?>">
		<ul class="book-search-form">
			<li>
				<label for="book-search"><?php _e('Search...', 'txtdomain'); ?></label>
				<input type="text" id="book-search" name="book-search" value="<?php echo $search_string; ?>" />
			</li>
			<li>
				<label for="book-category"><?php _e('Genre', 'txtdomain'); ?></label>
				<?php 
				wp_dropdown_categories([
					'taxonomy' => 'book_category',
					'name' => 'book-category',
					'id' => 'book-category',
					'value_field' => 'slug',
					'selected' => $category,
					'show_option_none' => __('Any genre', 'txtdomain'),
					'option_none_value' => '',
					'hierarchical' => true,
					'hide_if_empty' => false,
				]);
				?>
			</li>
			<li>
				<label for="book-author"><?php _e('Author', 'txtdomain'); ?></label>
				<input type="text" id="book-author" name="book-author" value="<?php echo $author; ?>" />
			</li>
			<li>
				<label for="year-published"><?php _e('Year published', 'txtdomain'); ?></label>
				<input type="number" id="year-published" name="year-published" value="<?php echo $year; ?>" />
			</li>
			<li>
				<label for="isbn"><?php _e('ISBN', 'txtdomain'); ?></label>
				<input type="text" id="isbn" name="isbn" value="<?php echo $isbn; ?>" />
			</li>
			<li>
				<input type="checkbox" id="out-of-stock" name="out-of-stock" value="out-of-stock" <?php checked($out_of_stock, 'out-of-stock'); ?> /><label for="out-of-stock"><?php _e('Include out of stock', 'txtdomain'); ?></label>
			</li>
		</ul>
		<input type="submit" value="<?php _e('Search', 'txtdomain'); ?>" />
	</form>
	<?php

	// Reset wp_query temporary
	$tmp_wpquery = $wp_query;
	$wp_query = null;

	// Start setting up custom query
	$args = [
		'post_type' => 'book',
		'posts_per_page' => 20,
		'paged' => $paged
	];

	$meta_query = [];
	$tax_query = [];

	// Search post title and content
	if (!empty($search_string)) {
		$args['s'] = $search_string;
	}

	// Search by category
	if (!empty($category)) {
		$tax_query[] = [
			'taxonomy' => 'book_category',
			'field' => 'slug',
			'terms' => $category
		];
	}

	// Search by ISBN
	if (!empty($author)) {
		$meta_query[] = [
			'key' => 'book_author',
			'value' => $author,
			'compare' => 'LIKE'
		];
	}

	// Search by year
	if (!empty($year)) {
		$year = (int) $year;
		$meta_query[] = [
			'key' => 'year_published',
			'value' => $year,
			'compare' => 'LIKE'
		];
	}

	// Search by ISBN
	if (!empty($isbn)) {
		$meta_query[] = [
			'key' => 'isbn',
			'value' => $isbn,
		];
	}

	// Filter out of stock
	if (empty($out_of_stock)) {
		$meta_query[] = [
			'key' => 'in_stock',
			'value' => true,
		];
	}

	// Add to query arguments
	if (!empty($meta_query)) {
		$args['meta_query'] = $meta_query;
	}
	if (!empty($tax_query)) {
		$args['tax_query'] = $tax_query;
	}

	// Perform query and assign it to wp_query
	$books = new WP_Query($args);
	$wp_query = $books;

	// Loop through results
	if (have_posts()) { 
		while (have_posts()) : the_post();
			get_template_part('content', 'book');
		endwhile;

		the_posts_pagination([
			'mid_size' => 2,
			'prev_text' => __('« Previous', 'txdomain'),
			'next_text' => __('Next »', 'txdomain')
		]);
	} else {
		?><p class="no-posts"><?php _e('No books found.', 'txdomain'); ?></p><?php
	}

	// Reset wp_query back to what it was
	$wp_query = null;
	$wp_query = $tmp_wpquery;
	?>
</main>
<?php get_footer(); ?>