preflow/folio

Folio — drop-in CMS for Preflow: shipped admin, JSON content types, slug rendering

Maintainers

Package info

github.com/getpreflow/folio

pkg:composer/preflow/folio

Statistics

Installs: 3

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

dev-main 2026-06-19 07:59 UTC

This package is auto-updated.

Last update: 2026-06-19 10:37:16 UTC


README

Folio is a drop-in CMS for Preflow. Require it into any Preflow app and you get a shipped admin (mounted at a configurable path), content types defined as JSON, and slug-based frontend rendering — without touching the framework.

Installation

composer require preflow/folio

Requires PHP 8.4+. Composes preflow/data, preflow/view, preflow/routing, and preflow/form (all already present in a standard Preflow app).

Pre-1.0: Preflow currently publishes dev-main only, so your project must allow dev stability. The Preflow skeleton already sets "minimum-stability": "dev" and "prefer-stable": true. If you're adding Folio to a different setup, add those to your composer.json (or require it explicitly with composer require preflow/folio:@dev).

Getting started

Folio is opt-in: a fresh Preflow skeleton does not wire it for you. Three small steps.

1. Register the service provider

In config/providers.php:

return [
    \App\Providers\AppServiceProvider::class,
    \Preflow\Folio\FolioServiceProvider::class,
];

2. Choose the admin mount path (optional)

The admin mounts at /folio by default. To change it, add config/folio.php:

<?php

return [
    'path' => '/admin', // or whatever doesn't collide with your app
];

3. Define a content type

Content types are JSON files in config/models/ (the existing data.models_path). Create config/models/page.json:

{
    "key": "page",
    "table": "page",
    "storage": "json",
    "id_field": "uuid",
    "label": "Pages",
    "fields": {
        "title":  { "type": "string", "searchable": true, "validate": ["required"] },
        "slug":   { "type": "string", "searchable": true, "validate": ["required"] },
        "body":   { "type": "text" },
        "status": { "type": "string", "validate": ["required", "in:draft,published"] }
    }
}

That's it. Start the app and visit /folio — a Pages area appears with create / edit / delete. Pages with status: published render on the public frontend at their slug (e.g. /about), resolved by a lowest-priority catch-all so your own routes always win.

storage: "json" writes one flat file per record under storage/data/page/ — zero config, no migration. Switch a type to a database by changing storage to a configured driver (e.g. sqlite); the admin and rendering are storage-agnostic.

How it fits together

Concern Owner
Type definition (page.json) preflow/data (TypeRegistry, DynamicRecord)
Record CRUD + validation preflow/data (DataManager::*Type())
Auto-form from the type preflow/form
Shipped admin (dashboard, CRUD), routing, slug rendering preflow/folio

Admin and frontend templates live under @folio/* and are overridable: drop a same-named template in resources/folio/ to replace the shipped one.

Overriding a core admin action

Shadow a single core action without forking by adding a class under App\Folio\Overrides\{Controller}\{Action} implementing OverridableAction:

namespace App\Folio\Overrides\Content;

use Preflow\Folio\Override\OverridableAction;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;

final class Index implements OverridableAction
{
    public function handle(ServerRequestInterface $request): ResponseInterface
    {
        // your custom dashboard
    }
}

Admin styling

The admin ships a single stylesheet served by Folio itself at {prefix}/_assets/admin.css (no build step, no asset publishing required). The <link> URL is content-hash versioned via the folio_admin_css_url Twig global, so it caches immutably and busts on change.

Dark mode is built in: the stylesheet defines warm-neutral tokens with an emerald accent and a [data-theme="dark"] override. The default follows the operating system (prefers-color-scheme); a sidebar toggle lets the user override it, persisted in localStorage with a no-flash inline script.

Every template is overridable via the @folio namespace (drop replacements in resources/folio/). Because the admin renders through action-mode controllers, the stylesheet link and the small theme scripts are plain markup in _layout.twig rather than going through the asset collector. If you serve the admin under a strict Content-Security-Policy, allow style-src 'self' for the stylesheet and either nonce or allow the two inline theme scripts (or override _layout.twig to suit your policy).

Status

Walking skeleton. Shipped: type discovery, auto-form CRUD, slug rendering, per-action overrides. Not yet: rich-text / relation / asset / matrix fields, live preview, the full extension layer, admin styling/assets, settings/users/media areas, i18n and versioning. Templates are intentionally unstyled at this stage.