How to Sort Posts within a WordPress Taxonomy

Published in WordPress.

Watch our video

There is a better version of your web

Comparison of two variants of the same page using A/B testing

Share this post

So far Nelio has published several plugins in the WordPress.org repository. Some of them are completely free and offer elegant solutions to common problems, such as, for example, Nelio Maps, which allows you to insert a Google map on your pages or posts created with Gutenberg, and Nelio Compare Images, which allows you to insert a block to, as it names suggests, compare two images side by side. We also published a couple of premium plugins that help us pay the bills: Nelio Content and Nelio A / B Testing.

If you take a look at our website, you will see that our two premium plugins occupy a prominent place, as they both have a few landing and pricing pages and they both have a related knowledge base where users can find the answer to their questions. Today we’ll be talking about how we created the documentation pages for Nelio A/B Testing and Nelio Content and what we had to do to sort questions as we please.

How to create custom post types in WordPress

First, let’s create a custom post type to keep track of all the questions our knowledge base will contain. You can see the result in the following screenshot, where there’s an instance of this new post type for Nelio A/B Testing’s knowledge base:

Nelio A/B Testing's Knowledge base
Example of a question in Nelio A/B Testing’s Knowledge Base.

To create a custom post type, all you have to do is use the WordPress register_post_type function as follows:

function nelio_add_help_type() {
  $labels = array(
    'name'          => __( 'Questions', 'nelio-help' ),
    'singular_name' => __( 'Question', 'nelio-help' ),
    'menu_name'     => __( 'Testing’s Help', 'nelio-help' ),
    'all_items'     => __( 'All Questions', 'nelio_help' ),
    'add_new_item'     => __( 'Add New Question', 'nelio_help' ),
    'edit_item'     => __( 'Edit Question', 'nelio_help' ),
    'view_item'     => __( 'View Question', 'nelio_help' ),
  );

  register_post_type( 'nab_help', array(
    'capability_type' => 'post',
    'labels'          => $labels,
    'map_meta_cap'    => true,
    'menu_icon'       => 'dashicons-welcome-learn-more',
    'public'          => true,
    'show_in_rest'    => true,
    'supports'        => [ 'title', 'editor', 'author' ],
  ) );
}
add_action( 'init', 'nelio_add_help_type' );

and voilà! You may already know there are plugins like Advanced Custom Fields that help you create and customize the interface of a custom post types, but I don’t necessarily recommend it to implement such a simple example.

By the way, if you’re wondering where this snippet should be placed, don’t miss our tutorial on how to customize WordPress with custom plugins.

How to create a new taxonomy to organize content

Our knowledge base is organized in “Topics” and “Keywords,” as you can see in the following screenshot:

Screenshot of the WordPress Dashboard showing our new post type with its two taxonomies: Topics and Keywords.
Screenshot of the WordPress Dashboard showing our new post type Testing’s Help with its two taxonomies: Topics and Keywords.

Topics and Keywords are the equivalent of the Categories and Tags that you have by default in WordPress posts. Let’s just focus on the former, as the latter is exactly the same.

As you can already imagine, a Topic is a custom taxonomy we have created for our custom content type. To add a new taxonomy to a WordPress post type, you simply have to use the register_taxonomy function as follows:

function nelio_add_help_taxonomy() {
  $labels = array(
    'name'          => __( 'Topics', 'nelio-help' ),
    'singular_name' => __( 'Topic', 'nelio-help' ),
    'menu_name'     => __( 'Topics', 'nelio-help' ),
    'all_items'     => __( 'All Topics', 'nelio_help' ),
    /* ... */
  );

  register_taxonomy( 'nab_topic', [ 'nab_help' ], array(
    'hierarchical'      => true,
    'label'             => __( 'Topic', 'nelio-help' ),
    'query_var'         => true,
    'show_admin_column' => false,
    'show_ui'           => true,
    'show_in_rest'      => true,
  ) );
}
add_action( 'init', 'nelio_add_help_taxonomy' );

The function is quite straightforward to use. First, you name the taxonomy (nab_topic in our example). Then you specify the post type(s) it’s related to (we only have one: nab_help). Finally, you add a few more arguments to customize the taxonomy. The result is a new taxonomy with which to create the concrete Topics we need to categorize the questions in our knowledge base:

Screenshot of a custom taxonomy in the WordPress Dashboard.
Screenshot of our custom taxonomy (Topics).

How to sort the posts in a Taxonomy

Now that we have a custom taxonomy and we can add questions to each Topic we create, how do we sort those questions within a specific topic? Well, I’m afraid we’ll have to code a little bit…

Taking a look at the database

WordPress and their relationships with your posts are stored in four different database tables:

WordPress 2.5 added a new numeric field in the wp_term_relationships table, term_order, which allows us to specify the position that a certain element occupies within the taxonomy. This sounds like the candidate we were looking for to sort our questions within our topic… but there’s a catch: as far as I can tell, WordPress doesn’t offer a standard mechanism to (1) define the value of the term_order field and (2) use it to actually sort the posts within a certain taxonomy. Let’s fix this!

How to use the term_order field to sort the posts included in a taxonomy

Let’s suppose we were able to somehow set the desired term_order values we want, as you can see in the following screenshot:

Screenshot of the order we see in the database and the one that actually appears on the web.
The order that we see in the database (and that is what we would expect to see) is different than the one we actually get when browsing our website.

By default, WordPress ignores this field and the posts aren’t sorted in the front-end. That’s why the three questions shown in the screenshot above aren’t properly sorted.

Luckily, we can easily tell WordPress to use the field to sort the posts by it. Just use the posts_orderby filter to tweak the query that retrieves the posts belonging to a certain term so that it includes a sorting request:

add_filter( 'posts_orderby', 'nelio_sort_questions_in_topic', 99, 2 );
function nelio_sort_questions_in_topic( $orderby, $query ) {
  if ( ! nelio_is_topic_tax_query( $query ) ) return;
  global $wpdb;
  return "{$wpdb->term_relationships}.term_order ASC";
}

function nelio_is_topic_tax_query( $query ) {
  if ( empty( $query->tax_query ) ) return;
  if ( empty( $query->tax_query->queries ) ) return;
  return in_array(
    $query->tax_query->queries[0]['taxonomy'],
    [ 'nab_topic' ],
    true
  );
}

All the previous snippet does is to check that we’re querying a certain taxonomy (in our case, nab_topic) and, when we do, we add an ORDER BY clause in the query so that the final result is ordered by the value of the term_order attribute found in the $wpdb->term_relationships table:

Screenshot of the results (ordered) of a taxonomy
Screenshot of (sorted) posts within a taxonomy.

Implementation of a user interface to order the contents of a taxonomy

Finally, we only have one thing left to do. In the previous section, we assumed we were able to somehow set the correct value in the term_order field. But how? Is there a screen or option somewhere to set this value in a user-friendly manner? As far as I know, there isn’t. But we can create one.

In my opinion, the best solution to this problem would be to have a UI that allows me to select the taxonomy that I want to sort, and then drag and drop all the questions to establish their order. Something like this, go:

User interface to easily sort the posts we have in a certain taxonomy.
User interface to easily sort the posts we have in a certain taxonomy.

To achieve the desired effect we must implement the following steps:

  • Register a new page on the WordPress dashboard
  • Implement the function that will render the taxonomy selector and the posts within the selected taxonomy
  • Add a tiny JavaScript snippet so that the UI is responsive
  • Create a new callback to save the resulting order once we’re done sorting the posts

You can make this interface as complicated as you want, using the new WordPress development stack. But today I’m going to outline a quick and dirty solution that gets the job done.

As I was saying, the first thing is to register the page where we will put the interface. We can easily do this with the add_submenu_page function during the admin_menu action:

add_action( 'admin_menu', function() {
  add_submenu_page(
    'edit.php?post_type=nab_help',
    'Sort',
    'Sort',
    'edit_others_posts',
    'nab-help-question-sorter',
    __NAMESPACE__ . '\render_question_sorter'
  )
} );

The render_question_sorter method i also extremely simple:

function render_question_sorter() {
  echo '<div class="wrap"><h1>Sort Questions</h1>';

  $terms = get_terms( 'nab_topic' );
  render_select( $terms );
  foreach ( $terms as $term ) {
    render_questions_in_term( $term );
  }
  render_script();

  echo '</div>';
}

As you can see, we simply retrieve all terms in the nab_topic taxonomy and then we rely on three auxiliary functions to (1) render the selector, (2) render the questions in each category and (3) add a small script to make everything dynamic.

Rendering the selector is as easy as iterating over each term and render an option:

function render_select( $terms ) {
  echo '<select id="topic">';
  foreach ( $terms as $term ) {
    printf(
      '<option value="%s">%s</option>',
      esc_attr( $term->slug ),
      esc_html( $term->name )
    );
  }
  echo '</select>';
}

To render the questions we have in each term of the taxonomy, we launch a query to the database and iterate through all of them:

function render_questions_in_term( $term ) {
  printf(
    '<div id="%s" class="term-block" data-term-id="%d">',
    esc_attr( $term->slug ),
    esc_attr( $term->term_id )
  );

  echo '<div class="sortable">'
  $query = new WP_Query( array(
    'post_type'     => 'nab_help',
    'post_per_page' => -1,
    'tax_query'     => array(
      array(
        'taxonomy' => 'nab_topic',
        'field'    => 'term_id',
        'terms'    => $term->term_id,
        'orderby'  => 'term_order',
      ),
    ),
  ) );
  while ( $query->have_posts() ) {
    $query->the_post();
    global $post;
    printf(
      '<div class="question" data-question-id="%d">%s</div>',
      esc_attr( $post->ID ),
      esc_html( $post->post_title )
    );
  }
  echo '</div>';

  printf(
    '<input type="button" data-term-id="%s" value="%s" />',
    esc_attr( $term->term_id ),
    esc_attr( 'Save' )
  );
  echo '</div>';
}

Note this function renders a div that uses the slug of the current $term as its ID, it groups the questions in yet another div element, and it finally includes a Save button. All these details will be useful when we finally render the script that wires our app.

Once everything’s ready, we simply need to add some JavaScript magic. In particular, this quick and dirty UI requires two scripts (jquery and jquery-ui) to implement the drag-and-drop functionality and then the following JS snippet:

( function() {
  // MAKE QUESTIONS SORTABLE
  $( '.sortable' ).sortable();


  // TERM SELECTOR
  const select = document.getElementById( 'topic' );
  const termBlocks = [ ...document.querySelectorAll( '.term-block' ) ];
  select.addEventListener( 'change', showSelectedTermBlock );

  // Helper function
  function showSelectedTermBlock() { /* ... */ }


  // SAVE BUTTONS
  [ ...document.querySelectorAll( '.term-block input[type="button"]' ) ].forEach( addButtonLogic );


  // Helper function
  function addButtonLogic( button ) {
    const termId = button.getAttribute( 'data-term-id' );
    button.addEventListener( 'click', () => {
      button.disabled = true;
      const selector = `div[data-term-id="${ termId }"] .question`;
      const ids = [ ...document.querySelectorAll( selector ) ]
        .map( ( question ) => question.getAttribute( 'data-question-id' );
      $.ajax( {
        url: ajaxurl,
        method: 'POST',
        data: {
          action: 'nelio_save_tax_sorting',
          objectIds: ids,
          termId,
        },
      } ).always( () => button.disable = false );
    }
  }
} )()

At first glance, the previous snippet might seem complicated. But I promise: it isn’t. First, we make our questions “sortable” by using jQuery UI’s sortable function. Then, we add a listener to our select component that shows/hides the appropriate set of questions using a helper function. Finally, we add the click listeners to our Save buttons so that, when the user clicks on them, the order in which the questions are sorted is stored in the database.

As you can see, saving this new order is done via an AJAX request, which means we need to implement its PHP callback counterpart:

add_action( 'wp_ajax_nelio_save_tax_sorting', __NAMESPACE__ . '\save_tax_sorting' );
function save_tax_sorting() {
  $term_id = isset( $_POST['termId'] ) ? absint( $_POST['termId'] : 0 );
  if ( ! $term_id ) die();

  $object_ids = isset( $_POST['objectIds'] ) ? $_POST['objectIds'] : [];
  if ( ! is_array( $object_ids ) || empty( $object_ids ) ) die();
  $object_ids = array_values( array_map( 'absint', $object_ids ) );

  global $wpdb;
  foreach ( $object_ids as $order => $object_id ) {
    $wpdb->update(
      $wpdb->term_relationships,
      array( 'term_order' => $order + 1 ),
      array(
        'object_id'        => $object_id,
        'term_taxonomy_id' => $term_id,
      ),
    );
  }
  die();
}

And that’s it! Quite easy, huh?

In summary

WordPress doesn’t offer a default mechanism to sort posts within a certain taxonomy. But that’s OK! We can implement one ourselves: all we have to do to sort posts in a WordPress taxonomy is set the term_order field in the term_relationships database table and then extend WordPress with the posts_orderby filter so that it uses said field.

Finally, we have outlined a simple UI to easily populate the term_order field by dragging and dropping the posts included in a certain taxonomy.

I hope you liked this tutorial and if you did, please share it with your friends and colleagues. And, as always, if you have any questions, leave them in the comment section below and I’ll be glad to address them.

Featured image by Steve Johnson on Unsplash.

9 thoughts on “How to Sort Posts within a WordPress Taxonomy

  1. Thank you for great tutorial. Could you please put your code to file and publish it in a repo? That would really help. Thank you in advance!

  2. Waiting for repo 😉

      1. Thank you very much! Works like a charm!

      2. I would buy a plugin which will help me to soft my custom posts according to my custom taxonomies. Together with ACF it is a great mix! I also would need a good php code example of how to print my data on the site.

        It is very useful in many cases. Your great article and code examples is a working prototype!

        Thank you a lot!

  3. Is it possible to have one question in few different topics?

Leave a Reply

Your email address will not be published. Required fields are marked: •

I have read and agree to the Nelio Software Privacy Policy

Your personal data will be located on SiteGround and will be treated by Nelio Software with the sole purpose of publishing this comment here. The legitimation is carried out through your express consent. Contact us to access, rectify, limit, or delete your data.