How7o
  • Home
  • Tools
  • Prank Screens
  • Learn
  • Blog
  • Contact
Reading: How to Apply pre_get_posts on Custom Post Types in WordPress
Share
How7oHow7o
Font ResizerAa
  • OS
Search
  • Home
  • Tools
  • Prank Screens
  • Learn
  • Blog
  • Contact
Follow US
© 2024–2026 How7o. All rights reserved.
How7o > Free Laravel, PHP, WordPress & Server Tutorials > 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.
[mc4wp_form]
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
Most Popular
Laravel Eloquent ORM — a model class mapping to a database table with query methods
Laravel Eloquent ORM: The Complete Guide to Querying Your Database
June 16, 2026
Set vi as the default editor in Ubuntu — a terminal opening the vim editor
How to Set vi (Vim) as the Default Editor in Ubuntu
June 8, 2026
rsync says ALL DONE but files are missing — a terminal showing ALL DONE next to an empty folder
rsync Says “ALL DONE” but Files Are Missing: How to Verify
June 8, 2026
Migrate a website to a new server with rsync — files copying from an old server to a new one over SSH
How to Migrate a Website to a New Server With rsync
June 8, 2026
Bun runtime — faster JS toolkit replacing npm in Laravel projects
How to Install Bun Runtime on Ubuntu (And Use It in a Laravel Project)
May 24, 2026

You Might Also Like

CSS print styles shown in a clean print preview layout
Web Development

How to Add CSS Print Styles for Printer and Print Screen

7 Min Read
WooCommerce SKU search — posts_search filter injecting SKU-matched product IDs
Web Development

How to Include SKU in WooCommerce Search

8 Min Read
Fix MySQL CONCAT returning NULL with COALESCE or CONCAT_WS
Web Development

How to Handle MySQL CONCAT Returning NULL

4 Min Read
Nginx 502 with recv() failed 104 connection reset — PHP-FPM timeout and memory tuning
Server Management

Fix Nginx ‘recv() failed (104: Connection reset by peer)’ with FastCGI

9 Min Read
How7o

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

Tools

  • Age Calculator
  • Word Counter
  • Image Upscaler
  • Password Generator
  • QR Code Generator
  • See all tools→

Pranks

  • Fake Blue Screen Prank
  • Hacker Typer
  • Fake iMessage Generator
  • Windows XP Crash Prank
  • Windows 11 Update Prank
  • See all prank screens →

Company

  • About Us
  • Blog
  • Contact
  • Privacy Policy
  • Terms of Service
  • Sitemap
© 2024–2026 How7o. All rights reserved.
Welcome Back!

Sign in to your account

Username or Email Address
Password

Lost your password?