How7o
  • Home
  • Tools
  • Prank Screens
  • Contact
  • Blog
Reading: How to Apply pre_get_posts on Custom Post Types in WordPress
Share
Subscribe Now
How7oHow7o
Font ResizerAa
  • Marketing
  • OS
  • Features
  • Guide
  • Complaint
  • Advertise
Search
  • Home
  • Tools
  • Prank Screens
  • Contact
  • Blog
Follow US
Copyright © 2014-2023 Ruby Theme Ltd. All Rights Reserved.
How7o > Blog > Web Development > How to Apply pre_get_posts on Custom Post Types in WordPress
Web Development

How to Apply pre_get_posts on Custom Post Types in WordPress

how7o
By how7o
Last updated: May 10, 2026
8 Min Read
WordPress pre_get_posts scoped to a custom post type with is_post_type_archive
SHARE

To scope a wordpress pre_get_posts custom post type hook, check $query->is_post_type_archive('your_type') inside the callback — it returns true only on that archive URL and the main query. Without a scope check, pre_get_posts fires on every WP_Query in the request (widgets, related posts, REST calls) and leaks your ordering or filter into queries you didn’t mean to touch. This guide shows the three common check forms, the mandatory guards around them, and when to modify tax_query safely.

Contents
  • TL;DR
  • The three guards that belong on every pre_get_posts
  • Check 1 — $query->is_post_type_archive()
  • Check 2 — is_post_type_archive() conditional tag
  • Check 3 — $query->get('post_type')
  • Modifying meta_query and tax_query
  • Also-useful scope checks
  • Frequently asked questions
  • Related guides
  • References

Last verified: 2026-04-23 on WordPress 6.5 and PHP 8.3. Originally published 2022-07-17, rewritten and updated 2026-04-23.

TL;DR

add_action( 'pre_get_posts', 'how7o_customize_cpt_archive' );

function how7o_customize_cpt_archive( $query ) {
    if ( is_admin() || ! $query->is_main_query() ) {
        return;
    }

    if ( $query->is_post_type_archive( 'custom_post_type' ) ) {
        $query->set( 'meta_key', 'votes' );
        $query->set( 'orderby', 'meta_value_num' );
        $query->set( 'order', 'DESC' );
    }
}

The three guards that belong on every pre_get_posts

  • is_admin() — skip the hook in the WordPress admin. The admin list tables are their own WP_Querys; unless you specifically want to alter them, stay out.
  • $query->is_main_query() — skip secondary queries. Widgets, block queries, related posts, REST API endpoints all fire through WP_Query and hit the hook too. The main query is the one WordPress built from the URL.
  • A scope check for the specific page(s) you want to affect — one of the three forms below.

Skip any of these and the customization leaks. The cost of the guards is one extra condition per request.

Check 1 — $query->is_post_type_archive()

if ( $query->is_post_type_archive( 'event' ) ) {
    // runs on /events/ URL, main query
}

Returns true when the request is the custom-post-type archive (typically /events/ if your CPT uses has_archive => true). Doesn’t fire for single posts of that type, taxonomy archives, or search results that happen to include that type. This is the form you want 90% of the time.

Check 2 — is_post_type_archive() conditional tag

if ( is_post_type_archive( 'event' ) ) {
    // same result as $query->is_post_type_archive('event')
}

Same semantics, global function form. $query->is_post_type_archive() is more explicit inside a pre_get_posts callback — it’s obviously asking about the query being processed, not some ambient global state — so prefer the method form. The global tag is there for compatibility with older codebases.

Check 3 — $query->get('post_type')

if ( $query->get( 'post_type' ) === 'event' ) {
    // fires any time a query targets the 'event' post type
}

Reads the post_type query var directly. Fires anywhere the query specifies that post type — archive pages, some REST endpoints, explicit new WP_Query(['post_type' => 'event']) calls. Use this when you want the customization to apply broadly, not just on the archive URL. Combine with is_main_query to still avoid affecting secondary queries.

Watch out when your CPT is accessed via multiple query shapes — post_type can be a string ('event') or an array (['event', 'post']) on queries that mix types. For the array case: in_array('event', (array) $query->get('post_type'), true).

wordpress pre_get_posts custom post type — three scope checks compared

Modifying meta_query and tax_query

$query->set('meta_query', [...]) replaces the existing value. On archive pages WordPress may have already built part of the query (for example the tax_query on a category archive), and overwriting wipes that. Merge instead:

// Merge with existing, don't replace
$meta_query = $query->get( 'meta_query' ) ?: array();

$meta_query[] = array(
    'key'     => 'featured',
    'value'   => '1',
    'compare' => '=',
);

$query->set( 'meta_query', $meta_query );

Same pattern for tax_query. The ?: [] gives you a usable starting value when nothing was there yet, and the append leaves any existing clauses alone.

Also-useful scope checks

$query->is_home()           // the blog page
$query->is_category()       // any category archive
$query->is_category( 'news' ) // specifically the 'news' category
$query->is_tag( 'featured' ) // a specific tag archive
$query->is_tax( 'genre' )    // a custom-taxonomy archive
$query->is_search()          // search results
$query->is_author()          // author archive

Combine with the three mandatory guards (is_admin / is_main_query / scope) to target exactly the archive you want to modify.

Frequently asked questions

What’s the simplest wordpress pre_get_posts custom post type check?

$query->is_post_type_archive('custom_type') inside a pre_get_posts callback. It returns true only when the request is the archive for that custom post type — not for the regular blog, category pages, taxonomy pages, or individual posts. Combine it with is_main_query() and the !is_admin() guard so you only modify the intended frontend query.

When should I use is_post_type_archive vs $query->get('post_type')?

is_post_type_archive is the higher-level answer — it reflects the URL context (is this the /events/ archive page?), so it naturally fits the main query on archive URLs. $query->get('post_type') === 'event' is a lower-level check that answers “is this query specifically for events?” — useful when you want the hook to also fire on secondary queries, REST endpoints, or admin-list screens where archive semantics don’t apply.

Why does the hook change all my default posts when I skip the scope check?

pre_get_posts fires on every WP_Query — the main query, widget queries, related-posts queries, REST API queries, dashboard widgets. Without a scope check (is_main_query, is_post_type_archive, etc.), your ordering or filter leaks into all of those and you get unexpected output everywhere. Treat scope checks as mandatory.

Should I check is_admin() or !is_admin()?

Skip the hook in admin (if (is_admin()) return;) when your changes are user-facing (frontend post ordering, frontend filtering). Run the hook in admin when you’re intentionally modifying the admin list table (hiding posts from a certain role, changing the default sort in Posts → All Posts). The default is to skip — most customizations are frontend-only.

Can I modify meta_query or tax_query in pre_get_posts?

Yes, using $query->set('meta_query', [...]) and $query->set('tax_query', [...]). Be careful: set replaces the existing value. If you want to add a clause without wiping whatever WordPress already computed (category archive’s tax_query, for example), read the current value first, merge, then set: $tax_query = $query->get('tax_query') ?: []; $tax_query[] = [...]; $query->set('tax_query', $tax_query);.

Related guides

  • How to Order Posts by Meta Value in WordPress — the most common pre_get_posts customization.
  • How to Get Posts by Date Range in WordPress — adding date_query via pre_get_posts.
  • How to Get the Current Category ID in WordPress — context for taxonomy archive checks.
  • How to Disable Revisions and Autosave in WordPress — another filter-driven customization.

References

WordPress developer reference for pre_get_posts and WP_Query conditional methods: developer.wordpress.org/reference/hooks/pre_get_posts.

TAGGED:phpwordpress

Sign Up For Daily Newsletter

Be keep up! Get the latest breaking news delivered straight to your inbox.
By signing up, you agree to our Terms of Use and acknowledge the data practices in our Privacy Policy. You may unsubscribe at any time.
Share This Article
Facebook Copy Link Print
Previous Article WordPress logged-in menu swap — register_nav_menus + wp_nav_menu with is_user_logged_in ternary How to Display Different Menus to Logged-In Users in WordPress
Next Article WordPress custom avatar without a plugin — media uploader writes user meta, get_avatar filter renders the image How to Change a User Profile Picture in WordPress Without a Plugin
Leave a Comment

Leave a Reply Cancel reply

You must be logged in to post a comment.

FacebookLike
XFollow
PinterestPin
InstagramFollow

Subscribe Now

Subscribe to our newsletter to get our newest articles instantly!
Most Popular
Display PHP errors — ini_set + php.ini configuration
How to Display PHP Errors
May 10, 2026
PHP convert string to uppercase — strtoupper and mb_strtoupper
How to Convert a String to Uppercase in PHP
May 10, 2026
PHP string to float conversion with cast, regex cleanup, NumberFormatter
How to Convert a String to Float in PHP
May 10, 2026
PHP merge arrays without duplicates — union operator and array_unique
How to Combine Two Arrays Without Duplicates in PHP
May 10, 2026
PHP delete array element — unset, array_splice, array_filter, array_search
How to Delete an Element from a PHP Array
May 10, 2026

You Might Also Like

CSS page break for printing shown in a print preview layout
Web Development

CSS Page Break for Printing: How to Split a Web Page Into Multiple Printed Pages

6 Min Read
WordPress order posts by meta value — WP_Query or pre_get_posts with meta_value_num
Web Development

How to Order Posts by Meta Value in WordPress

8 Min Read
WooCommerce product view counter — meta-based counter with increment and display hooks
Web Development

How to Display a Product View Counter in WooCommerce Without a Plugin

7 Min Read
Laravel global variable for views — View::share in AppServiceProvider and View::composer wildcard patterns
Web Development

How to Set a Global Variable for Laravel Views

7 Min Read
How7o

We provide tips, tricks, and advice for improving websites and doing better search.

Latest News

  • SEO Audit Tool
  • Client ReferralsNew
  • Execution of SEO
  • Reporting Tool

Resouce

  • Google Search Console
  • Google Keyword Planner
  • Google OptimiseHot
  • SEO Spider

Get the Top 10 in Search!

Looking for a trustworthy service to optimize the company website?
Request a Quote
Welcome Back!

Sign in to your account

Username or Email Address
Password

Lost your password?