I'm wondering what is the best way to organize book-page content without involving taxonomies. So for example, I have a math book with a bunch of problems that I wish to list. I have a custom post type qa which can include a taxonomy <book-name>, and I want the URL to be: <domain>/qa/<book-name>/<page-number>/<problem-number>. So creating such a structure isn't a big deal except the <page-number> - there will be lots of pages, and I think that it is not practical to create so many taxonomies. So what will be the optimal implementation? I'd appreciate any suggestions.

1 Answer 1


I don't know if this is the optimal, it's one suggestion.

For me I'd created the qa post type as hierachical.

Then for every page in the book I'd created a child qa post using menu_order field to handle the page number.

This child has a 3rd level child for problems.

Hooking intro pre_get_posts for qa archive, you can set the post_parent to 0, so in the archive view you get only books, and not the big list of pages and problems.

Last thing to is create a rewrite rule that convert the url into some query vars.


<domain>/qa/<book-name>/<page-number>/<problem-number> should be rewritten in


That url will end in a very simple query where post id = <problem-number>.

Example code:

function my_book_rules() {
  add_rewrite_rule( 'qa/([^/]+)/([0-9]+)/([0-9]+)$', 'index.php?&post_type=qa&name=$matches[1]&number=$matches[2]&problem=$matches[3]', 'top' );
  add_rewrite_rule( 'qa/([^/]+)/([0-9]+)$', 'index.php?&post_type=qa&name=$matches[1]&number=$matches[2]', 'top');
add_action('init', 'my_book_rules');

function my_book_query_vars($vars) {
  $vars[] = 'problem';
  $vars[] = 'number';   
  return $vars;

add_filter('query_vars', 'my_book_query_vars'); 

function my_book_query( $query ) {
  if ( is_admin() || ! is_main_query() ) return;
  if ( is_archive() && $query->get('post_type') == 'qa') {
     $query->set('post_parent', 0);
  } elseif ( is_single() && get_post_type() == 'qa') {
    $post = get_queried_object();
    if ( $post->post_parent == 0 && $query->get('number') && ! $query->get('problem') ) {
      // a page number is requested
      $query->set('name', '');
      global $wpdb;
      $n = intval($query->get('number'));
      $p = (int) $wpdb->get_var("SELECT ID FROM $wpdb->posts WHERE post_status = 'publish' AND post_type = 'qa' AND post_parent = $post->ID AND menu_order = $n");
      $query->set('p', $n);
    } elseif( $query->get('problem') ) {
      // a problem is required
      $query->set('name', '');
      $n = intval($query->get('problem'));
      $query->set('p', $n);
add_action('pre_get_posts', 'my_book_query');

In the single view template single-qa.php you can load sub tempalte for books, pages or problems, something like:

if ( $post->post_parent == 0 ) {
   get_template_part('book', 'content');
} else {
  $parent = get_post( $post->post_parent );
  if ( $parent->post_parent == 0 ) {
    get_template_part('page-number', 'content');
  } else {
    get_template_part('problem', 'content');

Another snippet is for display the book title and the page number inside the problem template (problem-content.php according to previous snippet),

$thepage = get_post( $post->post_parent);
$pagenumber = $thepage->menu_order
$the_book = get_post( $thepage->post_parent )->post_title

echo "The problem " . get_the_title() . 
  " is in the book " . $the_book->post_title .
  " at the page number " . $pagenumber;

Note that the code is a proof of concept and untested.

  • Thank you sir. I've got the idea. I'm new to WP and I want to ask you what does the $query->set('p', $foo); mean? I've chosen to modify the architecture a bit, introducing new table which will hold data about books. Can I create my own query in my_book_query such that the fetched items will be available at single-qa.php? Because WP supplies methods such as the_title() and the_content() in single.php, but can I affect the way these methods work without hardcoding the core files? Can it be done in the my_book_query method?
    – somebody
    Sep 7, 2013 at 14:19
  • I'm asking this because I have to execute a certain queries for my new tables in the db (so I don't have to create this nested structure you've suggested). I just want to execute different query for each option: if it is qa/<book-name> then I'll fetch on my own the pages and I would like to pass the result to single-qa.php. If it is qa/<book-name>/<book-page> I would like to execute a different query, fetching all the problems and sending it, again, to single-qa.php. So can I update somehow the object that operates the whole single-qa.php thing? Thanks.
    – somebody
    Sep 7, 2013 at 14:25
  • I guess I could fetch the book name or book page in single-qa.php using $_SERVER['REQUEST_URI']; and then execute queries for different conditions, but I know that WP has already executed its own queries and there's some data in methods like the_title() and the_content(), so I want to avoid making unnecessary queries. That is why I want to affect the way WP fetches the object that operates single-qa.php. I hope that I explained myself better. Thank you in advance!
    – somebody
    Sep 7, 2013 at 14:31
  • the_title() and other template tags use global $post variable to work. If you use your custom table to store data, you need extra code to make it works. It's hard to explain here in a comment, expecially without knowing how you have structured your table. The built-in query runned by wordpress known as 'main query', can be edited before it run using 'pre_get_posts' hook. My function does that, and query->set is the way to change the WP main query. If you work with your custom table you have to create your custom query and query->set will be un-useful for you, I think. @somebody
    – gmazzap
    Sep 7, 2013 at 15:52

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.