, ,

How to Use WP_Query in WordPress (With Examples)

SEO-friendly ALT text: Digital marketing agency specializing in SEO, web design, and social media marketing services.

The short answer

WP_Query is the WordPress class for writing your own query: you choose exactly which posts, pages or custom post types to show, and in what order. Pass it an array of arguments (post type, category, number of posts, custom fields), loop through the results, then call wp_reset_postdata() so the rest of the page works normally.

WP_Query is how you control exactly which content WordPress shows and in what order. This guide covers writing custom queries, the parameters you will use most, keeping queries fast, and the habits that prevent the common bugs. If you’d rather hand this off, our WordPress development services cover custom queries, theme builds, and performance tuning end to end.

Unlocking the Potential of Custom WordPress Queries

A custom query with WP_Query lets you decide exactly what content to show and how to display it. Here is how to build one.

How to Create Your First Custom Query

Here is the basic structure of a WP_Query:

$args = array(
    'post_type'      => 'post',
    'posts_per_page' => 5,
);

$query = new WP_Query( $args );

if ( $query->have_posts() ) {
    while ( $query->have_posts() ) {
        $query->the_post();
        // Output your post content here
    }
    wp_reset_postdata();
}

In this snippet:

  • post_type: Specifies the type of content you want to retrieve, such as posts, pages, or custom post types.
  • posts_per_page: Limits the number of posts retrieved (5 in this example), which is critical for performance.

This example fetches five posts and loops through them using The Loop, a key part of WordPress development that you’ll use frequently. If you want to go deeper on loop structure, see our guide on customizing the WordPress loop.

Why Custom Queries Matter for Your WordPress Site

Custom queries decide what visitors see, so they shape how people use your site. With them you can:

  • Improve navigation: Visitors can find relevant content faster.
  • Optimize loading times: Only load what you need, when you need it.
  • Create tailored experiences: Show different content to different audiences.

On client builds, I mostly use WP_Query to call custom post types and blog rolls onto pages.

SEO-friendly ALT text: Digital marketing agency specializing in SEO, web design, and social media marketing services.
Custom WordPress functions.php file.

Decoding WP_Query Parameters: A Developer’s Guide

WP_Query accepts a long list of parameters for filtering and sorting what it returns. These are the ones you will use most.

Must-Know WP_Query Parameters for Beginners

Every WP_Query starts with a set of parameters, which act as the building blocks of your custom query:

  • post_type: Defines the type of content you’re querying, such as post, page, or custom post type.
  • posts_per_page: Specifies how many posts to show.
  • order and orderby: Control the order of your posts.
  • category_name: Filters posts by category name.
  • tag: Retrieves posts with a specific tag.
  • meta_query: Allows for complex filtering using custom fields.

Together, these cover most of what a typical site needs to show.

Mixing and Matching Parameters for Ultimate Flexibility

Combining multiple parameters enables you to fine-tune your queries. Here’s a more advanced example:

$args = array(
    'post_type'      => 'product',
    'posts_per_page' => 10,
    'order'          => 'ASC',
    'category_name'  => 'featured',
);

$query = new WP_Query( $args );

In this scenario:

  • post_type: Fetches content from a custom post type called “product.”
  • posts_per_page: Limits the result to 10 items.
  • order: Specifies ascending order, ideal for chronological lists.
  • category_name: Ensures only “featured” products are displayed.

Combining parameters this way lets one query do the filtering, instead of fetching extra posts and sorting them out later in PHP.

Complete WP_Query Args Reference (Cheat Sheet)

This is the scannable reference most developers bookmark. Every parameter below is grouped by type, with its default value and a quick-copy example. Use it to build any query without digging through the Codex.

Post & Page Parameters

ParameterWhat it doesDefaultQuick copy
post_typeContent type to query'post''post_type' => 'page'
post_statusStatus filter'publish''post_status' => 'draft'
pSingle post by ID(none)'p' => 42
nameSingle post by slug(none)'name' => 'hello-world'
page_idSingle page by ID(none)'page_id' => 7
posts_per_pageNumber of posts10 (or Settings value)'posts_per_page' => 6
post__inLimit to specific IDs(none)'post__in' => array(1,2,3)
post__not_inExclude specific IDs(none)'post__not_in' => array(9)
post_parentChildren of a parent ID(none)'post_parent' => 12

Taxonomy, Category & Tag Parameters

ParameterWhat it doesDefaultQuick copy
catCategory by ID(none)'cat' => 4
category_nameCategory by slug(none)'category_name' => 'news'
category__inPosts in any listed cat ID(none)'category__in' => array(2,5)
category__not_inExclude category IDs(none)'category__not_in' => array(8)
tagTag by slug(none)'tag' => 'wordpress'
tag_idTag by ID(none)'tag_id' => 15
tax_queryCustom taxonomy filtering(none)(see deep dive below)

Ordering, Pagination & Meta Parameters

ParameterWhat it doesDefaultQuick copy
orderbySort field'date''orderby' => 'title'
orderSort direction'DESC''order' => 'ASC'
meta_keyCustom field key (also enables sort by meta)(none)'meta_key' => 'price'
meta_valueCustom field value(none)'meta_value' => 'yes'
meta_compareComparison operator'=''meta_compare' => '>='
meta_queryComplex custom-field filtering(none)(see deep dive below)
pagedCurrent page number1'paged' => $paged
offsetSkip N posts(none)'offset' => 3
nopagingReturn all, ignore pagingfalse'nopaging' => true

Date Parameters

ParameterWhat it doesDefaultQuick copy
date_queryFilter by date range(none)'date_query' => array(array('after'=>'2024-01-01'))
yearFour-digit year(none)'year' => 2024
monthnumMonth number (1-12)(none)'monthnum' => 6
dayDay of month (1-31)(none)'day' => 15

Performance Parameters

ParameterWhat it doesDefaultQuick copy
fieldsReturn only what you need'' (full objects)'fields' => 'ids'
no_found_rowsSkip pagination count queryfalse'no_found_rows' => true
update_post_meta_cachePre-load post metatrue'update_post_meta_cache' => false
update_post_term_cachePre-load term datatrue'update_post_term_cache' => false

Keep this section bookmarked. It covers the wp_query args you’ll reach for most of the time.

Common meta_compare operators: =, !=, >, >=, <, <=, LIKE, NOT LIKE, IN, NOT IN, BETWEEN, NOT BETWEEN, EXISTS, NOT EXISTS.

Professional insight

Filter in the query, not after it

The mistake I see most often from other developers is loading everything at once and then filtering it afterwards, or passing the query the wrong context.

Run your meta filters inside the query itself with meta_query. The database does the filtering, WordPress only carries back what you asked for, and load time drops significantly.

WordPress development built on lean custom queries

Using WP_Query with Custom Post Types: What You Need to Know

Custom post types extend what WordPress can store, and WP_Query is how you pull them onto a page.

How to Display Custom Post Types Like a Pro

If your site relies on custom post types, you’ll need to use WP_Query to fetch and display them properly:

$args = array(
    'post_type'      => 'portfolio',
    'posts_per_page' => 3,
);

$query = new WP_Query( $args );

if ( $query->have_posts() ) {
    while ( $query->have_posts() ) {
        $query->the_post();
        // Your custom content output
    }
    wp_reset_postdata();
}

In this code:

  • post_type: Fetches posts from your “portfolio” custom post type.
  • posts_per_page: Limits the display to three items, ideal for a compact portfolio showcase.

Pro Tips for Handling Custom Post Types

  • Proper Registration: Always register your custom post types using register_post_type(). This ensures that your content is queryable and accessible.
  • Taxonomy Filtering: Use taxonomies (custom or built-in) to further refine your content. Example:
$args = array(
    'post_type' => 'portfolio',
    'tax_query' => array(
        array(
            'taxonomy' => 'project_type',
            'field'    => 'slug',
            'terms'    => 'web-design',
        ),
    ),
);

$query = new WP_Query( $args );

This query fetches portfolio items tagged with the “web-design” taxonomy, making it easier to organize and display your content.

Example showing a page building using the WordPress Query as the foundation of the WordPress loop for a page
Example showing a page being built using the WordPress Query as the foundation of the WordPress loop for a page

tax_query and meta_query: The Deep Dive

These two parameters are where WP_Query does its most useful work, and where most developers get stuck. Both accept nested arrays with a relation key (AND or OR) that controls how conditions combine.

Multiple Taxonomy Conditions with AND / OR

Want posts that match two taxonomies at once? Set 'relation' => 'AND'. Want either? Use 'OR'.

$args = array(
    'post_type' => 'portfolio',
    'tax_query' => array(
        'relation' => 'AND',
        array(
            'taxonomy' => 'project_type',
            'field'    => 'slug',
            'terms'    => 'web-design',
        ),
        array(
            'taxonomy' => 'industry',
            'field'    => 'slug',
            'terms'    => array( 'healthcare', 'finance' ),
            'operator' => 'IN',
        ),
    ),
);

$query = new WP_Query( $args );

This returns web-design projects that are also tagged healthcare or finance. Swap 'AND' for 'OR' to widen the net.

meta_query Compare Operators (LIKE, IN, BETWEEN)

meta_query supports far more than =. The compare key sets how each value is matched:

$args = array(
    'post_type'  => 'event',
    'meta_query' => array(
        'relation' => 'AND',
        array(
            'key'     => 'price',
            'value'   => array( 50, 200 ),
            'compare' => 'BETWEEN',
            'type'    => 'NUMERIC',
        ),
        array(
            'key'     => 'location',
            'value'   => array( 'atlanta', 'marietta', 'roswell' ),
            'compare' => 'IN',
        ),
        array(
            'key'     => 'title_text',
            'value'   => 'workshop',
            'compare' => 'LIKE',
        ),
    ),
);

$query = new WP_Query( $args );

Nested Query Relationships

You can nest a meta_query (or tax_query) inside another to build compound logic, such as “A AND (B OR C)”:

$args = array(
    'post_type'  => 'property',
    'meta_query' => array(
        'relation' => 'AND',
        array(
            'key'     => 'status',
            'value'   => 'available',
        ),
        array(
            'relation' => 'OR',
            array(
                'key'     => 'beds',
                'value'   => 3,
                'compare' => '>=',
                'type'    => 'NUMERIC',
            ),
            array(
                'key'     => 'has_pool',
                'value'   => 'yes',
            ),
        ),
    ),
);

$query = new WP_Query( $args );

This finds available properties that have either 3+ bedrooms or a pool.

WP_Query Examples You Can Use Right Now

Here are two common cases.

Your Go-To Example for Simple Post Lists

Fetching the latest three posts is a common requirement:

$args = array(
    'posts_per_page' => 3,
);

$query = new WP_Query( $args );

if ( $query->have_posts() ) {
    while ( $query->have_posts() ) {
        $query->the_post();
        echo '<h2>' . get_the_title() . '</h2>';
        echo '<p>' . get_the_excerpt() . '</p>';
    }
    wp_reset_postdata();
}

This code outputs the titles and excerpts of the latest three posts, making it easy to set up a “Latest News” or “Recent Articles” section.

Advanced Filtering: Making Complex Queries Simple

To create more sophisticated queries, combine different filters:

$args = array(
    'post_type'  => 'event',
    'meta_query' => array(
        array(
            'key'     => 'event_date',
            'value'   => date( 'Y-m-d' ),
            'compare' => '>=',
            'type'    => 'DATE',
        ),
    ),
    'orderby' => 'meta_value',
    'order'   => 'ASC',
);

$query = new WP_Query( $args );

This query pulls upcoming events only and orders them by date, which suits an event calendar.

Developer coding AI-driven automation solutions to streamline workflows and integrate SaaS platforms for small business efficiency.

Filtering Posts with WP_Query: From Basics to Advanced

You can filter by category, tag or custom field.

Filtering Posts by Category or Tag

Categories and tags are standard in WordPress. Here’s how to use them:

$args = array(
    'category_name'  => 'news',
    'posts_per_page' => 5,
);

$query = new WP_Query( $args );

This example fetches five posts from the “news” category, making it ideal for category-specific sections of your site.

Going Deep with Custom Field Filtering

Custom fields let you take filtering to another level:

$args = array(
    'meta_query' => array(
        array(
            'key'   => 'featured',
            'value' => 'yes',
        ),
    ),
);

$query = new WP_Query( $args );

This query pulls posts marked as “featured” using a custom field, which is useful for a highlights section.

Making Pagination Work with WP_Query

If your site has a lot of content, pagination keeps each page light and easy to browse.

Simple Steps to Add Pagination

Add the paged parameter to your query to paginate your content:

$paged = ( get_query_var( 'paged' ) ) ? get_query_var( 'paged' ) : 1;

$args = array(
    'posts_per_page' => 10,
    'paged'          => $paged,
);

$query = new WP_Query( $args );

Using get_query_var('paged') ensures the correct page number is retrieved from the URL.

For complete pagination, add navigation links:

echo paginate_links( array(
    'total' => $query->max_num_pages,
) );

These links let visitors move between pages of results.

Optimizing WP_Query: Tips to Keep Your Site Fast

Slow queries slow the whole page, especially on busy sites. Here is how I keep them fast.

Reducing Load with Efficient Queries

Fetching too much data slows your site. Always limit the number of posts. My own rule is no more than 20 results per call, because anything past that bloats the load and everything the page has to carry afterwards:

$args  = array( 'posts_per_page' => 5 );
$query = new WP_Query( $args );

Use specific filters like post_type and meta_query to reduce the burden on your database. I also run queries in small bursts rather than all at once, so they don’t overload the site.

Leveraging Caching to Boost Speed

Caching saves your database from executing repeated queries. Here’s how to use transients:

$posts = get_transient( 'custom_query_posts' );

if ( false === $posts ) {
    $args  = array( 'posts_per_page' => 5 );
    $query = new WP_Query( $args );
    set_transient( 'custom_query_posts', $query->posts, 12 * HOUR_IN_SECONDS );
}

By caching query results for 12 hours, you significantly reduce server load.

Professional insight

Cap the query, then clean up after it

I limit every custom query to no more than 20 results at a time. On a large site a query could match thousands of posts, and the cap stops it from carrying all of them.

Transients cache results, but they also pile up. About once a week I clear out stale transient data with WP-Optimize so the options table stays lean.

WordPress builds and care plans that stay fast

Free resource

The Pre-Launch Website Checklist

The build is the visible part. The next question is what has to be true before it goes live, and it is a longer list than most projects plan for.

Get the pre-launch checklist

Troubleshooting & Best Practices for WP_Query

Beyond the basics, a handful of habits separate clean, fast queries from slow, buggy ones. Follow these every time.

Always Reset Post Data

After any custom loop that calls the_post(), run wp_reset_postdata(). Skipping it leaves the global $post object pointing at your last queried item, which corrupts the main loop, widgets, and any query that runs afterward. It is one of the most common WP_Query bugs, so make it a habit.

Avoid Querying the Main Loop Unnecessarily

Don’t spin up a new WP_Query just to change what’s already on the page (a category archive, the homepage feed, search results). That runs the database twice. To alter the main query, use the pre_get_posts hook instead (covered below). Reserve new WP_Query for secondary content: sidebars, related posts, custom sections.

Limit Fields with fields and no_found_rows for Speed

Two parameters give you easy performance wins:

  • 'fields' => 'ids': When you only need post IDs (for example, to feed another function), this skips building full post objects and cuts memory use and query time.
  • 'no_found_rows' => true: WP_Query normally runs a second SQL_CALC_FOUND_ROWS query to support pagination. If you’re not paginating, set this to true and skip the extra query entirely.
$args = array(
    'post_type'      => 'product',
    'posts_per_page' => 8,
    'fields'         => 'ids',
    'no_found_rows'  => true,
);

$query = new WP_Query( $args );

On non-paginated, ID-only queries this is often the fastest WP_Query you can write.

Mastering the pre_get_posts Hook for Ultimate Control

The pre_get_posts hook lets you change the main query before it runs.

My rule: use pre_get_posts when you want to modify the existing main query before WordPress hits the database. Use a new WP_Query when you want to add a separate, secondary query that displays extra content on a page.

How to Customize Queries with pre_get_posts

Use this hook to change what content is displayed on specific pages:

function modify_main_query( $query ) {
    if ( is_home() && $query->is_main_query() ) {
        $query->set( 'posts_per_page', 5 );
    }
}
add_action( 'pre_get_posts', 'modify_main_query' );

This function modifies the main query on the homepage to show only five posts.

Creative Use Cases for pre_get_posts

Other uses for pre_get_posts:

  • Filter search results to include only posts, not pages.
  • Modify custom post type archives to display in a specific order.
  • Limit posts on category archives for better performance.

WP_Query vs. get_posts vs. query_posts: Which to Use

WordPress has three ways to query posts, and picking the wrong one costs performance or breaks pagination. Here is how they differ.

Quick Comparison

FunctionBest forSide effectsVerdict
WP_QueryFull-control secondary queries, loops, paginationNone: runs independently of the main queryUse this
get_postsLightweight, read-only lists (sidebars, footers)None: a thin wrapper around WP_Query, returns an arrayUse for simple lists
query_posts(Modifying the main query)Re-runs the main query, breaks pagination, hurts performanceAvoid

Why WP_Query Is the Preferred Choice

WP_Query is the most flexible way to query posts in WordPress. It lets you build a query for exactly what you need without interfering with the main global query, so it is the right tool whenever you need The Loop, pagination, or complex filtering.

When get_posts Is the Right Call

get_posts is a simplified wrapper around WP_Query that returns an array of post objects. Reach for it when you need a quick, read-only list and don’t need The Loop or pagination, such as a “recent posts” sidebar or a footer widget. It’s lighter to write, but it lacks the full flexibility of WP_Query.

$args = array(
    'numberposts' => 5,
    'category'    => 3,
);

$recent_posts = get_posts( $args );

foreach ( $recent_posts as $post ) {
    setup_postdata( $post );
    echo '<h2>' . get_the_title() . '</h2>';
}
wp_reset_postdata();

Why query_posts Is Discouraged

query_posts overrides and re-runs the main query. That second database call slows the page, breaks pagination, and interferes with default behavior, especially on pages running multiple queries. There’s almost never a good reason to use it. To alter the main query, use pre_get_posts. For everything else, use WP_Query.

function modify_main_query( $query ) {
    if ( ! is_admin() && $query->is_main_query() && is_home() ) {
        $query->set( 'posts_per_page', 10 );
    }
}
add_action( 'pre_get_posts', 'modify_main_query' );

Summary: Use WP_Query for full control and flexibility, get_posts for lightweight read-only lists, and avoid query_posts whenever possible.

Block Themes: Query Loop Block or WP_Query?

In block themes I use the Query Loop block. That is what WordPress expects when you are building with blocks. If you are writing classic theme template files, WP_Query is still the tool to use.

Common WP_Query Mistakes and How to Avoid Them

Experienced developers make these too. Here are the big three.

1. Forgetting wp_reset_postdata()

The classic. After a custom loop, the global $post object stays stuck on your last queried post. Other loops, template tags, and widgets then output the wrong content. Fix: call wp_reset_postdata() immediately after every custom loop’s closing brace.

$query = new WP_Query( $args );

if ( $query->have_posts() ) {
    while ( $query->have_posts() ) {
        $query->the_post();
        // Custom loop content
    }
    wp_reset_postdata(); // Never skip this
}

If you need extra control on complex templates, store the global first and restore it manually:

global $post;
$original_post = $post; // Store

// ... your custom WP_Query loop ...

$post = $original_post; // Restore
setup_postdata( $post );

2. Using posts_per_page => -1

Setting -1 returns every matching post. On a small site that’s harmless; on a site with thousands of posts it can exhaust memory and crash the page. Fix: always set an upper bound (I cap my own queries at 20 with posts_per_page => 20), or paginate. If you genuinely need all IDs, pair a high limit with 'fields' => 'ids' and 'no_found_rows' => true to keep it cheap.

3. Modifying the Main Query Incorrectly

Spinning up a new WP_Query to “replace” what’s already on an archive or homepage means the database runs twice and pagination breaks. Fix: modify the main query with pre_get_posts (and always guard it with ! is_admin() and $query->is_main_query() so you don’t affect the dashboard or secondary loops).

Resetting WP_Query: A Crucial Step You Can’t Skip

Failing to reset WP_Query can lead to conflicts, especially when working with global variables.

wp_reset_postdata vs. wp_reset_query Explained

After a custom query, always reset post data:

if ( $query->have_posts() ) {
    while ( $query->have_posts() ) {
        $query->the_post();
        // Output your content
    }
    wp_reset_postdata();
}
  • wp_reset_postdata(): Resets global post data to ensure subsequent queries work as expected.
  • wp_reset_query(): Resets the main query, but it should only be used in specific situations.

WP_Query Examples: Ready-to-Use Code Snippets

Below are five ready-made examples covering authors, categories, date queries and ordering, each with a short explanation.

Example 1: Display Posts by a Specific Author in Descending Order

This example showcases how to use the WP_Query class to fetch posts written by a specific author, ordered by date in descending order.

$args = array(
    'author'         => 2, // Replace 2 with the author's ID
    'posts_per_page' => 5,
    'orderby'        => 'date',
    'order'          => 'DESC',
);

$query = new WP_Query( $args );

if ( $query->have_posts() ) {
    while ( $query->have_posts() ) {
        $query->the_post();
        echo '<h2>' . get_the_title() . '</h2>';
        echo '<p>Written by: ' . get_the_author() . '</p>';
    }
    wp_reset_postdata();
}

Explanation:

  • author: Filters posts by the author’s ID. Replace 2 with the desired author ID from your WordPress database.
  • orderby: Orders the posts by the publication date.
  • order: Specifies descending order (DESC), showing the latest posts first.
  • wp_reset_postdata(): Ensures that global post data is reset after the custom query loop.

Example 2: Query Posts from a Specific Category with Author Information

Here’s how to display posts from a specific category, along with the author’s name and a custom excerpt.

$args = array(
    'category_name'  => 'technology', // Replace with your category slug
    'posts_per_page' => 3,
    'orderby'        => 'title',
    'order'          => 'ASC',
);

$query = new WP_Query( $args );

if ( $query->have_posts() ) {
    while ( $query->have_posts() ) {
        $query->the_post();
        echo '<h2>' . get_the_title() . '</h2>';
        echo '<p>By: ' . get_the_author() . '</p>';
        echo '<p>' . get_the_excerpt() . '</p>';
    }
    wp_reset_postdata();
}

Explanation:

  • category_name: Filters posts by the category slug, such as “technology.”
  • orderby: Sorts posts alphabetically by title (title).
  • order: Specifies ascending order (ASC), useful for a directory-style listing.
  • The loop outputs the post title, author name, and an excerpt.

Example 3: Fetch Posts Using a Date Query in Descending Order

Use this example to pull posts published after a certain date, ordered by most recent first.

$args = array(
    'date_query' => array(
        array(
            'after'     => 'January 1, 2023',
            'inclusive' => true,
        ),
    ),
    'posts_per_page' => 5,
    'orderby'        => 'date',
    'order'          => 'DESC',
);

$query = new WP_Query( $args );

if ( $query->have_posts() ) {
    while ( $query->have_posts() ) {
        $query->the_post();
        echo '<h2>' . get_the_title() . '</h2>';
        echo '<p>Published on: ' . get_the_date() . '</p>';
    }
    wp_reset_postdata();
}

Explanation:

  • date_query: Filters posts published after a specified date (after parameter). The inclusive flag includes posts published on the exact date.
  • orderby: Uses the date to order posts.
  • order: Descending order (DESC) ensures that newer posts appear first.

This query is ideal for displaying recent content since a specific date.

Example 4: Retrieve Posts in a Custom Order with Multiple Parameters

You can sort by more than one field in a single query.

$args = array(
    'post_type'      => 'post',
    'posts_per_page' => 10,
    'orderby'        => array(
        'author' => 'ASC',
        'date'   => 'DESC',
    ),
);

$query = new WP_Query( $args );

if ( $query->have_posts() ) {
    while ( $query->have_posts() ) {
        $query->the_post();
        echo '<h2>' . get_the_title() . '</h2>';
        echo '<p>Author: ' . get_the_author() . ' | Published on: ' . get_the_date() . '</p>';
    }
    wp_reset_postdata();
}

Explanation:

  • orderby: An array used to order posts first by the author in ascending order (ASC), then by date in descending order (DESC).
  • posts_per_page: Limits the number of posts to 10, optimizing the query for performance.

Use this when content needs sorting by author first and date second.

Example 5: Query Posts with a Category and Date Constraint

This final example demonstrates a query that filters posts by category and only shows posts published before a specific date.

$args = array(
    'category_name' => 'news',
    'date_query'    => array(
        array(
            'before'    => 'December 31, 2023',
            'inclusive' => true,
        ),
    ),
    'posts_per_page' => 7,
    'order'          => 'DESC',
);

$query = new WP_Query( $args );

if ( $query->have_posts() ) {
    while ( $query->have_posts() ) {
        $query->the_post();
        echo '<h2>' . get_the_title() . '</h2>';
        echo '<p>Published: ' . get_the_date() . '</p>';
    }
    wp_reset_postdata();
}

Explanation:

  • category_name: Restricts posts to the “news” category.
  • date_query: Only includes posts published before December 31, 2023.
  • order: The descending order (DESC) ensures the most recent posts within the date range are displayed.

Useful for archiving past content or creating a historical news section.

Change the parameters in these examples to fit your own post types, categories and dates.

Can WP_Query Replace Plugins?

In most cases, yes. Many “dynamic content” plugins simply wrap WP_Query behind a visual interface, and they often add layers of abstraction, extra server requests and bloated markup.

By writing your own queries:

  • You reduce plugin bloat
  • You gain full control over layout and logic
  • You avoid breaking your site during plugin updates

That means less time troubleshooting and fewer surprises when something stops working, because you are building on core WordPress functionality.

I am very much against using plugins unless I absolutely need one. When I moved my own site to my own builder, GF Builder, I removed all but 2 plugins, because I can loop through the content and drive everything with my own code. On client builds, custom queries do the same job, so the only plugins a site needs are for security, forms and SEO. I don’t even use Site Kit: I would rather connect to Google’s APIs directly, which cuts load time further. Fewer plugins means less maintenance, faster pages and fewer security holes.

🧱 Want to learn how this fits into your project? Book a custom WordPress consultation now.

Here’s a real-world example:

Let’s say you’re on a page about Commercial Painting in Atlanta. At the bottom of the page, you want to display relevant case studies from Atlanta that showcase commercial projects.

Instead of adding those manually, you can use WP_Query to auto-pull them like this:

$city = 'atlanta';
$service = 'commercial-painting';

$q = new WP_Query([
  'post_type' => 'case_study',
  'posts_per_page' => 3,
  'tax_query' => [
    'relation' => 'AND',
    [
      'taxonomy' => 'service_location',
      'field'    => 'slug',
      'terms'    => $city
    ],
    [
      'taxonomy' => 'related_services',
      'field'    => 'slug',
      'terms'    => $service
    ]
  ]
]);

You can display each result as a linked card with title, excerpt, and featured image, which keeps the page fresh and passes PageRank to your proof pages without a plugin or manual updates.

Professional insight

Let your blog keep your service pages fresh

A trick I use on client sites: allow categories to be assigned to pages as well as posts, then use that category in the query on each service page.

Every time you publish a blog post in that category, the matching service page updates with it. Google sees the page content changing and staying fresh, so it is more likely to serve that page.

Technical SEO that keeps service pages current

Wrapping Up How to Use WP_Query in WordPress

WP_Query gives you control over what each page shows. Use it for secondary content, use pre_get_posts for the main query, keep every query capped and filtered in the database, and always reset post data afterwards.

Not sure whether all this custom work is worth it for your business? Our breakdown of what small businesses really need from WordPress in 2025 puts the cost-versus-value question in perspective.

If you would rather have this built for you, our WordPress development services cover custom queries, block themes and performance work.

Written by

Andrew Buccellato

Andrew Buccellato is the owner and lead developer at Good Fellas Agency. With over 10 years of self-taught experience in web design, SEO, digital marketing, and workflow automation, he helps small businesses grow smarter, not just bigger. Andrew specializes in building high-converting WordPress websites and marketing systems that save time and drive real results.