Manually throw 404 from Controller

Hey friends,
is there a way to manually throw a 404 error from a controller, complete with passing everything off to the default 404 handler?
I know I could do $kirby->response()->code('404') to get the header right and then hack something into the template to include the 404 error template instead, but that’s too hacky for my tastes.
Thanks!

Why don’t you send the user to the error page?

go('error');

What’s your use case?

For example, when building a simple blog tag system with an URL like /blog/tag:foobar - if foobar is not a valid tag, the user should see a 404 page under that URL.

This should work:

return function ($page) {
    
    if ('your condition') {
        echo page('error')->render();
        exit();
    } else {
       
        return [
           //...
        ];
    }

};

I had a similar issue. It’s always possible to call the PHP header() function or Kirby’s Header::notfound() helper, but it looks like Kirby sets the response status itself later on, so whatever you declare is overwritten.

I settled on something like @texnixe’s solution, with a few changes:

<?php // site/controllers/custom.php
use Kirby\Cms\Site;
use Kirby\Http\Header;

return function (Site $site) {
  Header::notfound(true);
  echo $site->errorPage()->render();
  exit;
}
<?php // site/templates/custom.php
/**
 * This templates/custom.php is not used, but it must exist in order
 * for the controllers/custom.php file to be executed.
 */

(I’m only showing the 404 error branch. In my case I’m either returning an array of data for the template, or echoing the HTML for the error page and exiting.)

You might want to actually render the template for this page type, which can look like this:

<?php // site/controllers/custom.php
use Kirby\Cms\Page;
use Kirby\Http\Header;

return function (Page $page) {
  $someData = /* retrieve some data */;
  $is404 = /* determine if we have content */;

  if (!$is404) Header::notfound(true);
  echo $page->template()->render([
    'someData' => $someData,
    'is404' => $is404
  ]);
  exit;
}

Just be careful to run $page->template()->render() and not $page->render(), because the latter will run the page’s controller and create an infinite loop!

1 Like

Addendum: if you’re letting Kirby render the template instead of doing it more manually, $site->response()->code(404); does work. So you can replace this:

if (!$is404) Header::notfound(true);
echo $page->template()->render([
  'someData' => $someData,
  'is404' => $is404
]);
exit;

with:

if (!$is404) $site->resonse()->code(404);
return [
  'someData' => $someData,
  'is404' => $is404
];
1 Like

I was just playing with this on a site we are refactoring, specifically I was thinking about the case of a tag being wrong from the URL. Perhaps a bad link or someone entered the URL incorrectly. Now for our site it is less likely to happen as we use a list of tags or tag buttons from which to select from… but it could of course happen!

So from a user friendly point of view, rather than throw a 404/Error, why not render the page as normal with a heading/note saying something like ‘no tags’ available for ‘xxx’ and ‘please select from’ a list of tags to tag buttons?

That’s definitely possible, yes! It’s a UX choice whether you want to render a tag page with a “no content for this tag” message, or a more generic 404 page.