This is a complete example of how you would set up a custom post type for books with category of genre, and a special page template for the category (genre). I find that getting the posts for the custom post type with the custom taxonomy (using tax_query and meta_query) has a bit of a trick to it.

1. Setting up a custom post type with a custom taxonomy

In your theme functions.php file:

// Register custom Post Type: book
add_action( 'init', 'create_post_type_book' );
function create_post_type_book() {
  register_post_type( 'book',
    array(
      'labels' => array(
        'name' => __( 'Books' ),
        'singular_name' => __( 'Book' )
      ),
      'public' => true,
      'has_archive' => true
    )
  );
}

add_action('init' , 'book_taxonomies' );
function book_taxonomies()
  {
    $labels = array(
      'name' => _x( 'Book Genres', 'taxonomy general name' ),
      'singular_name' => _x( 'Book Genres', 'taxonomy singular name' ),
      'search_items' =>  __( 'Search Book Genres' ),
      'all_items' => __( 'All Book Genres' ),
      'parent_item' => __( 'Parent Book Genre' ),
      'parent_item_colon' => __( 'Parent Book Genre:' ),
      'edit_item' => __( 'Edit Book Genre' ), 
      'update_item' => __( 'Update Book Genre' ),
      'add_new_item' => __( 'Add New Book Genre' ),
      'new_item_name' => __( 'New Book Genre' )
  );    

  register_taxonomy('genre',array('genre'), array(
    'hierarchical' => true,
    'labels' => $labels,
    'show_ui' => true,
    'query_var' => true,
    'show_in_nav_menus' => true,
    'rewrite' => array('slug' => 'genre'),
    'has_archive' => true
  ));   

2. Setup your custom post type and archive page templates

For this example, the template file you need to create in your theme is: taxonomy-genre.php and if you wished to set a custom template for the book post, the template would be single-book.php.

For further reference, see the Theme Developer Handbook on taxonomy templates and custom post type templates.

If you are following this example, you should be able to create a couple of book posts and a genre, and view them.

3. Listing all posts from the queried custom taxonomy

Presuming you have a header.php and footer.php file in your theme, a basic taxonomy-genre.php that lists the books in that genre would look like this. Leave out the header and footer lines if you don’t use those.

<?php get_header(); 
// setup vars to query taxonomy term
$queried_object = get_queried_object();
$taxonomy = $queried_object->taxonomy;
$term_id = $queried_object->term_id; 
$term_name = $queried_object->name; 
// The following line comments your taxonomy term_id and name into the html to help with debugging
echo '<!-- Term: '.$term_id.' / '.$term_name.' -->';
$args = array(
  'post_type'   => 'book', 
  'post_status' => 'publish',
  'orderby' => 'title',
  'order' => 'ASC',
  'posts_per_page' => -1,
  'tax_query' => array(
    array(
      'taxonomy' => 'genre',
      'field' => 'term_id',
      'terms' => $term_id
    )
  )
);

$posts = get_posts( $args );

echo '<ul>';
foreach ($posts as $post) {
  $post_title = $post->post_title;
  $permalink = get_permalink($post->ID);
  echo '<li><a href="'.$permalink.'">'.$post_title.'</a></li>';
}
echo '</ul>';
get_footer(); ?>

4. Why isn’t my tax_query working?

If you came here because your tax_query isn’t working or is returning all posts, the mistake you might be making is with the structure of the array. Note that tax_query has a nested array. This nested structure allows you to make complex queries across multiple taxonomies (for example, if you add tags to your books), but when you’re only querying one taxonomy it’s easy to miss.

5. Adding a meta_query with custom fields

If you are using custom fields or Advanced Custom Fields with your custom post type, you may also be looking to add additional search terms based on your custom field.

This example assumes a custom field called published_year, and that you are looking for posts published this year (using the PHP data() function).

$args = array(
  'post_type'   => 'book', 
  'post_status' => 'publish',
  'orderby' => 'title',
  'order' => 'ASC',
  'posts_per_page' => -1,
  'tax_query' => array(
    array(
      'taxonomy' => 'genre',
      'field' => 'term_id',
      'terms' => $term_id
    )
  ),
  'meta_query' => array(
    array(
      'key'   => 'published_year',
      'value'     => date('Y'),
      'compare'   => '>='
      )
  )
);

6. More information about meta_query and tax_query

If you are trying to do something more complex with meta_query, the constructor reference for WP_Meta_Query is worth examining. The constructor reference for tax_query is also very helpful.

An even more technical resource is the class reference for WP_Meta_Query.

If you find this post helpful, or if it didn’t quite answer your question, please comment below.