xylo-is-coding / vanilla-cms
A minimal, framework-free, PHP library for defining content types, storing their data, routing requests to pages, and editing content through a small admin panel.
Requires
- php: >=8.4
Requires (Dev)
None
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-09-22 10:07:18 UTC
README
A minimal, framework-free, PHP library for defining content types, storing their data, routing requests to pages, and editing content through a small admin panel.
What's included
A Router class handles routing requests to pages. In your index.php file, you can use Router::dispatch,
passing in an array of RouterDispatcher to associate url patterns with a callback function. The library comes with
a set of default dispatchers (default_router_dispatchers()), which handles routing to pages and archetypes, using
their slugs, and to the admin panel.
Site pages are defined by Page objects: extend Page, implement the Page::render_internal method, and provide the
parameters requested by the parent's constructor. Finally, register the page through register_pages() which takes in an
array of Page classes.
In Page classes you can define Field derived members, which must be initialized in the constructor. These fields will
show up in the built-in admin panel to edit their values, and they can be freely accessed in the Page::render_internal method.
The library has an Admin panel, which allows editing and creating pages from the types that were registered. Pages can be either 'simple' or 'archetypes'. All non-archetype pages appear in the admin panel under the same tab. The system expects them to exist, and only one instance of those can be created. There is a tab for each page archetype, from there you can manage its instances (e.g., adding or modifying new posts in a blog).
To fully initialize the CMS, you must call Auth::set passing in a AuthDriver object, and a link to the login page.
You must also call Storage::set passing in a StorageDriver object, to initialize the database connection. The library
comes with a JsonStorage class, which can be created by passing in a root directory for the JSON files to be stored in.
The final setup step is to call AdminAssets::set to register the admin panel .js and .css files (the library comes with
a default theme you can use).
Minimal example
.htaccess
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.php [L]
index.php
// Register the page types
register_pages([
HomePage::class,
ArchivePage::class,
ProductPage::class,
LoginPage::class,
]);
// Set auth driver class for admin pannel
Auth::set(new class implements AuthDriver {
public function isAdmin(): bool
{
return true;
}
}, '/login');
// Set storage driver
Storage::set(new JsonStorage(__DIR__ . '/../storage'));
Storage::setUploadsRoot(__DIR__ . '/../public/uploads', '/uploads');
if (extension_loaded('gd')) {
Storage::setImageSrcsetGenerator(new GdImageSrcsetGenerator());
}
// Set admin assets
AdminAssets::set('/path/to/admin.css', '/path/to/admin.css');
vcms_register_default_admin_tabs();
Router::dispatch(vcms_default_router_dispatchers());
namespace App\Pages;
use VanillaCms\Pages\Page;
use VanillaCms\Fields\TextField;
class ProductPage extends Page {
public TextField $productName;
public function __construct()
{
parent::__construct('product', 'Product', true);
$this->productName = new TextField(['label' => 'Name']);
}
public function render_internal(): void
{
require_once __DIR__ . '/../includes/header.php';
require_once __DIR__ . '/../includes/footer.php';
generate_header($this->meta()->name());
?>
<h1><?= htmlspecialchars($this->label()) ?>: <?= htmlspecialchars($this->meta()->slug()) ?></h1>
<p>Name: <?= htmlspecialchars($this->productName->getText()) ?></p>
<?php
generate_footer();
}
}
<?php
namespace App\Pages;
use VanillaCms\Admin\AdminController;
use VanillaCms\Auth\Auth;
use VanillaCms\Pages\Page;
use VanillaCms\Core\Router\Router;
class LoginPage extends Page {
public function __construct()
{
parent::__construct('login', 'Login', false);
}
public function render_internal(): void
{
// If the user is already logged in, redirect to the page they were trying to access before logging in.
if (Auth::isAdmin()) {
Router::return(AdminController::getHomeUrl());
}
require_once __DIR__ . '/../includes/header.php';
require_once __DIR__ . '/../includes/footer.php';
generate_header($this->meta()->name());
?>
<h1>Login</h1>
<p>Login is required</p>
<?php
generate_footer();
}
}
Requirements
- PHP >= 8.4
Installation
composer require xylo-is-coding/vanilla-cms