I think I've got this figured out and I'd like to make sure I'm not missing anything obvious.

My goal is to prioritize a list of results by custom posts with attachments. So, I have a query returning say 60 results and, of those 60, 40 have attachments. I'd like those to show first, then continue with the posts that don't have any attachments.

Given the limitations of the "orderby" parameter my method of attack is as follows:

  1. Create a custom field ("has_attachment") that is updated "true" if the post has any attachments. Tie the update into an action (e.g. "attachment_fields_to_save").

  2. Run a query to set the value to true on posts that already have attachments.

I would like to make this bulletproof and go so far as to empty the field in the event that all attachments to the post are deleted. That's not necessary in my use-case, though, as the likelihood is slim.

Anyway, I'd like to see if any of you have suggestions for improving my method or ideas for a different way to approach this. Otherwise, I'll get to work!

  • Maintaining data about data, except in the case of performance, is generally not a good idea. – h0tw1r3 Feb 26 '12 at 22:25
  • Might be easier with just doing a usort on your results. But without seeing code, it's guessing and theory. – kaiser Feb 26 '12 at 23:23

Surprisingly, Wordpress does not make it very straightforward. Even though lots of people have had similar questions on the wordpress.org forums, I believe yours is unique in that you want to keep the order and sort.

This is by no means quality code, but does exactly what you're looking for.

class WP_Query_Custom extends WP_Query {
    static $filters = array( 'join', 'groupby', 'fields', 'results' );

    function get_posts() {
        $posts = parent::get_posts();
        return $posts;

    function enable_filters() {
        foreach( self::$filters as $filter ) {
            add_filter( 'posts_' . $filter, array( __CLASS__, 'filter_' . $filter ), 10, 1 );

    function disable_filters() {
        foreach( self::$filters as $filter ) {
            remove_filter( 'posts_' . $filter, array( __CLASS__, 'filter_' . $filter ), 10, 1 );

    function filter_join($join) {
        global $wpdb;
        return "{$join} LEFT JOIN {$wpdb->posts} AS post_attachment ON {$wpdb->posts}.ID=post_attachment.post_parent AND post_attachment.post_type='attachment' ";

    function filter_groupby($groupby) {
        global $wpdb;
        if ( ! empty($groupby) ) {
            $groupby = ",$groupby";
        return "{$wpdb->posts}.ID{$groupby}";

    function filter_fields($fields) {
        $fields = "GROUP_CONCAT(post_attachment.ID) AS post_attachments, ".$fields;
        return $fields;

    function filter_results($posts) {
        $num_posts = count($posts);
        $offset = 0;
        $new_posts = array();
        for ( $i = 0; $i < $num_posts; $i++ ) {
            if ( ! empty($posts[$i]->post_attachments) ) {
                array_push( $new_posts, $posts[$i] );
                unset( $posts[$i] );
        return array_merge( $new_posts, $posts );

$query = new WP_Query_Custom('post_type=post');
  • Wow, thank you for that! I will experiment with it and report back. – Jonathan Wold Feb 27 '12 at 2:32
  • That works beautifully! Is it possible to extend the order through pagination? I'm OK with it doing the sort on a per page basis - the ideal, though, would be able to paginate through the listings with all the posts with photos showing up first. – Jonathan Wold Mar 1 '12 at 23:29

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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