Project Structure
What your project looks like depends on which path you picked.
Binary CLI
leaf init creates a minimal tree. You only see content, assets, optional templates, and config.
my-docs/
content/
getting-started/
introduction.md
public/
assets/
css/app.css
js/
images/
templates/ # optional overrides (Latte, PHP, or HTML)
config.yml
dist/ # build output (deploy this)
Everything else — the framework, controllers, the build pipeline, the vendor graph — lives inside the leaf binary.
content/
Your Markdown pages. Organized into sections (directories) with pages (.md files). Each file has title and order front matter.
public/
Static assets copied verbatim to dist/. Put your logo, custom CSS overrides, fonts, and images here.
templates/ (optional)
Drop Latte/PHP/HTML files here to override the bundled defaults. The binary merges your templates on top of the bundled theme at build time. Examples:
templates/layouts/docs.latte→ overrides the bundled docs layouttemplates/partials/nav.latte→ overrides the bundled navtemplates/landing.latte→ rendered at/(if present,/no longer redirects to the first doc page)
You can use any of the three formats. Pick what fits:
- Latte for layouts, partials, blocks
- PHP (
<?= $leafName ?>) if you prefer raw PHP views - HTML if you want a static page without template variables (the binary copies it through)
To add standalone URLs like /about or /contact, drop Latte files under templates/pages/. See Custom Pages.
config.yml
Site settings: name, version, sections, production URL. See Configuration Reference.
dist/
Generated output. Static HTML, CSS, JS, sitemap.xml, robots.txt, search.json. Deploy this.
Composer template
composer create-project zephyrus-framework/leaf my-docs — or leaf eject on an existing CLI project — gives you the full Zephyrus application alongside the same content/, templates/, public/, config.yml structure:
my-docs/
app/
Controllers/
DocsController.php # Routes and page rendering
Models/Core/
Application.php # Extends Leaf\Kernel
Views/
layouts/docs.latte # HTML wrapper for docs pages
docs/page.latte # Individual doc page template
partials/ # Reusable components (nav, sidebar, footer)
404.latte # Error page
bin/
build.php # Static site build script
router.php # Dev server entry point
content/ # same as CLI
public/ # same as CLI
templates/ # optional; CLI-style overrides still work
config.yml # same as CLI
vendor/ # Composer dependencies
composer.json
dist/
Key files unique to the Composer path
app/Models/Core/Application.php
Your application class. Extends Leaf\Kernel and provides dependency injection for controllers:
use Leaf\Kernel;
final class Application extends Kernel
{
protected function createController(string $class): object
{
if ($class === DocsController::class) {
return new DocsController(
$this->contentLoader,
$this->searchIndexBuilder,
$this->leafConfig,
);
}
return new $class();
}
}
bin/build.php
The build script. A file you own and can modify:
$app = new Application();
$command = new BuildCommand($app);
exit($command->run());
Add custom paths, hook into the pipeline, post-process output. See the BuildCommand reference.
app/Controllers/
Where you add custom routes. Each controller is a Zephyrus class with #[Get], #[Post], etc. attribute-routed endpoints.
Which files are shared between the two paths?
Everything under content/, templates/, public/, and config.yml is identical. You can move a project between paths via leaf eject (CLI → Composer) or by deleting app/, bin/, composer.json, and vendor/ (Composer → CLI).