How to use WP_Query without breaking the main Query

it’s unclear when to use one or the other, or if they must be used at
all

  • If you’ve modified the global $wp_query variable (e.g. using query_posts() which should NOT actually be used), then use wp_reset_query() to restore the variable back to the main query, i.e. so that the variable references or points to the main query again, and for example when you call have_posts(), it would check against the main query (whether it contains any posts) and not some other/custom query.

    // This (should NOT be done and it) modifies the $wp_query global.
    query_posts( 'posts_per_page=2' );
    
    while ( have_posts() ) {
        the_post();
        the_title( '<h3>', '</h3>' );
    }
    
    // This restores the $wp_query global back to the main query.
    wp_reset_query();
    
  • If you’ve modified the global $post variable (e.g. using setup_postdata() or by calling the the_post() method in a secondary/custom WP_Query instance), then use wp_reset_postdata() to restore the variable back to the current post in the main query, so that for example calling the_title() would display the title of the second post in the main query and not the last post in a secondary query.

    // Case 1: Calling the the_post() method in a custom WP_Query instance.
    
    $query = new WP_Query( 'posts_per_page=2' );
    
    while ( $query->have_posts() ) {
        // This modifies the $post global.
        $query->the_post();
    
        the_title( '<h3>', '</h3>' );
    }
    
    // Restore the $post global back to the current post in the main query.
    wp_reset_postdata();
    
    // Case 2: Using setup_postdata().
    
    global $post;
    $posts = get_posts( 'posts_per_page=2' );
    
    foreach ( $posts as $post ) {
        // This modifies the $post global.
        setup_postdata( $post );
    
        the_title( '<h3>', '</h3>' );
    }
    
    // Restore the $post global back to the current post in the main query.
    wp_reset_postdata();
    

    So in the two examples above, we only called wp_reset_postdata() and there’s no need to call wp_reset_query() because neither of the two queries modified the main query (or the global $wp_query variable).

Therefore, if the $wp_query global is not modified, then no need to call wp_reset_query(), and if the $post global is not modified, then no need to call wp_reset_postdata(). I.e. Call just one of them.

Additional Notes

  • query_posts() will change the main query and is not recommended. Only use if absolutely necessary. Creating a new instance of WP_Query or get_posts() is preferred for secondary loops. If you would like to modify the main query, use the pre_get_posts action.

  • In a pre_get_posts hook, you can use the is_main_query() method in the passed query object, to check whether the current query is the main query. Same goes with hooks like posts_orderby which passes (if requested) the query object to the hook callbacks.

  • The hooks in WP_Query also fires on the admin side (wp-admin), so you can use is_admin() to avoid changing the main query on the admin, unless of course if that is something you intended to do.

And I’m not exactly sure the main points in your question (or the exact kind of answer that you’re looking for), but I hope this answer helps. 🙂