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:

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:

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:

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…

Nelio A/B Testing
Native Tests for WordPress
Use your WordPress page editor to create variants and run powerful tests with just a few clicks. No coding skills required.
Taking a look at the database
WordPress and their relationships with your posts are stored in four different database tables:
wp_terms
includes all the terms you create within a certain taxonomy. For example, in our topics taxonomy, we have terms like General Questions, Compatibility, and Payments and Billing.wp_termmeta
helps us to store additional data related to each term. It’s pretty similar towp_postmeta
.wp_term_taxonomy
relates each term inwp_terms
to its defining taxonomy. Thus, for example, this table keeps track of the fact that the General Question term is actually an instance of thenab_topic
taxonomy.wp_term_relationships
relates each term (term_taxonomy_id
) to the posts it “contains” (object_id
). For example, this table tells us that the question about what Nelio A/B Testing is a General Question (and that’s why the question shows up when browsing the topic).
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:

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:

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:

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.
Leave a Reply