WP::handle_404()

In this article

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

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

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 );
	}
}

Hooks

apply_filters( ‘pre_handle_404’, bool $preempt, WP_Query $wp_query )

Filters whether to short-circuit default header status handling.

Changelog

VersionDescription
2.0.0Introduced.

User Contributed Notes

You must log in before being able to contribute a note or feedback.