Skip to content

Folders & Navigation

Folders appear in the sidebar and filter the list view. Every model gets default folders ("All", "Trash", "Checked Out"), but you can add custom folders with any filter logic.

Static Folders in meta()

The simplest way to add folders is in meta():

use System\CurrentUser;
use System\Data\Database;

public static function meta(): ?ModelMeta
{
    return (new ModelMeta('Tickets', 'ticket'))
        ->withSingular('Ticket')
        ->enable()

        // Static folder with a record finder closure
        ->folder(
            'tickets.own',       // unique key
            'Own',               // display label
            'user',              // Phosphor icon
            'default',           // CSS class
            '',                  // parent folder key (empty = root)
            fn(Database $db) => $db->find(Ticket::class, [
                'owner_id' => CurrentUser::id()
            ])
        )

        ->folder(
            'tickets.others',
            'Others',
            'users',
            'default',
            '',
            fn(Database $db) => $db->find(Ticket::class, [
                'owner_id' => ':NOT(' . CurrentUser::id() . ')'
            ])
        );
}

Folder Parameters

1
2
3
4
5
6
7
8
->folder(
    string $key,        // unique identifier (used in ?folder= URL)
    string $label,      // display label (auto-wrapped in Lang::t())
    string $icon,       // Phosphor icon name
    string $cssClass,   // CSS class ('default', 'danger', etc.)
    string $parent,     // parent folder key for nesting ('' = root)
    callable $finder    // closure that returns records for this folder
)

Dynamic Folders with withFolderBuilder()

For folders that need runtime logic (e.g. one folder per record type), use withFolderBuilder():

public static function meta(): ?ModelMeta
{
    return (new ModelMeta('Records', 'folder'))
        ->withSingular('Record')
        ->enable()
        ->withFolderBuilder(function (\System\AppPage $page, string $className) {

            // Folder: Modified today
            $page->folder('today', 'Modified today', 'calendar', 'default', 'all',
                function (Database $db, string $class) {
                    $today = (new \DateTime('midnight'))->format('Y-m-d');
                    $tomorrow = (new \DateTime('tomorrow'))->format('Y-m-d');
                    return $db->findRaw($class,
                        'modified_on BETWEEN ? AND ?',
                        [$today, $tomorrow]
                    );
                }
            );

            // Folder: Modified this month
            $page->folder('month', 'Modified this month', 'calendar', 'default', 'all',
                function (Database $db, string $class) {
                    $start = (new \DateTime('first day of this month'))->format('Y-m-d');
                    $end = (new \DateTime('first day of next month'))->format('Y-m-d');
                    return $db->findRaw($class,
                        'modified_on BETWEEN ? AND ?',
                        [$start, $end]
                    );
                }
            );
        });
}

Dynamic Folders Per Record

Create one folder per lookup record (e.g. one folder per record type):

->withFolderBuilder(function (\System\AppPage $page, string $className) {

    // Create a parent folder
    $page->folder('by_type', 'By Type', 'folder', 'default', 'all',
        fn() => null   // parent folder doesn't show records itself
    );

    // One child folder per RecordType
    $types = Database::instance()->find(RecordType::class, []);
    foreach ($types as $type) {
        $typeId = $type->getId();
        $page->folder(
            "type_{$typeId}",
            $type->name,
            'tag',
            'default',
            'by_type',   // nested under parent
            fn(Database $db, string $class) => $db->find($class, [
                'type_id' => $typeId
            ])
        );
    }
})

Folder Labels with i18n

Folder labels passed to ->folder() are automatically wrapped in Lang::t(). No additional wrapping needed.

For dynamic labels, use Lang::t() explicitly:

1
2
3
4
5
$page->folder(
    "type_{$id}",
    Lang::t("Record.recordtype.{$type->name}"),
    'tag'
);