Five minutes to set expectations. After this chapter you have not written any code yet. You have a sketch of the destination, a list of the rails you'll ride on, and a small to-do list to align your local Scriptor install with the tutorial.
What "done" looks like
At the end of chapter 6 you will be able to:
- Drop a
composer require bigins/scriptor-simple-routerinto any Scriptor install, - write a tiny
routes.phpsomewhere your site already loads (a theme bootstrap, the_ext.php, or a one-off boot snippet; chapter 5 covers the wiring options), - add one line to your theme's
_ext.php, - and reach
/api/users/123to get JSON back, with no Scriptor page resolution involved.
The finished plugin is publishable too: a tagged Composer package
that someone else can composer require from their own Scriptor
site.
The target API
Here is what the reader will be writing by the end of chapter 4. Read it like a screenshot, not a spec; we'll arrive at every line.
use Bigins\ScriptorSimpleRouter\Router;
use Bigins\ScriptorSimpleRouter\Request;
use Bigins\ScriptorSimpleRouter\Response;
$router = Router::instance();
// 1. JSON endpoint with a path parameter
$router->get('/api/users/{id}', function (Request $req): Response {
return Response::json([
'id' => (int) $req->param('id'),
'name' => 'Ada Lovelace',
]);
});
// 2. Webhook receiver. Handler is a controller class string
$router->post('/webhook/stripe', StripeWebhookController::class);
// 3. Plain-text response with a custom status code
$router->get('/healthz', fn() => Response::text('OK')->status(200));
And here is the one line of theme glue that activates it (chapter 5 unpacks every word):
// In your theme's _ext.php, before $site = new MyTheme(...):
if (\Bigins\ScriptorSimpleRouter\Router::handle()) return;
That is the entire surface. No middleware chains, no route groups,
no per-route dependency injection. Three handler shapes (Closure,
controller-class string, [Class, method] pair), four HTTP verbs,
path parameters with {name} syntax. Small on purpose.
The four moving parts
Every chapter touches one of these four boxes. By chapter 5 they all click together:
composer.jsonwith a PSR-4 autoload entry pointing atsrc/, plus a require onscriptor/scriptor. Chapter 2 walks the file line by line.- A
Pluginclass implementingScriptor\Boot\Plugin\Plugin. Plugin Discovery picks it up automatically once the package is installed. The class registers the Router singleton in the container duringregister(). - A
Routerclass that holds the route registry and runs the match algorithm. Chapter 3 builds it; it has no Scriptor dependencies at all and is unit-testable in isolation. - One line in the theme's
_ext.php. That is the only piece the site operator (not the plugin author) has to touch. Chapter 5 explains why this line is enough and where it has to sit relative to caching.
If any of those phrases feel new, the relevant Concept chapter is linked at the top of the chapter where it shows up.
The rules of the road
Three guard-rails to keep the result honest:
- Routes are defined once, at boot. No runtime route mutation from inside handlers, no lazy "register if not already there". A static, fully-known route table is the only thing that makes the matcher predictable and the plugin debuggable.
- Handlers return a
Response. They do notecho, do not set headers directly, do not callexit. The Router takes theResponseand sends it. That contract is what lets chapter 6 ship a tested plugin: tests assert on theResponse, not on captured output buffers. - The Router short-circuits BEFORE page resolution. Once
Router::handle()returnstrue, Scriptor's normal pipeline (PSR-14 events, page rendering, template loading) never runs. That is the whole point of hooking into_ext.phpinstead of subscribing toPageResolving: a JSON endpoint has no business being a fake Page.
What you will not find in this chapter (or this track)
- Comparison to FastRoute / league/route / Symfony Routing. Those libraries are excellent and solve more than Simple Router ever will. The Cookbook covers how to wrap one of them inside a Scriptor plugin if you need the extra power.
- Authentication middleware. Real APIs need it; Simple Router doesn't ship it. The Cookbook has a recipe.
- OpenAPI / spec generation. Out of scope.
To-do before chapter 2
- Working Scriptor install:
composer installclean andphp bin/scriptor installran successfully (seedocs/install.mdif you have not done this yet). -
/editor/pages/accessible with the admin credentials you set during install. - A theme with an
_ext.php(the bundledbasictheme already has one). If your current theme has no_ext.php, chapter 5 shows how to add one. Three lines, no dependencies. You do not need to do this yet. - Pick an empty directory for the plugin to live in outside
the Scriptor install. We'll use
~/code/scriptor-simple-router/throughout; substitute your own path. Chapter 2 walkscomposer initfrom inside it and shows how to point a local Scriptor at the in-progress plugin via a path repository, so you can iterate without publishing anything.
When all four boxes are ticked, open chapter 2 and write your first
composer.json.
Behind the scenes. The plugin discovery + lifecycle contract
Simple Router rides on is documented in the
Concepts → Plugin Discovery chapter.
The ScriptorPlugin interface itself lives at
boot/Plugin/Plugin.php.
The _ext.php request-pipeline hook that chapter 5 leans on lives
at
public/index.php
lines 53-67.