shaack / reboot-cms
A lightweight, flat-file Content Management System with block-based content rendering
Requires
- php: >=8.0
- ext-dom: *
- ext-fileinfo: *
- ext-json: *
- erusev/parsedown: ^1.7
- symfony/yaml: ^4.1
- whitehat101/apr1-md5: ~1.0
This package is not auto-updated.
Last update: 2026-03-14 02:57:34 UTC
README
A flat file, Markdown CMS in PHP, inspired by Pico, Redaxo and Craft CMS.
Reboot CMS is a minimal CMS without a database, but with the support of blocks 🚀.
Why another CMS?
I developed Reboot CMS because I couldn't find a CMS that works with flat markdown files but allows easy use of blocks.
Reboot CMS is very small and the pages are delivered extremely fast. My website shaack.com, built with Reboot CMS, has a PageSpeed Insights performance score of 100.
Websites using Reboot CMS
Minimum System Requirements
- PHP 8.0 or higher
- PHP Extensions: json, fileinfo, dom
- Web Server: Apache with mod_rewrite (or compatible server)
- Composer for PHP dependency management
- No database required — all content is stored as flat files
Install
Download the Reboot CMS repository and install it in your web root.
Then install dependencies:
composer install
This should work out of the box.
Then (important), set the Admin password in /local/.htpasswd
Directory Structure
core/src/— Core CMS classes (Reboot, Site, Page, Block, Request, AddOn)site/— Site content:pages/,blocks/,addons/,template.php,config.ymlweb/— Document root (index.php,.htaccess)local/— Local environment config (config.yml,.htpasswd) — not in gitcore/admin/— Admin interface (itself a Reboot CMS site)
Configuration
site/config.yml— Site-wide settings: addon registration, navbar withbrandandstructurelocal/config.yml— Local settings:logLevel(0=debug, 1=info, 2=error)
Template
The file site/template.php is the main HTML template. It receives three variables:
$site— theSiteobject$page— thePageobject$request— theRequestobject
Call $page->render($request) to render the page content and $page->getConfig() to access the YAML front matter.
Documentation
Page
Folder: /site/pages
A Page can be a flat Markdown file, can contain Blocks or also can be a PHP file.
Pages are auto-routed on web-requests:
index.mdorindex.phpwill be shown on requesting/NAME.mdorNAME.phpwill be shown on requesting/NAMEFOLDER/index.md(or .php) will be shown on requesting/FOLDERFOLDER/NAME.md(or .php) will be shown on requesting/FOLDER/NAME404.md(or .php) will be used as a custom 404 error page
Example for a Markdown Page with Blocks:
--- title: Reboot CMS description: Reboot CMS is a flat file CMS, with the support of blocks. author: Stefan Haack (shaack.com) --- <!-- hero --> # Reboot CMS A flat file, markdown CMS with blocks --- The main idea is, to have a **minimal CMS** without needing a database, but with the support of blocks. --- [Learn more](documentation) <!-- text-image --> ## The text-image block The gray block above was a hero block. This one is a text-image block, it contains two parts. Parts are separated by `---`. ---  <!-- text-image: image-position: left --> ## Configure blocks in the block comment The text-image block can also display the image to the left. --- > <!-- three-columns --> ### the Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est. --- ### three-colums Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquid ex ea commodi consequat. Quis aute iure reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. --- ### block Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
This Page contains 3 Block types, "hero", "text-image" and "three-columns". It will render to this:
Blocks can be configured in the block comment. With this configuration, the text-image
block allows to display the image to the left side in desktop view.
Markdown files without blocks will render to a flat Markdown page like in every other flat file CMS.
You can define metadata for the page on top of the file in YAML Front Matter syntax.
Block
Folder: /site/blocks
A Block describes how a block is rendered. Blocks are written in PHP.
The code for the "text-image" Block which was used in the page above, looks like this:
<?php // read the configuration $imagePosition = @$block->getConfig()["image-position"]; ?> <section class="block block-text-image"> <div class="container-fluid"> <div class="row"> <div class="col-md-6 <?= $imagePosition === "left" ? "order-md-1" : "" ?>"> <!-- all text from part 1 (xpath statement) --> <?= $block->nodeHtml($block->xpath("/*[part(1)]")) ?> </div> <div class="col-md-6"> <!-- using attributes of the image in part 2 --> <img class="img-fluid" src="<?= $block->nodeHtml($block->xpath("//img[part(2)]/@src")) ?>" alt="<?= $block->nodeHtml($block->xpath("//img[part(2)]/@alt")) ?>" title="<?= $block->nodeHtml($block->xpath("//img[part(2)]/@title")) ?>"/> </div> </div> </div> </section>
Elements in the markdown are queried and used as values for the block. The query syntax
is Xpath with the addition of the part(n) function.
Use $block->content() to get the full block content as rendered HTML (without XPath querying).
This is useful for simple blocks like "text":
<section class="block block-text">
<div class="container-fluid">
<?= $block->content() ?>
</div>
</section>
Another example, the "hero" Block:
<?php /* hero */ ?> <section class="block block-hero"> <div class="container-fluid"> <div class="card border-0 bg-gradient"> <div class="card-body"> <div class="p-xl-5 p-md-4 p-3"> <!-- use the text of the <h1> in part 1 for the display-4 --> <h1 class="display-4"><?= $block->nodeHtml($block->xpath("/h1[part(1)]/text()")) ?></h1> <!-- the lead will be the text of the <p> in part 1 --> <p class="lead"><?= $block->nodeHtml($block->xpath("/p[part(1)]/text()")) ?></p> <hr class="my-4"> <!-- print everything from part 2 --> <div class="mb-4"> <?= $block->nodeHtml($block->xpath("/*[part(2)]")) ?> </div> <p> <!-- the link in part 3 will be used as the primary button --> <a class="btn btn-primary btn-lg" href="<?= $block->nodeHtml($block->xpath("//a[part(3)]/@href")) ?>" role="button"><?= $block->nodeHtml($block->xpath("//a[part(3)]/text()")) ?></a> </p> </div> </div> </div> </div> </section>
Admin interface
You find the admin interface at /admin.
If no users exist yet (e.g. on a fresh installation), the admin interface will automatically show a setup page where you can create the first admin account. After that, you can log in with the credentials you created.
In the admin interface you can edit markdown pages, set the site configuration, manage users, and update the CMS.
Edit the startpage
Edit a flat markdown page
Edit the site configuration
In the site configuration, you can store global values of the site, like the navigation structure or the content of header elements. The site configuration is written in YAML.
User management
The "Users" page in the admin interface allows you to manage admin accounts directly from the browser. You can:
- Add users — create new admin accounts with a username and password
- Change passwords — update the password for any existing user
- Delete users — remove admin accounts (you cannot delete your own account)
Usernames may contain letters, numbers, and underscores (max 64 characters). Passwords must be at least 8 characters.
All credentials are stored as APR1-MD5 hashes in local/.htpasswd.
You can also manage users via the command line:
cd local htpasswd .htpasswd admin
Update
The "Update" page in the admin interface shows the currently installed version and checks for available updates from the GitHub repository.
When an update is available, you can apply it directly from the admin interface. The updater downloads the latest
release and replaces core/, web/admin/, and vendor/. Your site content (site/), local configuration (local/),
and entry point (web/index.php) are not affected.
It is recommended to make a backup of the project folder before updating.
AddOns
In Reboot CMS you can extend the functionality of your site with AddOns. AddOns allow you to hook into the request lifecycle to add authentication, modify rendered content, inject headers, track analytics, or implement any custom logic.
Creating an AddOn
An AddOn is a PHP class that extends AddOn. Place your AddOn file in
site/addons/ with the class name matching the file name.
<?php // site/addons/MyAddOn.php namespace Shaack\Reboot; use Shaack\Logger; class MyAddOn extends AddOn { protected function init() { // Called once when the AddOn is loaded Logger::info("MyAddOn initialized"); } public function preRender(Request $request): bool { // Called before page rendering // Return true to continue, false to stop (e.g. after a redirect) return true; } public function postRender(Request $request, string $content): string { // Called after page rendering, can modify the HTML output return $content; } }
Registering AddOns
Register your AddOns in site/config.yml. They are loaded and executed in the order listed:
addons: [ MyAddOn, AnotherAddOn ]
Available Properties
Inside your AddOn, you have access to:
$this->reboot— theRebootinstance (base paths, config, redirects)$this->site— theSiteinstance (site config, paths, other addons)
Lifecycle Hooks
init(): void
Called once after construction of the AddOn. Use this to initialize data, read configurations, or start sessions.
preRender(Request $request): bool
Called on every request before rendering the page. Use it to:
- Control access — check authentication and redirect unauthorized users
- Modify request handling — perform redirects based on request path or parameters
Return true to continue rendering the page, or false to stop (e.g. after calling $this->reboot->redirect()).
postRender(Request $request, string $content): string
Called after the page is rendered, before the output is sent to the browser. Use it to:
- Modify HTML output — inject scripts, stylesheets, or meta tags
- Add tracking — append analytics snippets
- Transform content — search and replace patterns in the rendered HTML
Returns the (possibly modified) content string.
Accessing AddOns from Pages
You can access a registered AddOn from any page template using:
$myAddOn = $site->getAddOn("MyAddOn");
Examples
See the included ExampleAddOn.php for a basic implementation. The admin interface itself uses AddOns: Authentication for login session handling and Admin for admin-specific functionality.