How to add virtual pages to kirby's index

Apologies to revive this old post, but it came the closest to my problem and I might have found a solution or add a missing link.

My situation is that I created a plugin to handle multiple API calls to different endpoints{/:id}{/:id}

For Kirby I setup a content folder:


And my goal is to reuse the API endpoints and integrate them in kirby and be able to query all those virtual kirby pages like native pages.

my plugin index.php looks like this: (Work in Progress!)

	'EntriesPage' => 'src/EntriesPage.php',
	'EntryPage' => 'src/EntryPage.php',
], __DIR__);

Kirby::plugin('mo/core', [
	'routes' => function($kirby) {
		return [
				'pattern' => [
					'(:any)/locations' // needs more patterns?!
				'language' => '*',
				'action' => function ($language, $parent) {
					$page = [
						'slug' => 'locations',
						'template' => 'locations',
						'num' => 0,
						'model' => 'entries',
						'parent' => page($parent),
						'children' => [],
						'content' => [
							'title' => t('Standorte', 'Standorte'),

					$page = new EntriesPage($page);
					return $page;


	'pageModels' => [
		'entries' => 'EntriesPage',
		'entry' => 'EntryPage',

The EntriesPage Model creates the children()

class EntriesPage extends EntryPage
	public function children() : Pages

		// Query the `record` collection, could also be `geojson`
		$results = $this->_query('records/'. $this->slug());

		foreach(A::get($results,'records') as $item) {
			$pages[] = [
				'slug' => $item['id'],
				'num' => 0,
				'parent' => $this,
				'model' => 'entry',
				'template' => 'location',
				'content' => array_merge($item,[
					'title' => t($item['name_en'], $item['name'])
		return Pages::factory($pages, $this);


And EntryPage Model has a method to query the API.

So this works fine if I visit, but if I want to show a list of locations as teaser on the parent parent page and try to access the location children with
page('customerX/locations'), the children aren’t registered with kirby, as @bvdputte described it initially.

@texnixe is correct when she says, VP are getting registered in the guide “Virtual Pages from a Database”.

In the Database example it comes down to creating a file in the content folder.
So when I added a locations/entries.txt to each customer:


then customerX/locations wasn’t a virtual file anymore, and it showed up in the index with all the children. But I don’t want to add a bunch of ghost folders/files to my content, just to trick Kirby.

It comes down to Pages::inventory() that collects all files (children, images, templates) and sets the $page->children() somewhere in \HasChildren->index() when you attempt to find a page(’…’)

So my solution is to overwrite inventory in the site model like this:


class SitePage extends Page
	public function inventory(): array

		if ($this->inventory !== null) {
			return $this->inventory;

		$kirby = $this->kirby();

		$this->inventory = Dir::inventory(
		// register my virtual Parent Page
		$this->inventory['children'][] = [
			'dirname' => "locations",
			'model' => 'entries',
			'num' => 0,
			'root' => "M:\WEB\project\content\customerX/locations", //?
			'slug' => "locations",

		return $this->inventory;


And now everything works as expected.

I still have to make it more universal for my case and experiment with the virtual inventory, but this did the trick to register a virtual page with child pages.

Hopefully all of this makes sense, and maybe it helps someone find a solution.
@bvdputte recommended his plugin to me GitHub - bvdputte/kirby-vpkit: Virtual pages helper for multilingual Kirby 3 that deals with virtual pages. It’s a different approach, and worth to explore.

Any insight into how or why inventory() plays such an important role is appreciated. I just came to this conclusion by a lot trial and error and stepping through the code with Xdebug.

1 Like