How do I set my custom loop to only show top-level posts? I have a hierarchical custom post type and the archive page shows both the parent and child posts.

3 Answers 3


This solution is based on some code by Justin Tadlock. pre_get_posts is called before WordPress gets the posts of the main loop. Basically, you test to see if the page is the archive of the post type and make sure post_parent hasn't been set. Then you set post_parent to 0, which is the default parent of top level posts. Easy as pie.

    //pre_get_posts filter is called before WordPress gets posts
    add_filter( 'pre_get_posts', 'my_get_posts' );
    function my_get_posts( $query ) {
        //If the user is viewing the frontend, the page is an archive and post_parent is not set and post_type is the post type in question
        if ( ! is_admin() && is_archive() && false == $query->query_vars['post_parent'] &&  $query->query_vars['post_type'] === 'my_post_type')
            //set post_parent to 0, which is the default post_parent for top level posts
            $query->set( 'post_parent', 0 );
        return $query;
  • You should also check to see if the user is in the admin area or the frontend with is_admin() or only top-level pages will show in the admin post list. Sep 16, 2020 at 15:27

You can just add post_parent=0 to your query

  • Yeah that's what I first thought, but it does not always work for some reason.
    – MTT
    Nov 11, 2011 at 18:04
  • 2
    $args = array( ... 'post_parent' => 0, .... ); does the trick. Quick and Easy.
    – Mav2287
    Aug 17, 2019 at 20:05

Elaborating from @Ryan's post, the key is setting post_parent=0 and post_type='page'.

You can always view the SQL request of the WP_Query Object to see what arguments you need to add to get your desired results.

This code works for me:

$args=array('post_parent' => 0, // required
                'post_type' => 'page', // required
                'orderby' => 'menu_order', // to display according to hierarchy
                'order' => 'ASC', // to display according to hierarchy
                'posts_per_page' => -1, // to display all because default is 10

    $query = new \WP_Query( $args ); 

    /*  Uncomment to see the resulting SQL to debug
    echo $query->request; die();

    if ( $query->have_posts() ) {
        while($query->have_posts()) {
            echo $post['ID'].': '.$post['post_title'].'<br>';

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.