Posted on

Search results breakdown by type

Do you want to have a breakdown of search results by post type? This could be used for example to show a list of post types like books, movies, or albums on a review site, and make them links to limit the search results to that post type.

This is fairly easy to do with the relevanssi_hits_filter hook. Here’s a function that can be attached to the hook. It will fill a global variable with the post type breakdown:

add_filter( 'relevanssi_hits_filter', 'search_result_types' );
function search_result_types( $hits ) {
    global $hns_search_result_type_counts;
    $types = array();
    if ( ! empty( $hits ) ) {
        foreach ( $hits[0] as $hit ) {
            relevanssi_increase_value( $types[ $hit->post_type ], 1 );
    $hns_search_result_type_counts = $types;
    return $hits;

Add the function to your site. This function was written by Simon from Lumpy Lemon. Thanks to Simon for sharing the code!

An extended version

The code above has one problem. If the search is restricted to just one post type, the code won’t count the other post types: only those that are present in the search. If you want all post types counted, you can use this version of the function:

add_filter( 'relevanssi_hits_filter', 'search_result_types' );
function search_result_types( $hits ) {
    global $hns_search_result_type_counts, $wp_query, $rlv_doing_this_already;
    if ( ! $rlv_doing_this_already && 'any' !== $wp_query->query_vars['post_type'] ) {
    	$copy_query                          = $wp_query;
    	$copy_query->query_vars['post_type'] = 'any';
    	$rlv_doing_this_already              = true;
    	relevanssi_do_query( $copy_query );
    	return $hits;
    $types = array();
    if ( ! empty( $hits ) ) {
        foreach ( $hits[0] as $hit ) {
            $types[ $hit->post_type ]++;
    $hns_search_result_type_counts = $types;
    return $hits;

This version counts all post types.

51 comments Search results breakdown by type

  1. Great plugin!
    A question: How do i use this function with the relevanssi_hits_filter hook in order to separate results by type? I’m at a loss.

    Is there also an easy way to style the post types?

  2. The code was missing an add_filter() call. I’ve added that.

    Styling post types… Well, the post type is stored in $post->post_type, so you can find it there and use that to style it, for example using it to add a post type dependent class to a div that contains the post.

  3. Thank you for your prompt reply!

    I’ve pasted the code in functions.php, but I’m maybe supposed to do more steps – cause nothing changes?

    My usual approach would be to add multiple loops and style each loop separatly, but iguess that will not work. I’d like some post-types only show as links.

  4. Ah, sorry, misread your questions. This does not separate posts by post type, just creates an array that has the counts of posts per type.

    If you want to separate by type, you need to go through the $hits array, check each post and file it in the correct array by post type, then reconstruct the $hits array from the post type arrays in the order you desire.

    In your loop, check $post->post_type and style the post according to the type. So instead of multiple loops, you’ll have a single loop and a switch clause or something like that.

    1. Hi Mikko,

      can you please go in more detail here? I would like to show my custom post type “products” first, then pages and last posts.

        1. This function doesn’t do anything visible, that’s why.

          It just puts the data in the global variable $hns_search_result_type_counts. You need to write some code to do something with that data in order to see visible results.

          1. Hi Mikko. Can you guide me on how to access the data from that variable? I tried but failed.

          2. Add this to your search results template:

            global $hns_search_result_type_counts;

            It’ll show you what’s inside the variable.

          3. Thank you so much Mikko. My bad because I tried to access it without calling “global”.

  5. Thank you so much for this information! I was able to get it to work. However, I do have a question. Let’s say I have results from 3 post types: posts, media, and products. On the search landing page, it shows “# posts, # media, and # products”. However, when you click on products, let’s say, the hits in the function change, and instead of showing “# products” only. Any way of keeping the original counts for the entirety of the search on the filtered results pages?

    ETA: I’m using Emily Johnson’s pastebin code below, along with the function above.

    Thank you!

  6. Hey Mikko, thanks for the code. I’m on the latest version of WordPress and I get the error “Undefined index” from the line where it adds to the count. Any ideas why?

    1. Of course I figure it out right after I post this. I had to add the following to get the code above to work:

      $types[‘product’] = ”;

      $types[‘lookbook’] = ”;

      $types[‘post’] = ”;

  7. It looks like the extended version needs to be changed from query_vars[‘post_type’] to query_vars[‘post_types’] in order to work.

  8. hi Mikko,
    i am developing a products per page drop-down in woocommerce and am passing a variable with value ?my_select_ppp=60 at the url and it does set the posts_per_page as per the value when it’s not search but if the url has the search set like ?s=’stickers’&post_type=’product’ this variable my_select_ppp doesn’t get added to the given URL and doesn’t do the expected functionality.
    Can you help me in what needs to be done
    i hereby attach the code
    // my select option for no of products to be shown at the shop page


    function my_select(){
    global $wp_query;
    $per_page = filter_input(INPUT_GET,’perpage’,FILTER_SANITIZE_NUMBER_INT);
    echo “Products per Page:”;
    echo “”;
    $orderby_options = array(”=>”,’40’=>’40’,’60’=>’60’);
    foreach($orderby_options as $key=>$value){
    echo “$value”;
    echo “”;

    function my_pre_get_products_query($query){
    $per_page = filter_input(INPUT_GET,’ga_select_ppp’,FILTER_SANITIZE_NUMBER_INT);
    if($query->is_main_query() && !is_admin() ){



  9. You could also just use the following above your loop:

    if ( have_posts() ) {
    $post_types = wp_list_pluck( $posts, ‘post_type’ );
    $post_type_counts = array_count_values( $post_types );

  10. Hello! I tried using the extended version but I get different counts than the actual results. For example, I get a count of `1` for a `podcast` type, but when I go to the results I actually see two different posts.

    Any suggestions on how I can debug further?

    1. Arturo, I would check the parameters to the $copy_query, is there something that is restricting the search results to a smaller set of results? That would be a good place to start.

      1. Thank you for the quick reply Mikko!

        Coincidentally that was the first place I looked into. I used `var_dump` on the original $wp_query and $copy_query and then used a text diff tool to find the differences. The only difference is `post_type = any`, vs. `post_type = podcast`, as expected.

        Another approach I took was to count the results that were returned directly from the call to `relevanssi_do_query($copy_query)`, instead of waiting for the results in the `relevanssi_hits_filter` filter, but I got the same results, as expected I supposed.

        This leads me to believe that the issue might be deeper into how Relevanssi hooks into WordPress search… and… writing this comment got me thinking that I might be able to find a difference in the blog posts results that could help narrow down the issue.

        One difference that I found is that the Blog post that comes back has the key words in the Title, and the one that is shown in the results but not in the count has the keywords in an indexed custom field. So I guess my copied query is not returning results from indexed custom fields. Does that help?

          1. Mikko, thank you for your insight, it really did help me not to go down the wrong rabbit hole.

            The issue was the “Throttle search” option 🤦‍♂️. On the count query the results that landed > 500 where obviously not counted.

  11. Hi there,

    Iam using the latest version of Relevanssi Pro but can’t get this to work. I get the response, that there is no function called “relevanssi_increase_value”. Why is that?

    Tried a lot and it seems many of your custom functions can’t be used or Iam getting something wrong here.

    I’d like to build a simple filtering by post types on the search results page, nothing fancy and without any additional plugins. But Iam failing here because of the error.

    Fatal error: Uncaught Error: Call to undefined function relevanssi_increase_value()

    Thanks for your time.

    1. Hans, first of all – if you’re using Premium, please use the Premium support. It gets you answers faster.

      Second, relevanssi_increase_value() was added in Relevanssi Premium 2.17.0. If you are using an older version, that would explain the problem. The current version is 2.18.0. Which version do you have?

      Third, the function is not important. All it does is to increase the value $types[ $hit->post_type ] by one. You can replace it with

      $types[ $hit->post_type ]++;

      1. Dear Mikko,

        thanks for your answer. Sorry, it seems we’ve got a pretty old version, but there is no update notice. My colleague installed it long time ago, I wasn’t aware of the premium support.
        I will make sure to update it. And in the meantime, I got it working with the extended function above. Thanks to you and the other commenters, I now have a great search results page.

        Very great support here. I hope this helps someone else.

        Best Regards

  12. I’m using your first code. It works just fine. But it causes this PHP warning:

    2023/04/02 17:00:01 [error] 29595#29595: *1807802 FastCGI sent in stderr: “post_type” on int in /var/www/site/wp-content/themes/twentytwenty-child/functions.php on line 40
    PHP message: PHP Warning: Attempt to read property “post_type” on int in /var/www/meow/wp-content/themes/twentytwenty-child/functions.php on line 40

    Any ideas? I’d appreciate any help. Thank you.

    1. Your search is returning post IDs, not post objects. Replace relevanssi_increase_value( $types[ $hit->post_type ], 1 ); with

      $hit_object = relevanssi_get_post_object( $hit );
      relevanssi_increase_value( $types[ $hit_object->post_type ], 1 );

      1. Thanks for the quick reply! After doing that I get a fatal error:

        2023/04/02 18:17:33 [error] 29595#29595: *1810296 FastCGI sent in stderr: “PHP message: PHP Fatal error: Uncaught TypeError: substr(): Argument #1 ($string) must be of type string, stdClass given in /var/www/site/wp-content/plugins/relevanssi/lib/utils.php:600

        1. Well, now the $hit is an object, not a post ID. Try this:

          $hit_object = is_object( $hit ) ? $hit : relevanssi_get_post_object( $hit );
          relevanssi_increase_value( $types[ $hit_object->post_type ], 1 );

  13. Ciao a tutti, c’è la possibilità di raggruppare i risultati per post_type, e poi ordinarli per post_type?

    Vorrei qualcosa del genere:
    – prodotto
    – prodotto
    – prodotto
    – post
    – post
    – post
    – etc


      1. Hi Mikko, thanks for the support. I’ve added in my functions.php, replacing news with my cpt, but I see no change in the results. I’m using “SearchWP Live Ajax Search” because “Relevanssi Live Ajax Search” gives me JS errors. Could it be for this?

        1. Check the Relevanssi admin search (Dashboard > Admin search). Are the results correct there? If not, the problem is in your code. If that’s correct, then the problem is in the Live Ajax Search.

          What kind of JS errors are getting from the Relevanssi Live Ajax Search?

          1. Ok in Admin->search works great! The JS problem I have with is this:

            jquery.min.js:2 Uncaught TypeError: Cannot read properties of undefined (reading ‘replace’)
            at s.position_results (script.min.js:1:7167)
            at s.init (script.min.js:1:1498)
            at new s (script.min.js:1:427)
            at HTMLInputElement. (script.min.js:1:8974)
            at Function.each (jquery.min.js:2:3003)
            at S.fn.init.each (jquery.min.js:2:1481)
            at jQuery.fn. [as relevanssi_live_search] (script.min.js:1:8897)
            at HTMLDocument. (script.min.js:1:9133)
            at e (jquery.min.js:2:30158)
            at t (jquery.min.js:2:30460)

          2. Ok, in that case the problem may be that SearchWP Live Ajax Search is not using Relevanssi. I don’t know about that; I haven’t followed with that plugin since I forked Relevanssi Live Ajax Search from it. I also don’t support it anymore.

            It’s hard to say from the JS error what’s up with that; looks like the script can’t find the paddingRight and paddingLeft values of the result element CSS. Why is that, I cannot tell, at least not without seeing your site.

          3. I noticed that, after installing “Relevanssi Live Ajax Search”, inspecting my code I see that the following properties have been automatically added:

            data-swplive=”true” data-swpengine=”default” data-swpconfig=”default” data-rlvlive=”true” data-rlvparentel=”#rlvlive_1″ data-rlvconfig=”default”

            But the div #rlvlive_1 is not created … and neither is #rlvlive.

            Even modifying my form code, as shown here: my changes are ignored….

          4. Ok, now works … i’m on Oxygen Builder, i need to insert this:

            add_filter( ‘relevanssi_live_search_add_result_div’, ‘__return_false’ );

  14. Hi all, is there a possibility to group the results by post_type, and then sort them by post_type?

    I would like something like this:
    – product
    – product
    – product
    – post
    – post
    – post
    – etc


Leave a Reply

Are you a Relevanssi Premium customer looking for support? Please use the Premium support form.

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