I've a custom post type "ordered_post" and a few posts of this type. which they have an order to display and all of them have a custom field "custom_post_order".

And they all are displayed in correct order in the archives page using the pre_get_filter

add_action('pre_get_posts', 'custom_query_ordered_post');
function custom_query_ordered_post($query) {
    if (!is_admin() && $query->is_main_query() && $query->is_post_type_archive('ordered_post')) {
        $query->set('meta_key', 'custom_post_order');
        $query->set('orderby', 'meta_value');
        $query->set('order', 'DESC');
    return $query;

But in the single page i want to display the "relative posts", but using the abs(meta_value - $current_post_order) to order it.

  • Use posts_orderby.
    – janh
    Feb 6, 2018 at 18:15
  • @janh I've created this WP_Query extended class to do the job as simple as possible, it was just to self answer because i've searched and didn't found about it ("abs function in order by"), so if someone ever needs, this is here in easy way. But thank ! Feb 6, 2018 at 18:22
  • by relative do you mean the next and previous posts?
    – Tom J Nowell
    Feb 6, 2018 at 18:30
  • @TomJNowell Yes. In that "part of the template" Feb 6, 2018 at 18:38

1 Answer 1


Custom Class to do the custom parse

class AS_Query extends \WP_Query {

    protected function parse_orderby($order_by) {
        $additional_allowed = array();
        $absValue = null;
        if (preg_match('/abs\(([0-9]+)\)/i', $order_by, $matches)) {
            $absValue = $matches[1];
            $order_by = sprintf('abs((meta_value+0) - %s)', intval($matches[1]));
            $additional_allowed[] = $order_by;
        if (!in_array($order_by, $additional_allowed, true)) {
            $parent_orderby = parent::parse_orderby($order_by);
            if ($parent_orderby) {
                return $parent_orderby;
            return false;
        return $order_by;

    protected function parse_order($order) {
        if (!is_string($order) || empty($order)) {
            return 'DESC';
        if ('ABS' == strtoupper($order)) {
            return ''; // to get the real closest order, this should be empty
        } else if ('ASC' === strtoupper($order)) {
            return 'ASC';
        } else {
            return 'DESC';


$order= get_field("custom_post_order");
$id = get_the_ID(); //to not show the post itself in the list
if (!empty($order)) {
    require_once get_template_directory() . "/functions/classes/class-as-query.php";
    $diretoria = new AS_Query(
            'order' => 'abs', // to remove "DESC" in the query to get the abs list
            'orderby' => 'abs(' . $order. ')',
            'meta_key' => 'custom_post_order',
            'posts_per_page' => 4, //custom list limit
            'post__not_in' => array($id), //to not show the post itself in the list
            'post_type' => 'ordered_post'

I haven't tested it with arrays of order_by

  • Note that it can be significantly faster to remove the post__not_in, bump the posts per page up by 1, then manually remove it in PHP. NOT IN queries can be nightmarish when it comes to database performance, and are especially bad for DB server load. They might seem okay when you have 5 posts, but as the post count rises, the cost increases sharply
    – Tom J Nowell
    Feb 6, 2018 at 18:32
  • @TomJNowell I didn't know about this. But in this case, my case, I've only 6 posts and will have one more each 3 years (2019 is next, then 2022, then 2025, so on...) So i don't think it will be problem... But i will try to avoid using it in the next things i do. Thanks for it ! Feb 6, 2018 at 18:41

Your Answer

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

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