Panel Performance issues with structure field (relations between entries)

Hi everyone,

I’m currently experimenting with a setup in Kirby where I want to store a relation between “software” and “skills”. In my blueprint, I have two tabs: one called Skills, where users manage a simple structure field containing only skill titles, and another called Software, where users manage software entries that each have a title and a relation to one or more skills using a multiselect field.

For testing, I created 100 skills and 100 software entries. Each software entry is linked to between zero and five skills. However, I’ve noticed significant performance issues with this setup. Opening the “Software” tab in the Panel (at /panel/pages/pageSlug?tab=software) takes about two seconds, and saving the page (/api/pages/pageSlug/changes/publish) takes around ten seconds. From my investigation, the slowdown seems to be caused by the line in my blueprint that uses query: page.skills.toStructure.

Here is my current blueprint:

title: Software Skill Relations
tabs:
  software:
    label: Software
    icon: display
    fields:
      software:
        label: Software
        type: structure
        fields:
          title:
            label: Titel
            type: text
          softwareSkill:
            label: Skills
            type: multiselect
            options:
              type: query
              query: page.skills.toStructure
              text: "{{ structureItem.title }}"
              value: "{{ structureItem.title }}"
  skills:
    label: Skills
    icon: star
    fields:
      skills:
        label: Skills
        type: structure
        fields:
          title:
            label: Titel
            type: text

I have already searched the forum for similar topics but could not find an exact match. I did, however, come across a discussion suggesting the use of subpages instead of structure fields for performance reasons (Speed advantage: structure or pages?) and another post that mentions using PHP-based blueprints to cache query results (When is Kirby not the right fit?).

Given that Kirby is a flat-file CMS without a relational database, I understand there are limitations, but I would have expected that handling 100 entries with a handful of relations should still be manageable with modern server performance. I would really appreciate any suggestions, possible directions to explore, experiences from similar setups, or specific tips for optimizing toStructure queries in my blueprints. I can also gladly provide a sample page content file if that would help others test this scenario.

Thanks a lot for your time, and for the great work on Kirby :heart:

if you have 100 software entries you will have 100 skills in the multiselect for each. thats 10,000 elements, no lazy loading etc. 100x the call to resolve the query as well. Kirby does not cache that queries for you for each individual item.

there will be improvements with the new (optional) AST-based query parsing. but you still have the same amout of 10k elements.

a possible solution would be to
a) use pages since the select will happen in a seperate dialog (thus lazy from the main view)
b) build a multiselect that only loads its options lazyly when it is used.

It’s mainly the Panel that is slow here. You custom fontend will not be affected.

I followed your idea of creating a custom multiselect, and it works beautifully — fast and with the same behavior as the default multiselect.

I created a simple plugin similar to what @steirico did:

// index.js
panel.plugin('user/multiselect-fast', {
    fields: {
        'multiselect-fast': {
            extends: 'k-multiselect-field',
        }
    }
});
// index.php
<?php

Kirby::plugin('user/multiselect-fast', [
    'fields' => [
        'multiselect-fast' => [
            'props' => [
                'type' => 'multiselect', // neccesary for correct display in structure column
                'options' => function () {
                    $page = page('pageSlugWithTheStructure');
                    
                    if (!$page) {
                        return [];
                    }

                    $items = $page->skills()->toStructure();
                    
                    $options = [];
                    foreach ($items as $item) {
                        $options[] = [
                            'value' => $item->title()->value,
                            'text' => $item->title()->value
                        ];
                    }

                    return $options;
                }
            ]
        ]
    ]
]);

And instead of using the default multiselect I use mine in the blueprint:

softwareSkill:
            label: Skills
            type: multiselect-fast

This keeps the panel snappy even with large datasets. Loading and Saving is in the realm of miliseconds again.

Honestly, I’m a bit surprised how simple the solution is. I’m not really into the nitty-gritty of the Kirby core, but why is the default multiselect handling this issue that different?