preflow / folio
Folio — drop-in CMS for Preflow: shipped admin, JSON content types, slug rendering
Requires
- php: >=8.4
- nyholm/psr7: ^1.8
- preflow/core: ^0.1 || @dev
- preflow/data: ^0.1 || @dev
- preflow/form: ^0.1 || @dev
- preflow/routing: ^0.1 || @dev
- preflow/validation: ^0.1 || @dev
- preflow/view: ^0.1 || @dev
Requires (Dev)
- phpunit/phpunit: ^11.0
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-mainonly, 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 yourcomposer.json(or require it explicitly withcomposer 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.