Hook when page is requested

I’m writing a simple analytics plugin for Kirby 3. So far I’ve only processed page requests in the page template, but since I want to support cached pages as well, the processing of the request must be done before the page is actually served to the client.

I have tried a route that listens for any pages, but it may conflict with other routes in the future. I have also tried to process the request in my plugin’s index.php file. But the index.php of the plugin is also loaded for each page in the backend, and I couldn’t find a way to distinguish between a “normal” page requested by a visitor and a backend page in the panel.

So what I’m basically looking for is a place where I can put my analtyics code, something like a hook that is fired when a user requests a page. Maybe there’s a really obvious way to do this that I don’t see!

1 Like

Kirby has the route hooks that you could use for this purpose:

Thank you very much for the quick reply! The route hook seems to be a good place. What would be the easiest way to distinguish between “normal” page request and panel requests? For the route:before hook seems to be fired for every backend and api page.

Yes, but you can rule out the api/panel paths by checking the path parameter…

Ah yes of course! For some reason I was only checking for the page id and the id seems to be the same for the pages home and panel home. With the path filtering everything works fine

For all who come across this via the search, this is the funtion I use:

function startsWith ($string, $startString) { 
  return (substr($string, 0, strlen($startString)) === $startString); 

function pathIsPage($path) {
  foreach (['panel', 'api', 'assets', 'media'] as $ignoreDir) {
    if ($path === $ignoreDir || startsWith($path, $ignoreDir . '/')) {
      return false;
  return true;

Hi @arnoson,

I, too, use a route hook for a simple pageview counter plugin.

I had some issues with route:before (AFAIR it was something about the hook being triggered even though eventually the route would not resolve and hop to the next instead; this was about a very specific setup where the frontend would feature prettified URLs that did not reflect my Kirby page structure), counting some page views twice and eventually settled for route:after – the benefit being that this is only triggered by routes that have actually been processed.

Here’s the hook I use for registering page views, maybe it’s of value for you or somebody else.

'route:after' => function ($route, $path, $method, $result) {

	// only for routes that yield a result
	if ($result) {

		// exclude requests that do not match a page
		if ($path != '' && !kirby()->page($path)) {

        // do something to register the view for $path



I exclude requests where $path does not match an existing page, but that is probably more expensive (as page() leads to a request on the file system) than your substr-based string processing. I might actually adopt that in my next iteration :wink:

Thank you @sebastiangreger, this works great! I had the problem that also Error pages were beeing counted but your solution takes care of that now. I haven’t thought of kirby()->page($path). Might be handy if something with the substr path testing doesn’t work as expected.