When you visit a WordPress site and request a page like a homepage, a single blog post, an archive listing, or a search results page—WordPress goes through a detailed, organized process to figure out what content to display. This operation revolves around something called the “main query.” Understanding how the main query works gives you powerful insight into how WordPress decides what content to fetch from the database, which template to use, and how you can customize these steps.
In this article, we’ll describe how to Process WordPress Main Query and Global Variables when a page loads, highlight the key global variables involved ($wp, $wp_query, and $wp_rewrite), and explain how they interact. We’ll also provide practical code examples, share best practices, discuss common pitfalls, and include a Q&A section before the conclusion. By the end, you will have a clear understanding of what happens behind the scenes whenever someone loads a page on your WordPress site.
Every time a user requests a page from a WordPress site, WordPress needs to determine what content to show. Is it the front page of the blog? A single post or page? A category archive? A search results page? WordPress answers these questions by constructing and executing what’s known as the “main query.”
The main query is like the master plan that WordPress uses to fetch posts or pages matching the user’s request. It sets up a set of query variables (like post_type=post or category_name=news) and uses these to query the database. After running the main query, WordPress decides which template file to load from your theme’s directory and how to structure the final HTML.
Without the main query, WordPress would have no systematic way to determine what content you’re asking for and how to display it. Whether you’re a theme developer, a plugin author, or a site owner wanting to understand how WordPress works, knowing how the main query is processed and which variables control it is invaluable.
Three key global variables help orchestrate the main query process:
These three globals collaborate to translate the URL requested by a visitor into database queries and finally select the right template to render the content.

Before WordPress even considers the main query, it sets up the environment: loading core files, connecting to the database, loading plugins, and initializing the active theme. Only after this groundwork is laid does WordPress start working on the main query.
Here’s a simplified rundown:
When a user visits example.com/my-post/, $wp_rewrite tries to match this URL against its rewrite rules, maybe there’s a rule that says: if the path segment after the domain looks like a post slug, interpret it as ?post_type=post&name=my-post.
If the user visits example.com/category/news/, $wp_rewrite matches this structure to ?category_name=news. For search requests like example.com/?s=keyword, WordPress directly uses the s query variable.
In the absence of pretty permalinks, $wp_rewrite needs to do more work. You might see URLs like example.com/?p=123, which already include query variables.
After $wp determines the query variables, it sets $wp_query->query_vars. For example, if the user requested a single post named “my-post,” $wp_query->query_vars might look like:
$wp_query->query_vars = array(
'name' => 'my-post',
'post_type' => 'post',
);
For a category archive, it might be:
$wp_query->query_vars = array(
'category_name' => 'news',
'post_type' => 'post',
);
For a search results page:
$wp_query->query_vars = array(
's' => 'keyword',
'post_type' => 'post'
);
Next, $wp_query transforms these query vars into an SQL query. For a single post, it might be something like:
$wp_query->query_vars = array(
's' => 'keyword',
'post_type' => 'post'
);
For a category archive, the query is more complex, joining wp_term_relationships and wp_term_taxonomy tables to find all posts in that category. Search queries seek to find posts that have been given titles or that contain messages that are within the search term.
After running the query, $wp_query stores the results in $wp_query->posts. It also sets properties like $wp_query->found_posts and $wp_query->max_num_pages. Based on the result, $wp_query sets flags like $wp_query->is_single, $wp_query->is_archive, $wp_query->is_search, and so forth. Themes and plugins rely on these conditions to decide what template to show and how to format the content.
With the main query results ready, WordPress triggers the template_redirect hook. Developers can modify or redirect templates at this stage if needed. After that, WordPress uses the Template Hierarchy to find the appropriate template file.
For a single post, it might load single.php, or if the post type is a book, it might load single-book.php. For a category archive named “news,” it might load category-news.php or fall back to archive.php.
Finally, the selected template runs The Loop:
if ( have_posts() ) :
while ( have_posts() ) : the_post();
// Display the post content
endwhile;
else :
// Show "No posts found" message
endif;
have_posts() and the_post() rely heavily on $wp_query to know if there are posts to display.
Developers often need to adjust the main query before it runs. For example, you might want to:
The best place to do this is the pre_get_posts action. This hook fires after WordPress sets the query variables but before $wp_query runs the database query. Here’s an example:
function my_custom_pre_get_posts( $query ) {
// Be sure that this is the main query and not an admin request
if ( $query->is_main_query() && !is_admin() ) {
// If on the homepage, show only 5 posts
if ( $query->is_home() ) {
$query->set( 'posts_per_page', 5 );
}
// For the category "news," only show posts from 2021
if ( $query->is_category( 'news' ) ) {
$query->set( 'year', 2021 );
}
}
}
add_action( 'pre_get_posts', 'my_custom_pre_get_posts' );
This code modifies $wp_query before it fetches posts, giving you fine control over the main query without editing WordPress core files.
If you need a different set of posts for a sidebar widget or a featured section on the homepage, you might create a custom query using the new WP_Query(). Keep in mind that $wp_query always refers to the main query. Your custom queries should be stored in separate variables to avoid confusion. After running a custom query, remember to call wp_reset_postdata() to restore $wp_query to the main query’s state.
$featured_query = new WP_Query( array( 'category_name' => 'featured', 'posts_per_page' => 3 ) );
if ( $featured_query->have_posts() ) {
while ( $featured_query->have_posts() ) : $featured_query->the_post();
// Display featured posts
endwhile;
}
wp_reset_postdata();
It ensures $wp_query and global template tags still refer to the main query after you’re done with the custom query.
Conditional tags like is_home(), is_single(), is_archive(), and is_search() depend on $wp_query flags. These tags let you check what kind of page is being viewed and adjust your output accordingly. They are typically used in theme template files:
if ( is_archive() ) {
echo "<h1>Browsing the Archive</h1>";
} elseif ( is_single() ) {
echo "<h1>Reading a Single Post</h1>";
}
Understanding that these checks rely on $wp_query helps you avoid confusion. If you run a custom query and don’t reset the post data, the conditional tags might reflect the custom query’s state rather than the main query’s state.

The main query is a standard operation that WordPress optimizes well. However, consider these tips:
If something doesn’t look right—maybe the wrong posts appear, or a certain template isn’t loading as expected—you can debug the main query:
Enable WP_DEBUG:
In wp-config.php:
define( 'WP_DEBUG', true );
define( 'WP_DEBUG_LOG', true );

Check Query Vars:
You can print $wp_query->query_vars using var_dump():
global $wp_query;
var_dump( $wp_query->query_vars );
If you set the front page to a static page (Settings > Reading), WordPress adjusts the main query accordingly. Instead of showing posts, $wp_query fetches the specified front page as a single page. If you have a “Blog” page set for posts, visiting that page triggers $wp_query to retrieve your latest posts.
If you register a custom post type (CPT) named portfolio and want those items to appear on the homepage, you can use pre_get_posts:
function add_portfolio_to_home( $query ) {
if ( $query->is_main_query() && $query->is_home() && !is_admin() ) {
$query->set( 'post_type', array( 'post', 'portfolio' ) );
}
}
add_action( 'pre_get_posts', 'add_portfolio_to_home' );
Now, your homepage shows both standard posts and portfolio items.
If you want category archives to display 20 posts instead of the default:
function custom_category_posts_per_page( $query ) {
if ( $query->is_main_query() && $query->is_category() && !is_admin() ) {
$query->set( 'posts_per_page', 20 );
}
}
add_action( 'pre_get_posts', 'custom_category_posts_per_page' );
It ensures category listings show more posts.
The main query is at the heart of how WordPress decides what content to show users. By working closely with three global variables—$wp, $wp_query, and $wp_rewrite—WordPress can parse friendly URLs, map them to internal query parameters, run a database query, and determine which template to load. This intricate process makes WordPress both powerful and flexible.
By understanding this process, you can customize the main query to display the content you want—be it limiting posts on the homepage, filtering category archives, or showing custom post types alongside regular posts. Looks like pre_get_posts let you alter the main query before it runs, and conditional tags let you tailor your themes and plugins based on the type of content being displayed.
God willing, this in-depth exploration helps you gain confidence and skill when working under the hood of WordPress, enabling you to create better themes, more functional plugins, and a smoother visitor experience.

Hassan Tahir wrote this article, drawing on his experience to clarify WordPress concepts and enhance developers’ understanding. Through his work, he aims to help both beginners and professionals refine their skills and tackle WordPress projects with greater confidence.