Skip to content

Instantly share code, notes, and snippets.

@robwent
Last active June 21, 2026 18:14
Show Gist options
  • Select an option

  • Save robwent/71b4a86b3bcc3fd7d740f486c67d915b to your computer and use it in GitHub Desktop.

Select an option

Save robwent/71b4a86b3bcc3fd7d740f486c67d915b to your computer and use it in GitHub Desktop.
<?php
/**
* Plugin Name: Elementor Pro – Restore 404s on Non-Existent Term Archives
* Description: Fixes a fatal error ("Object of class WP_Error could not be converted to string") and incorrect 200 responses on non-existent tag/category/taxonomy URLs when an Elementor Pro Archive template with a paginated posts widget is active.
* Version: 1.0.0
* Author: https://robertwent.com
* License: GPL-2.0-or-later
*
* ---------------------------------------------------------------------------
* THE PROBLEM
* ---------------------------------------------------------------------------
* On a site using an Elementor Pro Theme Builder "Archive" template that
* contains a Posts / Loop Grid / Archive Posts widget with pagination enabled
* (especially "Load More" / infinite scroll, or with no page limit), visiting a
* term archive URL whose term does NOT exist — e.g. /tag/does-not-exist/ —
* produces one of two broken results:
*
* 1. A fatal error:
* PHP Fatal error: Uncaught Error: Object of class WP_Error could not be
* converted to string in .../elementor/core/dynamic-tags/manager.php
* ...if the template uses the "Archive URL" dynamic tag anywhere. Elementor
* Pro's Utils::get_the_archive_url() calls get_term_link( get_queried_object() )
* without an is_wp_error() guard; for a missing term that returns a WP_Error,
* which Elementor then tries to cast to a string while generating CSS.
*
* 2. Failing that, an HTTP 200 response for a page that should be a 404.
*
* THE CAUSE
* ---------------------------------------------------------------------------
* Elementor Pro adds a `pre_handle_404` filter
* (Locations_Manager::should_allow_pagination_on_archive_templates) so that
* paginated archive templates don't get 404'd on page 2+. With Load More /
* infinite-scroll pagination (or no page limit) it returns "handled" even on
* page 1 — for ANY archive matching the template, including URLs whose term
* doesn't exist. That short-circuits WordPress's own handle_404(), so set_404()
* never runs: the status stays 200 and is_tag()/is_tax() stay true, which is
* what exposes the WP_Error in the Archive URL dynamic tag.
*
* THE FIX
* ---------------------------------------------------------------------------
* Hook `pre_handle_404` at a later priority than Elementor (which uses 10/11).
* When the request is a term archive (tag/category/custom taxonomy) whose
* queried object is NOT a real WP_Term, force a genuine 404 right there — at
* the canonical 404 decision point, before template selection and wp_head, so
* is_tag() is already false when Elementor renders. Real terms (even empty
* ones) and legitimate paginated archives are left completely untouched.
*
* Drop this file into wp-content/mu-plugins/ (it loads automatically). It is
* harmless if Elementor Pro is not installed.
* ---------------------------------------------------------------------------
*
* @package ElementorMissingTerm404Fix
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
add_filter( 'pre_handle_404', 'em404_force_404_on_missing_term', 99, 2 );
/**
* Force a proper 404 when a term archive's queried object can't be resolved.
*
* @param bool $handled Whether a plugin has already declared the request handled.
* @param \WP_Query $wp_query The query that triggered handle_404().
* @return bool True if we've handled it (as a genuine 404), otherwise the value unchanged.
*/
function em404_force_404_on_missing_term( $handled, $wp_query ) {
// Only term archives: tag, category, or any custom taxonomy.
if ( ! ( $wp_query->is_tag || $wp_query->is_category || $wp_query->is_tax ) ) {
return $handled;
}
// A valid term archive resolves to a WP_Term. Anything else (null / WP_Error)
// means the term doesn't exist, so this should be a 404.
if ( $wp_query->get_queried_object() instanceof WP_Term ) {
return $handled;
}
$wp_query->set_404();
status_header( 404 );
nocache_headers();
return true; // We've handled it — as a real 404.
}
@robwent

robwent commented Jun 21, 2026

Copy link
Copy Markdown
Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment