Public query vars are those available and usable via a direct URL query.

On my site, I've created some custom URLs with their own query variables like so:


(e.g. list posts that belong to both - the 'Tech' category and 'gadgets' tag)

And have rewrite rules (using add_rewrite_rule) in place to pretty the URLs, for example, like this:


(The category base is removed by default from category permalinks.)

Now, the problem with using public query vars is that, they almost never show a 404 error page. For example, take a look these two URLs:


Even though the tag jdfjdsjfdlkf doesn't exist, the visitor is shown a "Nothing found" page (content-none.php) instead of throwing a 404 error (404.php) with appropriate headers.

When I think about it, it does make sense. But to suit my specific case, I want a specific set of public query vars (category_name, tag, and channel - a custom taxonomy query_var) to show a 404 error page when the value supplied doesn't exist.

My initial attempts got me here:

add_action( 'template_redirect', 'my_page_template_redirect' );
function my_page_template_redirect() {

    global $wp_query, $post;

    $cat = get_query_var( 'category_name' );
    $category = get_term( $cat, 'category' );

    $chnl = get_query_var( 'channel' );
    $channel = get_term( $chnl, 'itsme_channel' );

    $tag = get_query_var( 'tag' );
    $post_tag = get_term( $tag, 'post_tag' );

    if( $category && !is_wp_error($category) && $channel && !is_wp_error($channel) ){
    // If URL is like `example.net/?category_name=uk&channel=tech` and both of the
    // supplied values (i.e. Category and Channel taxonomy terms) exist

        // Do nothing

    } elseif( $category && !is_wp_error($category) && $post_tag && !is_wp_error($post_tag) ) {
    // If URL is like `example.net/?category_name=uk&tag=highlights` and both of the
    // supplied values (i.e. Category and Tag taxonomy terms) exist

        // Do nothing

    } elseif( $category && !is_wp_error($category) && $channel && !is_wp_error($channel) && $post_tag && !is_wp_error($post_tag) ) {
    // If URL is like `example.net/?category_name=uk&channel=tech&tag=highlights` and all the
    // supplied values (i.e. Category, Channel, and Tag taxonomy terms) exist

        // Do nothing

    } else {
    // If one or all of the supplied values don't exist throw a 404 error




Looks like the conditions in place are wrong, or the term objects ($category, $channel, $post_tag aren't set properly), as I am always shown a 404 error page, i.e. even if the terms exist.

What am I doing wrong here?

1 Answer 1


Even if you make it works, you are running 3 different queries (get_term fire a query) in addition to the main query for any request, I don't think is the right way to do what you want.

I guess that when you send an invalid tag / category / custom tax you don't find any posts, so why don't

add_action( 'template_redirect', 'my_page_template_redirect' );

function my_page_template_redirect() {
  global $wp_query;
  if ( ! (int) $wp_query->found_posts > 0 ) {


This function return a 404 even if existing taxonomies are passed but with no post. someone can find this good, if not this is an alternative not doing so:

function my_page_template_redirect() {
  global $wp_query, $wp;
  // this get an array of the query vars in the url
  parse_str( parse_url ( add_query_arg( array() ), PHP_URL_QUERY ), $qv);
  if ( ! empty( $qv ) ) { // if there are some query vars in the url
    $queried = array_keys( $qv ); // get the query vars name
    $valid = $wp->public_query_vars; // this are the valid query vars accepted by WP
    // intersect array to retrieve the queried vars tha are included in the valid vars
    $good = array_intersect($valid, $queried); 
    // if there are no good query vars or if there are at least one not valid var
    if ( empty($good) || count($good) !== count($queried)  ) {

This function will set the 404 only if one or more invalid query vars (invalid taxonomy names but not only) are in the query.

  • Is there a way I can make the function check if it's an archive of posts displayed by using public query vars in URL? `is_archive(); doesn't work, I've tried. Besides, your function will return a 404 error even if the term exists, but has no posts, doesn't it?
    – its_me
    Commented Nov 12, 2013 at 13:37
  • @its_me have you seen the edit?
    – gmazzap
    Commented Nov 12, 2013 at 14:19
  • Yes, I have. (Makes sense; but I haven't tested it yet -- have to get back home.) Thanks a lot! :)
    – its_me
    Commented Nov 12, 2013 at 14:30
  • I have the special case where I need to throw 404 anyone trying to access wp-login.php the code doesn't seem to work ( is_404 set to 1 but the page still loads) I am hooking with init, with wp hook; function doesn't run. Any advice?
    – Sisir
    Commented Nov 25, 2013 at 11:26
  • wp is not fired on wp-login.php, but init is. However using init can be hard to understand if you are in login page at that early hook, use 'login_head' is probably best choose in your case: it is fired only in wp-login.php and is early enough to do a redirect (headers are not already sent). @Sisir
    – gmazzap
    Commented Nov 25, 2013 at 11:42

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.