Override System
Everything in phlat/ is a default. Place a matching file in site/ and it takes precedence — no configuration required.
How it works
PhlatPage resolves files by checking site/ first, then falling back to phlat/:
site/views/default.latte ← used if it exists
phlat/views/default.latte ← fallback
site/routers/shop.php ← used if it exists
phlat/routers/shop.php ← fallback
site/controllers/app.php ← used if it exists
phlat/controllers/app.php ← fallback
What can be overridden
| Type | Location |
|---|---|
| Views | site/views/{view}.latte |
| View field definitions | site/views/{view}.json |
| Routers | site/routers/{view}.php |
| Controllers | site/controllers/{view}.php |
| Field types | site/fields/{type}/ |
| Users | site/users/{slug}/ |
Pages are different
Pages are the exception — they live only in site/pages/ and are entirely user-controlled. The framework never ships built-in pages.
Replacing vs extending
An override fully replaces the framework default — there is no merge at the file level. To extend a router rather than replace it, invoke the framework version from inside your override:
<?php
// site/routers/admin.php
use Phlat\App;
use Phlat\Page;
return function (App $app, Page $page): void {
// include all framework admin routes
(include "{$app->system}/routers/admin.php")($app, $page);
// then add site-specific routes
$page->router->get('reports', fn() => null);
};
Overriding a field type
Replace the built-in markdown field with one that supports GitHub Flavored Markdown:
<?php
// site/fields/markdown/markdown.php
namespace Phlat;
use League\CommonMark\GithubFlavoredMarkdownConverter;
class FieldMarkdown extends Field
{
public function decode(): mixed
{
if (!$this->value) return '';
return (new GithubFlavoredMarkdownConverter())
->convert((string) $this->value)
->getContent();
}
}
Addons
Addons extend framework behaviour without replacing files. Each addon is a folder containing index.php, executed during App::execute(). Addons can register dependencies or attach tooling — but they cannot register their own page URLs. Routes must be wired by pages.
site/addons/
analytics/
index.php ← receives $app and $path as locals
Addons in site/addons/ override matching addons in phlat/addons/.
<?php
// site/routers/admin.php — extends phlat/routers/admin.php
use Phlat\App;
use Phlat\Page;
return function (App $app, Page $page): void {
// pull in all framework admin routes first
(include "{$app->system}/routers/admin.php")($app, $page);
// add site-specific admin routes
$page->router->get('reports', fn() => null);
$page->router->get('export', fn() => null);
};