Title: WP::handle_404
Published: April 25, 2014
Last modified: February 24, 2026

---

# WP::handle_404()

## In this article

 * [Description](https://developer.wordpress.org/reference/classes/wp/handle_404/?output_format=md#description)
 * [Source](https://developer.wordpress.org/reference/classes/wp/handle_404/?output_format=md#source)
 * [Hooks](https://developer.wordpress.org/reference/classes/wp/handle_404/?output_format=md#hooks)
 * [Related](https://developer.wordpress.org/reference/classes/wp/handle_404/?output_format=md#related)
 * [Changelog](https://developer.wordpress.org/reference/classes/wp/handle_404/?output_format=md#changelog)

[ Back to top](https://developer.wordpress.org/reference/classes/wp/handle_404/?output_format=md#wp--skip-link--target)

Set the Headers for 404, if nothing is found for requested URL.

## 󠀁[Description](https://developer.wordpress.org/reference/classes/wp/handle_404/?output_format=md#description)󠁿

Issue a 404 if a request doesn’t match any posts and doesn’t match any object (e.
g. an existing-but-empty category, tag, author) and a 404 was not already issued,
and if the request was not a search or the homepage.

Otherwise, issue a 200.

This sets headers after posts have been queried. handle_404() really means “handle
status”.
By inspecting the result of querying posts, seemingly successful requests
can be switched to a 404 so that canonical redirection logic can kick in.

## 󠀁[Source](https://developer.wordpress.org/reference/classes/wp/handle_404/?output_format=md#source)󠁿

    ```php
    public function handle_404() {
    	global $wp_query;

    	/**
    	 * Filters whether to short-circuit default header status handling.
    	 *
    	 * Returning a non-false value from the filter will short-circuit the handling
    	 * and return early.
    	 *
    	 * @since 4.5.0
    	 *
    	 * @param bool     $preempt  Whether to short-circuit default header status handling. Default false.
    	 * @param WP_Query $wp_query WordPress Query object.
    	 */
    	if ( false !== apply_filters( 'pre_handle_404', false, $wp_query ) ) {
    		return;
    	}

    	// If we've already issued a 404, bail.
    	if ( is_404() ) {
    		return;
    	}

    	$set_404 = true;

    	// Never 404 for the admin, robots, or favicon.
    	if ( is_admin() || is_robots() || is_favicon() ) {
    		$set_404 = false;

    		// If posts were found, check for paged content.
    	} elseif ( $wp_query->posts ) {
    		$content_found = true;

    		if ( is_singular() ) {
    			$post = isset( $wp_query->post ) ? $wp_query->post : null;
    			$next = '<!--nextpage-->';

    			// Check for paged content that exceeds the max number of pages.
    			if ( $post && ! empty( $this->query_vars['page'] ) ) {
    				// Check if content is actually intended to be paged.
    				if ( str_contains( $post->post_content, $next ) ) {
    					$page          = trim( $this->query_vars['page'], '/' );
    					$content_found = (int) $page <= ( substr_count( $post->post_content, $next ) + 1 );
    				} else {
    					$content_found = false;
    				}
    			}
    		}

    		// The posts page does not support the <!--nextpage--> pagination.
    		if ( $wp_query->is_posts_page && ! empty( $this->query_vars['page'] ) ) {
    			$content_found = false;
    		}

    		if ( $content_found ) {
    			$set_404 = false;
    		}

    		// We will 404 for paged queries, as no posts were found.
    	} elseif ( ! is_paged() ) {
    		$author = get_query_var( 'author' );

    		// Don't 404 for authors without posts as long as they matched an author on this site.
    		if ( is_author() && is_numeric( $author ) && $author > 0 && is_user_member_of_blog( $author )
    			// Don't 404 for these queries if they matched an object.
    			|| ( is_tag() || is_category() || is_tax() || is_post_type_archive() ) && get_queried_object()
    			// Don't 404 for these queries either.
    			|| is_home() || is_search() || is_feed()
    		) {
    			$set_404 = false;
    		}
    	}

    	if ( $set_404 ) {
    		// Guess it's time to 404.
    		$wp_query->set_404();
    		status_header( 404 );
    		nocache_headers();
    	} else {
    		status_header( 200 );
    	}
    }
    ```

[View all references](https://developer.wordpress.org/reference/files/wp-includes/class-wp.php/)
[View on Trac](https://core.trac.wordpress.org/browser/tags/6.9.4/src/wp-includes/class-wp.php#L724)
[View on GitHub](https://github.com/WordPress/wordpress-develop/blob/6.9.4/src/wp-includes/class-wp.php#L724-L805)

## 󠀁[Hooks](https://developer.wordpress.org/reference/classes/wp/handle_404/?output_format=md#hooks)󠁿

 [apply_filters( ‘pre_handle_404’, bool $preempt, WP_Query $wp_query )](https://developer.wordpress.org/reference/hooks/pre_handle_404/)

Filters whether to short-circuit default header status handling.

## 󠀁[Related](https://developer.wordpress.org/reference/classes/wp/handle_404/?output_format=md#related)󠁿

| Uses | Description | 
| [is_favicon()](https://developer.wordpress.org/reference/functions/is_favicon/)`wp-includes/query.php` |

Is the query for the favicon.ico file?

  | 
| [WP_Query::set_404()](https://developer.wordpress.org/reference/classes/wp_query/set_404/)`wp-includes/class-wp-query.php` |

Sets the 404 property and saves whether query is feed.

  | 
| [is_404()](https://developer.wordpress.org/reference/functions/is_404/)`wp-includes/query.php` |

Determines whether the query has resulted in a 404 (returns no results).

  | 
| [is_robots()](https://developer.wordpress.org/reference/functions/is_robots/)`wp-includes/query.php` |

Is the query for the robots.txt file?

  | 
| [is_singular()](https://developer.wordpress.org/reference/functions/is_singular/)`wp-includes/query.php` |

Determines whether the query is for an existing single post of any post type (post, attachment, page, custom post types).

  | 
| [is_search()](https://developer.wordpress.org/reference/functions/is_search/)`wp-includes/query.php` |

Determines whether the query is for a search.

  | 
| [is_paged()](https://developer.wordpress.org/reference/functions/is_paged/)`wp-includes/query.php` |

Determines whether the query is for a paged result and not for the first page.

  | 
| [is_author()](https://developer.wordpress.org/reference/functions/is_author/)`wp-includes/query.php` |

Determines whether the query is for an existing author archive page.

  | 
| [is_tag()](https://developer.wordpress.org/reference/functions/is_tag/)`wp-includes/query.php` |

Determines whether the query is for an existing tag archive page.

  | 
| [is_category()](https://developer.wordpress.org/reference/functions/is_category/)`wp-includes/query.php` |

Determines whether the query is for an existing category archive page.

  | 
| [is_tax()](https://developer.wordpress.org/reference/functions/is_tax/)`wp-includes/query.php` |

Determines whether the query is for an existing custom taxonomy archive page.

  | 
| [is_home()](https://developer.wordpress.org/reference/functions/is_home/)`wp-includes/query.php` |

Determines whether the query is for the blog homepage.

  | 
| [is_feed()](https://developer.wordpress.org/reference/functions/is_feed/)`wp-includes/query.php` |

Determines whether the query is for a feed.

  | 
| [get_query_var()](https://developer.wordpress.org/reference/functions/get_query_var/)`wp-includes/query.php` |

Retrieves the value of a query variable in the [WP_Query](https://developer.wordpress.org/reference/classes/wp_query/) class.

  | 
| [is_post_type_archive()](https://developer.wordpress.org/reference/functions/is_post_type_archive/)`wp-includes/query.php` |

Determines whether the query is for an existing post type archive page.

  | 
| [get_queried_object()](https://developer.wordpress.org/reference/functions/get_queried_object/)`wp-includes/query.php` |

Retrieves the currently queried object.

  | 
| [status_header()](https://developer.wordpress.org/reference/functions/status_header/)`wp-includes/functions.php` |

Sets HTTP status header.

  | 
| [nocache_headers()](https://developer.wordpress.org/reference/functions/nocache_headers/)`wp-includes/functions.php` |

Sets the HTTP headers to prevent caching for the different browsers.

  | 
| [is_user_member_of_blog()](https://developer.wordpress.org/reference/functions/is_user_member_of_blog/)`wp-includes/user.php` |

Finds out whether a user is a member of a given blog.

  | 
| [is_admin()](https://developer.wordpress.org/reference/functions/is_admin/)`wp-includes/load.php` |

Determines whether the current request is for an administrative interface page.

  | 
| [apply_filters()](https://developer.wordpress.org/reference/functions/apply_filters/)`wp-includes/plugin.php` |

Calls the callback functions that have been added to a filter hook.

  |

[Show 16 more](https://developer.wordpress.org/reference/classes/wp/handle_404/?output_format=md#)
[Show less](https://developer.wordpress.org/reference/classes/wp/handle_404/?output_format=md#)

| Used by | Description | 
| [WP::main()](https://developer.wordpress.org/reference/classes/wp/main/)`wp-includes/class-wp.php` |

Sets up all of the variables required by the WordPress environment.

  |

## 󠀁[Changelog](https://developer.wordpress.org/reference/classes/wp/handle_404/?output_format=md#changelog)󠁿

| Version | Description | 
| [2.0.0](https://developer.wordpress.org/reference/since/2.0.0/) | Introduced. |

## User Contributed Notes

You must [log in](https://login.wordpress.org/?redirect_to=https%3A%2F%2Fdeveloper.wordpress.org%2Freference%2Fclasses%2Fwp%2Fhandle_404%2F)
before being able to contribute a note or feedback.