Skip to content

Error accessing globals from non-Statamic 404 page #14167

Description

@stephenmeehanuk

I intermittently get this error in Laravel Nightwatch. Is this something that could be fixed with an update?

BadMethodCallException: Call to undefined method errorHeadline.

The Problem:

I was getting a BadMethodCallException: Call to undefined method errorHeadline in production, triggered by bots/scanners hitting the Livewire update endpoint directly (POST to /livewire-{hash}/update without the X-Livewire header).

The 404 error template (resources/views/errors/404.antlers.html) was pulling content from a 404 global set using the standard colon syntax:

{{ 404:error_headline }}
{{ 404:error_text }}

Root Cause:

Livewire registers its update route with only the web middleware group , it doesn't include Statamic's SwapExceptionHandler middleware, which is what hydrates the Cascade with global set data.

When Livewire's RequireLivewireHeaders middleware detects a request without the X-Livewire header, it calls abort(404). Laravel's default exception handler then renders the errors/404.antlers.html view through the Antlers engine, but without the Cascade being hydrated.

Without the Cascade, the Antlers runtime can't resolve 404 as a global set handle. It falls through to treating 404 as a tag name and attempts to call errorHeadline() as a method on Statamic\Tags\Tags via __call() — which throws BadMethodCallException.

The ?? (null coalescence) operator doesn't help either {{ 404:error_headline ?? 'fallback' }} still throws, because the exception happens during tag resolution before the fallback operator evaluates.

How to Reproduce:

From the browser console on any page with a valid session:

consttoken=decodeURIComponent(document.cookie.split('; ').find(c=>c.startsWith('XSRF-TOKEN='))?.split('=')[1]);fetch('/livewire-YOUR_HASH_HERE/update',{method: 'POST',headers: {'Content-Type': 'application/json','X-XSRF-TOKEN': token}// deliberately no X-Livewire header}).then(r=>r.text()).then(console.log)

You can find your Livewire endpoint hash with php artisan route:list | grep livewire.

Fix:

I hardcoded the content in the 404 template, removing all {{ 404:... }} references. The global set colon syntax ({{ handle:field }}) relies on the Cascade and can't be used safely in error templates that may be rendered outside Statamic's rendering pipeline.

Takeaway:

If you're using Antlers for your error templates (resources/views/errors/*.antlers.html), be aware that they can be rendered by Laravel's exception handler without Statamic's Cascade. Avoid global set references using the colon syntax ({{ global_handle:field }}). Either hardcode the values or use a Blade template with PHP to load globals via Statamic\Facades\GlobalSet::findByHandle().

I asked Claude if there's a fix? It suggested:

Statamic could hydrate the Cascade in the exception handler

Statamic's SwapExceptionHandler middleware is what sets up the Cascade for front-end routes. But routes outside Statamic's middleware stack (like Livewire's) use Laravel's default exception handler, which renders the Antlers 404 view without the Cascade. Statamic could register a global exception renderer (via Exceptions::render() in bootstrap/app.php or a service provider) that ensures the Cascade is hydrated before rendering any Antlers error template, regardless of which middleware stack the request went through.

Environment
Application Name: Statamic
Laravel Version: 12.53.0
PHP Version: 8.4.18
Composer Version: 2.9.5
Environment: local
Debug Mode: OFF
URL: website.test
Maintenance Mode: OFF
Timezone: America/New_York
Locale: en
Cache
Config: NOT CACHED
Events: NOT CACHED
Routes: NOT CACHED
Views: CACHED
Drivers
Broadcasting: log
Cache: redis
Database: sqlite
Logs: stack / single
Mail: smtp
Queue: sync
Session: redis
Storage
public/storage: NOT LINKED
Livewire
Livewire: v4.2.1
Statamic
Addons: 5
Sites: 1
Stache Watcher: Enabled
Static Caching: Disabled
Version: 5.73.11 PRO
Statamic Addons
marcorieser/statamic-live-search: 3.1.1
marcorieser/statamic-livewire: 5.2.1
statamic/collaboration: 1.0.1
statamic/importer: 1.9.0
visuellverstehen/statamic-anchor-navigation: 1.0.0

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      , 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
       blocks
      (function() {
      function addCopyButtons() {
      document.querySelectorAll('pre code').forEach(function(codeBlock) {
      if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
      codeBlock.parentElement.setAttribute('data-copy-added', 'true');
      var btn = document.createElement('button');
      btn.textContent = 'Copy';
      btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
      btn.onmouseover = function() { this.style.opacity = '1'; };
      btn.onmouseout = function() { this.style.opacity = '0.7'; };
      btn.onclick = function() {
      navigator.clipboard.writeText(codeBlock.textContent).then(function() {
      btn.textContent = 'Copied!';
      setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
      });
      };
      codeBlock.parentElement.style.position = 'relative';
      codeBlock.parentElement.appendChild(btn);
      });
      }
      addCopyButtons();
      // Re-run on dynamic content
      var observer = new MutationObserver(addCopyButtons);
      observer.observe(document.body, { childList: true, subtree: true });
      })();
      }
      } catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
      })();
      (function(){
      try {
      var __m = "github.com";
      var __re = new RegExp('^' + "github\\.com" + '
      Error accessing globals from non-Statamic 404 page · Issue #14167 · statamic/cms · GitHub
      Skip to content

      Error accessing globals from non-Statamic 404 page #14167

      Description

      @stephenmeehanuk

      I intermittently get this error in Laravel Nightwatch. Is this something that could be fixed with an update?

      BadMethodCallException: Call to undefined method errorHeadline.
      

      The Problem:

      I was getting a BadMethodCallException: Call to undefined method errorHeadline in production, triggered by bots/scanners hitting the Livewire update endpoint directly (POST to /livewire-{hash}/update without the X-Livewire header).

      The 404 error template (resources/views/errors/404.antlers.html) was pulling content from a 404 global set using the standard colon syntax:

      {{ 404:error_headline }}
      {{ 404:error_text }}
      

      Root Cause:

      Livewire registers its update route with only the web middleware group , it doesn't include Statamic's SwapExceptionHandler middleware, which is what hydrates the Cascade with global set data.

      When Livewire's RequireLivewireHeaders middleware detects a request without the X-Livewire header, it calls abort(404). Laravel's default exception handler then renders the errors/404.antlers.html view through the Antlers engine, but without the Cascade being hydrated.

      Without the Cascade, the Antlers runtime can't resolve 404 as a global set handle. It falls through to treating 404 as a tag name and attempts to call errorHeadline() as a method on Statamic\Tags\Tags via __call() — which throws BadMethodCallException.

      The ?? (null coalescence) operator doesn't help either {{ 404:error_headline ?? 'fallback' }} still throws, because the exception happens during tag resolution before the fallback operator evaluates.

      How to Reproduce:

      From the browser console on any page with a valid session:

      consttoken=decodeURIComponent(document.cookie.split('; ').find(c=>c.startsWith('XSRF-TOKEN='))?.split('=')[1]);fetch('/livewire-YOUR_HASH_HERE/update',{method: 'POST',headers: {'Content-Type': 'application/json','X-XSRF-TOKEN': token}// deliberately no X-Livewire header}).then(r=>r.text()).then(console.log)

      You can find your Livewire endpoint hash with php artisan route:list | grep livewire.

      Fix:

      I hardcoded the content in the 404 template, removing all {{ 404:... }} references. The global set colon syntax ({{ handle:field }}) relies on the Cascade and can't be used safely in error templates that may be rendered outside Statamic's rendering pipeline.

      Takeaway:

      If you're using Antlers for your error templates (resources/views/errors/*.antlers.html), be aware that they can be rendered by Laravel's exception handler without Statamic's Cascade. Avoid global set references using the colon syntax ({{ global_handle:field }}). Either hardcode the values or use a Blade template with PHP to load globals via Statamic\Facades\GlobalSet::findByHandle().

      I asked Claude if there's a fix? It suggested:

      Statamic could hydrate the Cascade in the exception handler

      Statamic's SwapExceptionHandler middleware is what sets up the Cascade for front-end routes. But routes outside Statamic's middleware stack (like Livewire's) use Laravel's default exception handler, which renders the Antlers 404 view without the Cascade. Statamic could register a global exception renderer (via Exceptions::render() in bootstrap/app.php or a service provider) that ensures the Cascade is hydrated before rendering any Antlers error template, regardless of which middleware stack the request went through.

      Environment
      Application Name: Statamic
      Laravel Version: 12.53.0
      PHP Version: 8.4.18
      Composer Version: 2.9.5
      Environment: local
      Debug Mode: OFF
      URL: website.test
      Maintenance Mode: OFF
      Timezone: America/New_York
      Locale: en
      Cache
      Config: NOT CACHED
      Events: NOT CACHED
      Routes: NOT CACHED
      Views: CACHED
      Drivers
      Broadcasting: log
      Cache: redis
      Database: sqlite
      Logs: stack / single
      Mail: smtp
      Queue: sync
      Session: redis
      Storage
      public/storage: NOT LINKED
      Livewire
      Livewire: v4.2.1
      Statamic
      Addons: 5
      Sites: 1
      Stache Watcher: Enabled
      Static Caching: Disabled
      Version: 5.73.11 PRO
      Statamic Addons
      marcorieser/statamic-live-search: 3.1.1
      marcorieser/statamic-livewire: 5.2.1
      statamic/collaboration: 1.0.1
      statamic/importer: 1.9.0
      visuellverstehen/statamic-anchor-navigation: 1.0.0
      

      Metadata

      Metadata

      Assignees

      No one assigned

        Type

        No type

        Projects

        No projects

          Milestone

          No milestone

          Relationships

          None yet

          Development

          No branches or pull requests

          Issue actions

          , 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Error accessing globals from non-Statamic 404 page · Issue #14167 · statamic/cms · GitHub
          Skip to content

          Error accessing globals from non-Statamic 404 page #14167

          Description

          @stephenmeehanuk

          I intermittently get this error in Laravel Nightwatch. Is this something that could be fixed with an update?

          BadMethodCallException: Call to undefined method errorHeadline.
          

          The Problem:

          I was getting a BadMethodCallException: Call to undefined method errorHeadline in production, triggered by bots/scanners hitting the Livewire update endpoint directly (POST to /livewire-{hash}/update without the X-Livewire header).

          The 404 error template (resources/views/errors/404.antlers.html) was pulling content from a 404 global set using the standard colon syntax:

          {{ 404:error_headline }}
          {{ 404:error_text }}
          

          Root Cause:

          Livewire registers its update route with only the web middleware group , it doesn't include Statamic's SwapExceptionHandler middleware, which is what hydrates the Cascade with global set data.

          When Livewire's RequireLivewireHeaders middleware detects a request without the X-Livewire header, it calls abort(404). Laravel's default exception handler then renders the errors/404.antlers.html view through the Antlers engine, but without the Cascade being hydrated.

          Without the Cascade, the Antlers runtime can't resolve 404 as a global set handle. It falls through to treating 404 as a tag name and attempts to call errorHeadline() as a method on Statamic\Tags\Tags via __call() — which throws BadMethodCallException.

          The ?? (null coalescence) operator doesn't help either {{ 404:error_headline ?? 'fallback' }} still throws, because the exception happens during tag resolution before the fallback operator evaluates.

          How to Reproduce:

          From the browser console on any page with a valid session:

          consttoken=decodeURIComponent(document.cookie.split('; ').find(c=>c.startsWith('XSRF-TOKEN='))?.split('=')[1]);fetch('/livewire-YOUR_HASH_HERE/update',{method: 'POST',headers: {'Content-Type': 'application/json','X-XSRF-TOKEN': token}// deliberately no X-Livewire header}).then(r=>r.text()).then(console.log)

          You can find your Livewire endpoint hash with php artisan route:list | grep livewire.

          Fix:

          I hardcoded the content in the 404 template, removing all {{ 404:... }} references. The global set colon syntax ({{ handle:field }}) relies on the Cascade and can't be used safely in error templates that may be rendered outside Statamic's rendering pipeline.

          Takeaway:

          If you're using Antlers for your error templates (resources/views/errors/*.antlers.html), be aware that they can be rendered by Laravel's exception handler without Statamic's Cascade. Avoid global set references using the colon syntax ({{ global_handle:field }}). Either hardcode the values or use a Blade template with PHP to load globals via Statamic\Facades\GlobalSet::findByHandle().

          I asked Claude if there's a fix? It suggested:

          Statamic could hydrate the Cascade in the exception handler

          Statamic's SwapExceptionHandler middleware is what sets up the Cascade for front-end routes. But routes outside Statamic's middleware stack (like Livewire's) use Laravel's default exception handler, which renders the Antlers 404 view without the Cascade. Statamic could register a global exception renderer (via Exceptions::render() in bootstrap/app.php or a service provider) that ensures the Cascade is hydrated before rendering any Antlers error template, regardless of which middleware stack the request went through.

          Environment
          Application Name: Statamic
          Laravel Version: 12.53.0
          PHP Version: 8.4.18
          Composer Version: 2.9.5
          Environment: local
          Debug Mode: OFF
          URL: website.test
          Maintenance Mode: OFF
          Timezone: America/New_York
          Locale: en
          Cache
          Config: NOT CACHED
          Events: NOT CACHED
          Routes: NOT CACHED
          Views: CACHED
          Drivers
          Broadcasting: log
          Cache: redis
          Database: sqlite
          Logs: stack / single
          Mail: smtp
          Queue: sync
          Session: redis
          Storage
          public/storage: NOT LINKED
          Livewire
          Livewire: v4.2.1
          Statamic
          Addons: 5
          Sites: 1
          Stache Watcher: Enabled
          Static Caching: Disabled
          Version: 5.73.11 PRO
          Statamic Addons
          marcorieser/statamic-live-search: 3.1.1
          marcorieser/statamic-livewire: 5.2.1
          statamic/collaboration: 1.0.1
          statamic/importer: 1.9.0
          visuellverstehen/statamic-anchor-navigation: 1.0.0
          

          Metadata

          Metadata

          Assignees

          No one assigned

            Type

            No type

            Projects

            No projects

              Milestone

              No milestone

              Relationships

              None yet

              Development

              No branches or pull requests

              Issue actions

              , 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Error accessing globals from non-Statamic 404 page · Issue #14167 · statamic/cms · GitHub
              Skip to content

              Error accessing globals from non-Statamic 404 page #14167

              Description

              @stephenmeehanuk

              I intermittently get this error in Laravel Nightwatch. Is this something that could be fixed with an update?

              BadMethodCallException: Call to undefined method errorHeadline.
              

              The Problem:

              I was getting a BadMethodCallException: Call to undefined method errorHeadline in production, triggered by bots/scanners hitting the Livewire update endpoint directly (POST to /livewire-{hash}/update without the X-Livewire header).

              The 404 error template (resources/views/errors/404.antlers.html) was pulling content from a 404 global set using the standard colon syntax:

              {{ 404:error_headline }}
              {{ 404:error_text }}
              

              Root Cause:

              Livewire registers its update route with only the web middleware group , it doesn't include Statamic's SwapExceptionHandler middleware, which is what hydrates the Cascade with global set data.

              When Livewire's RequireLivewireHeaders middleware detects a request without the X-Livewire header, it calls abort(404). Laravel's default exception handler then renders the errors/404.antlers.html view through the Antlers engine, but without the Cascade being hydrated.

              Without the Cascade, the Antlers runtime can't resolve 404 as a global set handle. It falls through to treating 404 as a tag name and attempts to call errorHeadline() as a method on Statamic\Tags\Tags via __call() — which throws BadMethodCallException.

              The ?? (null coalescence) operator doesn't help either {{ 404:error_headline ?? 'fallback' }} still throws, because the exception happens during tag resolution before the fallback operator evaluates.

              How to Reproduce:

              From the browser console on any page with a valid session:

              consttoken=decodeURIComponent(document.cookie.split('; ').find(c=>c.startsWith('XSRF-TOKEN='))?.split('=')[1]);fetch('/livewire-YOUR_HASH_HERE/update',{method: 'POST',headers: {'Content-Type': 'application/json','X-XSRF-TOKEN': token}// deliberately no X-Livewire header}).then(r=>r.text()).then(console.log)

              You can find your Livewire endpoint hash with php artisan route:list | grep livewire.

              Fix:

              I hardcoded the content in the 404 template, removing all {{ 404:... }} references. The global set colon syntax ({{ handle:field }}) relies on the Cascade and can't be used safely in error templates that may be rendered outside Statamic's rendering pipeline.

              Takeaway:

              If you're using Antlers for your error templates (resources/views/errors/*.antlers.html), be aware that they can be rendered by Laravel's exception handler without Statamic's Cascade. Avoid global set references using the colon syntax ({{ global_handle:field }}). Either hardcode the values or use a Blade template with PHP to load globals via Statamic\Facades\GlobalSet::findByHandle().

              I asked Claude if there's a fix? It suggested:

              Statamic could hydrate the Cascade in the exception handler

              Statamic's SwapExceptionHandler middleware is what sets up the Cascade for front-end routes. But routes outside Statamic's middleware stack (like Livewire's) use Laravel's default exception handler, which renders the Antlers 404 view without the Cascade. Statamic could register a global exception renderer (via Exceptions::render() in bootstrap/app.php or a service provider) that ensures the Cascade is hydrated before rendering any Antlers error template, regardless of which middleware stack the request went through.

              Environment
              Application Name: Statamic
              Laravel Version: 12.53.0
              PHP Version: 8.4.18
              Composer Version: 2.9.5
              Environment: local
              Debug Mode: OFF
              URL: website.test
              Maintenance Mode: OFF
              Timezone: America/New_York
              Locale: en
              Cache
              Config: NOT CACHED
              Events: NOT CACHED
              Routes: NOT CACHED
              Views: CACHED
              Drivers
              Broadcasting: log
              Cache: redis
              Database: sqlite
              Logs: stack / single
              Mail: smtp
              Queue: sync
              Session: redis
              Storage
              public/storage: NOT LINKED
              Livewire
              Livewire: v4.2.1
              Statamic
              Addons: 5
              Sites: 1
              Stache Watcher: Enabled
              Static Caching: Disabled
              Version: 5.73.11 PRO
              Statamic Addons
              marcorieser/statamic-live-search: 3.1.1
              marcorieser/statamic-livewire: 5.2.1
              statamic/collaboration: 1.0.1
              statamic/importer: 1.9.0
              visuellverstehen/statamic-anchor-navigation: 1.0.0
              

              Metadata

              Metadata

              Assignees

              No one assigned

                Type

                No type

                Projects

                No projects

                  Milestone

                  No milestone

                  Relationships

                  None yet

                  Development

                  No branches or pull requests

                  Issue actions

                  , 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' Error accessing globals from non-Statamic 404 page · Issue #14167 · statamic/cms · GitHub
                  Skip to content

                  Error accessing globals from non-Statamic 404 page #14167

                  Description

                  @stephenmeehanuk

                  I intermittently get this error in Laravel Nightwatch. Is this something that could be fixed with an update?

                  BadMethodCallException: Call to undefined method errorHeadline.
                  

                  The Problem:

                  I was getting a BadMethodCallException: Call to undefined method errorHeadline in production, triggered by bots/scanners hitting the Livewire update endpoint directly (POST to /livewire-{hash}/update without the X-Livewire header).

                  The 404 error template (resources/views/errors/404.antlers.html) was pulling content from a 404 global set using the standard colon syntax:

                  {{ 404:error_headline }}
                  {{ 404:error_text }}
                  

                  Root Cause:

                  Livewire registers its update route with only the web middleware group , it doesn't include Statamic's SwapExceptionHandler middleware, which is what hydrates the Cascade with global set data.

                  When Livewire's RequireLivewireHeaders middleware detects a request without the X-Livewire header, it calls abort(404). Laravel's default exception handler then renders the errors/404.antlers.html view through the Antlers engine, but without the Cascade being hydrated.

                  Without the Cascade, the Antlers runtime can't resolve 404 as a global set handle. It falls through to treating 404 as a tag name and attempts to call errorHeadline() as a method on Statamic\Tags\Tags via __call() — which throws BadMethodCallException.

                  The ?? (null coalescence) operator doesn't help either {{ 404:error_headline ?? 'fallback' }} still throws, because the exception happens during tag resolution before the fallback operator evaluates.

                  How to Reproduce:

                  From the browser console on any page with a valid session:

                  consttoken=decodeURIComponent(document.cookie.split('; ').find(c=>c.startsWith('XSRF-TOKEN='))?.split('=')[1]);fetch('/livewire-YOUR_HASH_HERE/update',{method: 'POST',headers: {'Content-Type': 'application/json','X-XSRF-TOKEN': token}// deliberately no X-Livewire header}).then(r=>r.text()).then(console.log)

                  You can find your Livewire endpoint hash with php artisan route:list | grep livewire.

                  Fix:

                  I hardcoded the content in the 404 template, removing all {{ 404:... }} references. The global set colon syntax ({{ handle:field }}) relies on the Cascade and can't be used safely in error templates that may be rendered outside Statamic's rendering pipeline.

                  Takeaway:

                  If you're using Antlers for your error templates (resources/views/errors/*.antlers.html), be aware that they can be rendered by Laravel's exception handler without Statamic's Cascade. Avoid global set references using the colon syntax ({{ global_handle:field }}). Either hardcode the values or use a Blade template with PHP to load globals via Statamic\Facades\GlobalSet::findByHandle().

                  I asked Claude if there's a fix? It suggested:

                  Statamic could hydrate the Cascade in the exception handler

                  Statamic's SwapExceptionHandler middleware is what sets up the Cascade for front-end routes. But routes outside Statamic's middleware stack (like Livewire's) use Laravel's default exception handler, which renders the Antlers 404 view without the Cascade. Statamic could register a global exception renderer (via Exceptions::render() in bootstrap/app.php or a service provider) that ensures the Cascade is hydrated before rendering any Antlers error template, regardless of which middleware stack the request went through.

                  Environment
                  Application Name: Statamic
                  Laravel Version: 12.53.0
                  PHP Version: 8.4.18
                  Composer Version: 2.9.5
                  Environment: local
                  Debug Mode: OFF
                  URL: website.test
                  Maintenance Mode: OFF
                  Timezone: America/New_York
                  Locale: en
                  Cache
                  Config: NOT CACHED
                  Events: NOT CACHED
                  Routes: NOT CACHED
                  Views: CACHED
                  Drivers
                  Broadcasting: log
                  Cache: redis
                  Database: sqlite
                  Logs: stack / single
                  Mail: smtp
                  Queue: sync
                  Session: redis
                  Storage
                  public/storage: NOT LINKED
                  Livewire
                  Livewire: v4.2.1
                  Statamic
                  Addons: 5
                  Sites: 1
                  Stache Watcher: Enabled
                  Static Caching: Disabled
                  Version: 5.73.11 PRO
                  Statamic Addons
                  marcorieser/statamic-live-search: 3.1.1
                  marcorieser/statamic-livewire: 5.2.1
                  statamic/collaboration: 1.0.1
                  statamic/importer: 1.9.0
                  visuellverstehen/statamic-anchor-navigation: 1.0.0
                  

                  Metadata

                  Metadata

                  Assignees

                  No one assigned

                    Type

                    No type

                    Projects

                    No projects

                      Milestone

                      No milestone

                      Relationships

                      None yet

                      Development

                      No branches or pull requests

                      Issue actions

                      , 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Error accessing globals from non-Statamic 404 page · Issue #14167 · statamic/cms · GitHub
                      Skip to content

                      Error accessing globals from non-Statamic 404 page #14167

                      Description

                      @stephenmeehanuk

                      I intermittently get this error in Laravel Nightwatch. Is this something that could be fixed with an update?

                      BadMethodCallException: Call to undefined method errorHeadline.
                      

                      The Problem:

                      I was getting a BadMethodCallException: Call to undefined method errorHeadline in production, triggered by bots/scanners hitting the Livewire update endpoint directly (POST to /livewire-{hash}/update without the X-Livewire header).

                      The 404 error template (resources/views/errors/404.antlers.html) was pulling content from a 404 global set using the standard colon syntax:

                      {{ 404:error_headline }}
                      {{ 404:error_text }}
                      

                      Root Cause:

                      Livewire registers its update route with only the web middleware group , it doesn't include Statamic's SwapExceptionHandler middleware, which is what hydrates the Cascade with global set data.

                      When Livewire's RequireLivewireHeaders middleware detects a request without the X-Livewire header, it calls abort(404). Laravel's default exception handler then renders the errors/404.antlers.html view through the Antlers engine, but without the Cascade being hydrated.

                      Without the Cascade, the Antlers runtime can't resolve 404 as a global set handle. It falls through to treating 404 as a tag name and attempts to call errorHeadline() as a method on Statamic\Tags\Tags via __call() — which throws BadMethodCallException.

                      The ?? (null coalescence) operator doesn't help either {{ 404:error_headline ?? 'fallback' }} still throws, because the exception happens during tag resolution before the fallback operator evaluates.

                      How to Reproduce:

                      From the browser console on any page with a valid session:

                      consttoken=decodeURIComponent(document.cookie.split('; ').find(c=>c.startsWith('XSRF-TOKEN='))?.split('=')[1]);fetch('/livewire-YOUR_HASH_HERE/update',{method: 'POST',headers: {'Content-Type': 'application/json','X-XSRF-TOKEN': token}// deliberately no X-Livewire header}).then(r=>r.text()).then(console.log)

                      You can find your Livewire endpoint hash with php artisan route:list | grep livewire.

                      Fix:

                      I hardcoded the content in the 404 template, removing all {{ 404:... }} references. The global set colon syntax ({{ handle:field }}) relies on the Cascade and can't be used safely in error templates that may be rendered outside Statamic's rendering pipeline.

                      Takeaway:

                      If you're using Antlers for your error templates (resources/views/errors/*.antlers.html), be aware that they can be rendered by Laravel's exception handler without Statamic's Cascade. Avoid global set references using the colon syntax ({{ global_handle:field }}). Either hardcode the values or use a Blade template with PHP to load globals via Statamic\Facades\GlobalSet::findByHandle().

                      I asked Claude if there's a fix? It suggested:

                      Statamic could hydrate the Cascade in the exception handler

                      Statamic's SwapExceptionHandler middleware is what sets up the Cascade for front-end routes. But routes outside Statamic's middleware stack (like Livewire's) use Laravel's default exception handler, which renders the Antlers 404 view without the Cascade. Statamic could register a global exception renderer (via Exceptions::render() in bootstrap/app.php or a service provider) that ensures the Cascade is hydrated before rendering any Antlers error template, regardless of which middleware stack the request went through.

                      Environment
                      Application Name: Statamic
                      Laravel Version: 12.53.0
                      PHP Version: 8.4.18
                      Composer Version: 2.9.5
                      Environment: local
                      Debug Mode: OFF
                      URL: website.test
                      Maintenance Mode: OFF
                      Timezone: America/New_York
                      Locale: en
                      Cache
                      Config: NOT CACHED
                      Events: NOT CACHED
                      Routes: NOT CACHED
                      Views: CACHED
                      Drivers
                      Broadcasting: log
                      Cache: redis
                      Database: sqlite
                      Logs: stack / single
                      Mail: smtp
                      Queue: sync
                      Session: redis
                      Storage
                      public/storage: NOT LINKED
                      Livewire
                      Livewire: v4.2.1
                      Statamic
                      Addons: 5
                      Sites: 1
                      Stache Watcher: Enabled
                      Static Caching: Disabled
                      Version: 5.73.11 PRO
                      Statamic Addons
                      marcorieser/statamic-live-search: 3.1.1
                      marcorieser/statamic-livewire: 5.2.1
                      statamic/collaboration: 1.0.1
                      statamic/importer: 1.9.0
                      visuellverstehen/statamic-anchor-navigation: 1.0.0
                      

                      Metadata

                      Metadata

                      Assignees

                      No one assigned

                        Type

                        No type

                        Projects

                        No projects

                          Milestone

                          No milestone

                          Relationships

                          None yet

                          Development

                          No branches or pull requests

                          Issue actions

                          , 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Error accessing globals from non-Statamic 404 page · Issue #14167 · statamic/cms · GitHub
                          Skip to content

                          Error accessing globals from non-Statamic 404 page #14167

                          Description

                          @stephenmeehanuk

                          I intermittently get this error in Laravel Nightwatch. Is this something that could be fixed with an update?

                          BadMethodCallException: Call to undefined method errorHeadline.
                          

                          The Problem:

                          I was getting a BadMethodCallException: Call to undefined method errorHeadline in production, triggered by bots/scanners hitting the Livewire update endpoint directly (POST to /livewire-{hash}/update without the X-Livewire header).

                          The 404 error template (resources/views/errors/404.antlers.html) was pulling content from a 404 global set using the standard colon syntax:

                          {{ 404:error_headline }}
                          {{ 404:error_text }}
                          

                          Root Cause:

                          Livewire registers its update route with only the web middleware group , it doesn't include Statamic's SwapExceptionHandler middleware, which is what hydrates the Cascade with global set data.

                          When Livewire's RequireLivewireHeaders middleware detects a request without the X-Livewire header, it calls abort(404). Laravel's default exception handler then renders the errors/404.antlers.html view through the Antlers engine, but without the Cascade being hydrated.

                          Without the Cascade, the Antlers runtime can't resolve 404 as a global set handle. It falls through to treating 404 as a tag name and attempts to call errorHeadline() as a method on Statamic\Tags\Tags via __call() — which throws BadMethodCallException.

                          The ?? (null coalescence) operator doesn't help either {{ 404:error_headline ?? 'fallback' }} still throws, because the exception happens during tag resolution before the fallback operator evaluates.

                          How to Reproduce:

                          From the browser console on any page with a valid session:

                          consttoken=decodeURIComponent(document.cookie.split('; ').find(c=>c.startsWith('XSRF-TOKEN='))?.split('=')[1]);fetch('/livewire-YOUR_HASH_HERE/update',{method: 'POST',headers: {'Content-Type': 'application/json','X-XSRF-TOKEN': token}// deliberately no X-Livewire header}).then(r=>r.text()).then(console.log)

                          You can find your Livewire endpoint hash with php artisan route:list | grep livewire.

                          Fix:

                          I hardcoded the content in the 404 template, removing all {{ 404:... }} references. The global set colon syntax ({{ handle:field }}) relies on the Cascade and can't be used safely in error templates that may be rendered outside Statamic's rendering pipeline.

                          Takeaway:

                          If you're using Antlers for your error templates (resources/views/errors/*.antlers.html), be aware that they can be rendered by Laravel's exception handler without Statamic's Cascade. Avoid global set references using the colon syntax ({{ global_handle:field }}). Either hardcode the values or use a Blade template with PHP to load globals via Statamic\Facades\GlobalSet::findByHandle().

                          I asked Claude if there's a fix? It suggested:

                          Statamic could hydrate the Cascade in the exception handler

                          Statamic's SwapExceptionHandler middleware is what sets up the Cascade for front-end routes. But routes outside Statamic's middleware stack (like Livewire's) use Laravel's default exception handler, which renders the Antlers 404 view without the Cascade. Statamic could register a global exception renderer (via Exceptions::render() in bootstrap/app.php or a service provider) that ensures the Cascade is hydrated before rendering any Antlers error template, regardless of which middleware stack the request went through.

                          Environment
                          Application Name: Statamic
                          Laravel Version: 12.53.0
                          PHP Version: 8.4.18
                          Composer Version: 2.9.5
                          Environment: local
                          Debug Mode: OFF
                          URL: website.test
                          Maintenance Mode: OFF
                          Timezone: America/New_York
                          Locale: en
                          Cache
                          Config: NOT CACHED
                          Events: NOT CACHED
                          Routes: NOT CACHED
                          Views: CACHED
                          Drivers
                          Broadcasting: log
                          Cache: redis
                          Database: sqlite
                          Logs: stack / single
                          Mail: smtp
                          Queue: sync
                          Session: redis
                          Storage
                          public/storage: NOT LINKED
                          Livewire
                          Livewire: v4.2.1
                          Statamic
                          Addons: 5
                          Sites: 1
                          Stache Watcher: Enabled
                          Static Caching: Disabled
                          Version: 5.73.11 PRO
                          Statamic Addons
                          marcorieser/statamic-live-search: 3.1.1
                          marcorieser/statamic-livewire: 5.2.1
                          statamic/collaboration: 1.0.1
                          statamic/importer: 1.9.0
                          visuellverstehen/statamic-anchor-navigation: 1.0.0
                          

                          Metadata

                          Metadata

                          Assignees

                          No one assigned

                            Type

                            No type

                            Projects

                            No projects

                              Milestone

                              No milestone

                              Relationships

                              None yet

                              Development

                              No branches or pull requests

                              Issue actions

                              , 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); Error accessing globals from non-Statamic 404 page · Issue #14167 · statamic/cms · GitHub
                              Skip to content

                              Error accessing globals from non-Statamic 404 page #14167

                              Description

                              @stephenmeehanuk

                              I intermittently get this error in Laravel Nightwatch. Is this something that could be fixed with an update?

                              BadMethodCallException: Call to undefined method errorHeadline.
                              

                              The Problem:

                              I was getting a BadMethodCallException: Call to undefined method errorHeadline in production, triggered by bots/scanners hitting the Livewire update endpoint directly (POST to /livewire-{hash}/update without the X-Livewire header).

                              The 404 error template (resources/views/errors/404.antlers.html) was pulling content from a 404 global set using the standard colon syntax:

                              {{ 404:error_headline }}
                              {{ 404:error_text }}
                              

                              Root Cause:

                              Livewire registers its update route with only the web middleware group , it doesn't include Statamic's SwapExceptionHandler middleware, which is what hydrates the Cascade with global set data.

                              When Livewire's RequireLivewireHeaders middleware detects a request without the X-Livewire header, it calls abort(404). Laravel's default exception handler then renders the errors/404.antlers.html view through the Antlers engine, but without the Cascade being hydrated.

                              Without the Cascade, the Antlers runtime can't resolve 404 as a global set handle. It falls through to treating 404 as a tag name and attempts to call errorHeadline() as a method on Statamic\Tags\Tags via __call() — which throws BadMethodCallException.

                              The ?? (null coalescence) operator doesn't help either {{ 404:error_headline ?? 'fallback' }} still throws, because the exception happens during tag resolution before the fallback operator evaluates.

                              How to Reproduce:

                              From the browser console on any page with a valid session:

                              consttoken=decodeURIComponent(document.cookie.split('; ').find(c=>c.startsWith('XSRF-TOKEN='))?.split('=')[1]);fetch('/livewire-YOUR_HASH_HERE/update',{method: 'POST',headers: {'Content-Type': 'application/json','X-XSRF-TOKEN': token}// deliberately no X-Livewire header}).then(r=>r.text()).then(console.log)

                              You can find your Livewire endpoint hash with php artisan route:list | grep livewire.

                              Fix:

                              I hardcoded the content in the 404 template, removing all {{ 404:... }} references. The global set colon syntax ({{ handle:field }}) relies on the Cascade and can't be used safely in error templates that may be rendered outside Statamic's rendering pipeline.

                              Takeaway:

                              If you're using Antlers for your error templates (resources/views/errors/*.antlers.html), be aware that they can be rendered by Laravel's exception handler without Statamic's Cascade. Avoid global set references using the colon syntax ({{ global_handle:field }}). Either hardcode the values or use a Blade template with PHP to load globals via Statamic\Facades\GlobalSet::findByHandle().

                              I asked Claude if there's a fix? It suggested:

                              Statamic could hydrate the Cascade in the exception handler

                              Statamic's SwapExceptionHandler middleware is what sets up the Cascade for front-end routes. But routes outside Statamic's middleware stack (like Livewire's) use Laravel's default exception handler, which renders the Antlers 404 view without the Cascade. Statamic could register a global exception renderer (via Exceptions::render() in bootstrap/app.php or a service provider) that ensures the Cascade is hydrated before rendering any Antlers error template, regardless of which middleware stack the request went through.

                              Environment
                              Application Name: Statamic
                              Laravel Version: 12.53.0
                              PHP Version: 8.4.18
                              Composer Version: 2.9.5
                              Environment: local
                              Debug Mode: OFF
                              URL: website.test
                              Maintenance Mode: OFF
                              Timezone: America/New_York
                              Locale: en
                              Cache
                              Config: NOT CACHED
                              Events: NOT CACHED
                              Routes: NOT CACHED
                              Views: CACHED
                              Drivers
                              Broadcasting: log
                              Cache: redis
                              Database: sqlite
                              Logs: stack / single
                              Mail: smtp
                              Queue: sync
                              Session: redis
                              Storage
                              public/storage: NOT LINKED
                              Livewire
                              Livewire: v4.2.1
                              Statamic
                              Addons: 5
                              Sites: 1
                              Stache Watcher: Enabled
                              Static Caching: Disabled
                              Version: 5.73.11 PRO
                              Statamic Addons
                              marcorieser/statamic-live-search: 3.1.1
                              marcorieser/statamic-livewire: 5.2.1
                              statamic/collaboration: 1.0.1
                              statamic/importer: 1.9.0
                              visuellverstehen/statamic-anchor-navigation: 1.0.0
                              

                              Metadata

                              Metadata

                              Assignees

                              No one assigned

                                Type

                                No type

                                Projects

                                No projects

                                  Milestone

                                  No milestone

                                  Relationships

                                  None yet

                                  Development

                                  No branches or pull requests

                                  Issue actions