Skip to main content

Course Progress

Loading...

🔍 Custom Queries with WP_Query

Master WordPress's powerful query system for custom content retrieval

Learn WP_Query, get_posts, and advanced query techniques

Learning Objectives

  • Understand the three main query methods in WordPress
  • Master WP_Query class and its parameters
  • Create custom loops for specific content
  • Implement multiple loops on a single page
  • Optimize queries for performance
  • Handle query reset and cleanup
  • Build complex queries with meta and taxonomy parameters

WordPress Query Methods

WordPress provides three main methods for querying posts. Understanding when to use each is crucial for efficient theme development.

Method Use Case Returns Affects Main Query Best For
WP_Query Complex custom queries WP_Query object No Secondary loops, widgets
get_posts() Simple post retrieval Array of post objects No Simple lists, dropdowns
query_posts() ⚠️ Avoid using Array of posts Yes (breaks pagination) Never use in themes
Never use query_posts() in themes! It can break pagination, affect performance, and cause unexpected behavior. Use WP_Query or get_posts() instead.

Understanding WP_Query

WP_Query is the heart of WordPress's content retrieval system. Every time WordPress displays posts, it's using WP_Query behind the scenes.

Basic WP_Query Structure

<?php
// 1. Define query arguments
$args = array(
    'post_type'      => 'post',
    'posts_per_page' => 10,
    'orderby'        => 'date',
    'order'          => 'DESC'
);

// 2. Create new query
$custom_query = new WP_Query( $args );

// 3. The Loop
if ( $custom_query->have_posts() ) :
    while ( $custom_query->have_posts() ) : $custom_query->the_post();
        // Display post content
        the_title();
        the_content();
    endwhile;
else :
    echo 'No posts found';
endif;

// 4. Reset post data
wp_reset_postdata();
?>

Best Practice

Always call wp_reset_postdata() after custom queries to restore the global $post object.

Essential WP_Query Parameters

Post Type Parameters

<?php $args = array( // Single post type 'post_type' => 'product', // Multiple post types 'post_type' => array( 'post', 'page', 'product' ), // All post types 'post_type' => 'any', // Include attachments 'post_type' => array( 'post', 'attachment' ), 'post_status' => array( 'publish', 'inherit' ) ); ?>

Pagination Parameters

<?php $args = array( 'posts_per_page' => 10, // Number of posts (use -1 for all) 'nopaging' => false, // Enable/disable pagination 'paged' => get_query_var('paged'), // Current page 'offset' => 5, // Skip first X posts 'page' => 2, // Static page number 'ignore_sticky_posts' => true // Ignore sticky posts ); ?>

Order & Orderby Parameters

<?php $args = array( 'orderby' => 'date', // date, title, menu_order, rand 'order' => 'DESC', // DESC or ASC // Multiple orderby 'orderby' => array( 'menu_order' => 'ASC', 'date' => 'DESC' ), // Order by meta value 'meta_key' => 'price', 'orderby' => 'meta_value_num', 'order' => 'ASC' ); ?>

Author Parameters

<?php $args = array( 'author' => 1, // Author ID 'author_name' => 'john', // Author nicename 'author__in' => array(1,2,3), // Multiple authors 'author__not_in' => array(4,5) // Exclude authors ); ?>

Category Parameters

<?php $args = array( 'cat' => 5, // Category ID 'category_name' => 'news', // Category slug 'category__and' => array(2,6), // Posts in ALL these categories 'category__in' => array(2,6), // Posts in ANY of these categories 'category__not_in' => array(3,7) // Exclude categories ); ?>

Tag Parameters

<?php $args = array( 'tag' => 'cooking', // Tag slug 'tag_id' => 5, // Tag ID 'tag__and' => array(2,3), // Posts with ALL these tags 'tag__in' => array(2,3), // Posts with ANY of these tags 'tag__not_in' => array(4,5), // Exclude tags 'tag_slug__and' => array('news','featured') ); ?>

Custom Taxonomy Parameters

<?php $args = array( 'tax_query' => array( array( 'taxonomy' => 'product_category', 'field' => 'slug', 'terms' => 'electronics', ), // Relationship between multiple taxonomies 'relation' => 'AND', array( 'taxonomy' => 'product_tag', 'field' => 'term_id', 'terms' => array(10, 20), 'operator' => 'IN' ) ) ); ?>

Meta Query (Custom Fields)

<?php $args = array( 'meta_query' => array( 'relation' => 'AND', array( 'key' => 'price', 'value' => 100, 'type' => 'NUMERIC', 'compare' => '>=' ), array( 'key' => 'color', 'value' => 'blue', 'compare' => '=' ), array( 'key' => 'featured', 'value' => 'yes', 'compare' => '=' ) ) ); ?>

Multiple Loops on One Page

Often you'll need multiple queries on a single page - featured posts, recent posts, related posts, etc.

Example: Homepage with Multiple Sections

<?php
// Featured Posts Loop
$featured_args = array(
    'post_type'      => 'post',
    'posts_per_page' => 3,
    'meta_key'       => 'featured',
    'meta_value'     => 'yes'
);
$featured_query = new WP_Query( $featured_args );

if ( $featured_query->have_posts() ) : ?>
    <section class="featured-posts">
        <h2>Featured Posts</h2>
        <?php while ( $featured_query->have_posts() ) : $featured_query->the_post(); ?>
            <article>
                <h3><?php the_title(); ?></h3>
                <?php the_excerpt(); ?>
            </article>
        <?php endwhile; ?>
    </section>
<?php endif;
wp_reset_postdata();

// Recent Posts Loop (excluding featured)
$recent_args = array(
    'post_type'      => 'post',
    'posts_per_page' => 5,
    'meta_query'     => array(
        array(
            'key'     => 'featured',
            'value'   => 'yes',
            'compare' => '!='
        )
    )
);
$recent_query = new WP_Query( $recent_args );

if ( $recent_query->have_posts() ) : ?>
    <section class="recent-posts">
        <h2>Recent Posts</h2>
        <?php while ( $recent_query->have_posts() ) : $recent_query->the_post(); ?>
            <article>
                <h3><?php the_title(); ?></h3>
                <?php the_excerpt(); ?>
            </article>
        <?php endwhile; ?>
    </section>
<?php endif;
wp_reset_postdata();

// Popular Posts Loop (by comment count)
$popular_args = array(
    'post_type'      => 'post',
    'posts_per_page' => 5,
    'orderby'        => 'comment_count',
    'order'          => 'DESC'
);
$popular_query = new WP_Query( $popular_args );

if ( $popular_query->have_posts() ) : ?>
    <section class="popular-posts">
        <h2>Popular Posts</h2>
        <?php while ( $popular_query->have_posts() ) : $popular_query->the_post(); ?>
            <article>
                <h3><?php the_title(); ?></h3>
                <span><?php comments_number(); ?></span>
            </article>
        <?php endwhile; ?>
    </section>
<?php endif;
wp_reset_postdata();
?>

Using get_posts() for Simple Queries

get_posts() is a simpler alternative to WP_Query for basic post retrieval. It returns an array of post objects.

get_posts() Example

<?php
$posts = get_posts( array(
    'numberposts' => 5,
    'category'    => 3,
    'orderby'     => 'date',
    'order'       => 'DESC'
));

foreach ( $posts as $post ) :
    setup_postdata( $post ); ?>
    <article>
        <h2><?php the_title(); ?></h2>
        <?php the_excerpt(); ?>
    </article>
<?php endforeach;
wp_reset_postdata();
?>

When to Use get_posts()

  • Simple lists without pagination
  • Dropdown menus or select boxes
  • Widget content
  • Quick data retrieval for processing

Advanced Query Examples

Posts from Date Range

<?php
$args = array(
    'post_type' => 'post',
    'date_query' => array(
        array(
            'after'     => 'January 1, 2024',
            'before'    => 'December 31, 2024',
            'inclusive' => true,
        ),
    ),
    'posts_per_page' => -1,
);

$year_posts = new WP_Query( $args );
?>

Products by Price Range

<?php
$args = array(
    'post_type'  => 'product',
    'meta_query' => array(
        'relation' => 'AND',
        array(
            'key'     => 'price',
            'value'   => array( 100, 500 ),
            'type'    => 'NUMERIC',
            'compare' => 'BETWEEN'
        ),
        array(
            'key'     => 'in_stock',
            'value'   => 'yes',
            'compare' => '='
        )
    ),
    'orderby'    => 'meta_value_num',
    'meta_key'   => 'price',
    'order'      => 'ASC'
);

$products = new WP_Query( $args );
?>

Query Performance Optimization

Performance Best Practices

  • Limit fields returned: Use 'fields' => 'ids' when you only need IDs
  • Disable unnecessary features: 'no_found_rows' => true when not using pagination
  • Cache queries: Use transients for expensive queries
  • Optimize meta queries: Index custom fields used in queries
  • Limit posts retrieved: Always set reasonable posts_per_page limits

Optimized Query Example

<?php
// Cache expensive query
$cache_key = 'featured_products_' . get_current_blog_id();
$featured_products = get_transient( $cache_key );

if ( false === $featured_products ) {
    $args = array(
        'post_type'      => 'product',
        'posts_per_page' => 10,
        'no_found_rows'  => true,           // Disable pagination info
        'fields'         => 'ids',           // Get only IDs
        'meta_query'     => array(
            array(
                'key'   => 'featured',
                'value' => 'yes'
            )
        )
    );
    
    $query = new WP_Query( $args );
    $featured_products = $query->posts;
    
    // Cache for 12 hours
    set_transient( $cache_key, $featured_products, 12 * HOUR_IN_SECONDS );
}

// Now use the IDs to get full posts
foreach ( $featured_products as $product_id ) {
    $post = get_post( $product_id );
    // Display post
}
?>

Common Pitfalls and Solutions

Pitfall 1: Forgetting wp_reset_postdata()
Solution: Always reset after custom queries
Pitfall 2: Using query_posts()
Solution: Use WP_Query or get_posts() instead
Pitfall 3: Not handling empty results
Solution: Always check have_posts() before looping
Pitfall 4: Inefficient meta queries
Solution: Combine conditions, use proper compare operators

Practice Exercise

💻
Custom Query Challenge

Create a custom homepage template that displays:

  1. One featured post (marked with custom field)
  2. Three recent posts from a specific category
  3. Five most commented posts
  4. Related products (custom post type)
  5. Upcoming events (date-based query)

Requirements:

  • Each section should use a separate WP_Query
  • Properly reset post data after each query
  • Handle empty results gracefully
  • Optimize queries for performance

Additional Resources