Headless CMS
Toolkit for Kirby

Keep editing in Kirby and treat it as a headless content backend. Query content via KQL, build custom API routes, and resolve blocks – one config.php for all of it.

Query Content With
KQL

Send Kirby Query Language queries to one endpoint and get JSON back. With Kirby's pages cache on, responses are cached per query and language and flushed on every content change.
  • Bearer Token Auth
    Switch the endpoint to bearer mode and one token from config.php guards it.
  • Response Caching
    Cached per query and language once the pages cache is on; one header bypasses it.
curl https://example.com/api/kql \
  -H "Authorization: Bearer your-token" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "page(\"blog\").children.listed",
    "select": {
      "title": true,
      "text": "page.text.toBlocks.toArray",
      "url": true
    }
  }'

Everything Between
Kirby and Your Frontend

Every page answers as JSON
Templates return JSON instead of HTML. Turn on global routes and each page URL serves it, drafts behind a preview token.
UUIDs become URLs and sizes
toResolvedBlocks() turns image blocks into URL, dimensions, srcset, and alt text, ready for the frontend.
A sitemap with hreflang alternates
One endpoint lists every indexable page with its URL per language. Exclude pages by template, by ID, in the blueprint, or with your own check.

Build Custom Routes With the
API Builder

Define API routes with an Express-style middleware pattern. Chain authentication, validation, and response logic.
  • Middleware Support
    Mix your own functions with built-in ones that check the token and body and resolve the language, file, and page.
  • Consistent Responses
    Api::createResponse() wraps your data in code, status, and result.
site/config/config.php
use JohannSchopplich\Headless\Api\Api;
use JohannSchopplich\Headless\Api\Middlewares;

return [
    'routes' => [
        [
            'pattern' => 'api/login',
            'method' => 'POST',
            'action' => Api::createHandler(
                Middlewares::hasBearerToken(),
                Middlewares::hasBody(...),
                function (array $context, array $args) {
                    $email = $context['body']->get('email');
                    $user = kirby()->user($email);
                    return Api::createResponse(200, [
                        'user' => $user?->email(),
                    ]);
                }
            ),
        ],
    ],
];

Open Source – Yours to Extend

Install the plugin and point your frontend at Kirby's API.