Mantra CMS
Lightweight flat-file Content Management System built with PHP
Mantra is a modern flat-file CMS that requires no database, no Composer, and no external dependencies. All content is stored as JSON or Markdown files. Just upload the files to your server and you're ready to go.
- Zero dependencies -- no Composer, no database, no external libraries needed in production
- Flat-file storage -- content stored as JSON/Markdown in
content/, easy to backup and version control - Modular architecture -- extend functionality through self-contained modules with dependency resolution
- Hook/event system -- priority-based pub/sub bus for decoupled module communication
- Admin panel -- built-in CRUD scaffolding for pages, posts, users, uploads, and permissions
- Role-based access control -- three roles (admin, editor, viewer) with granular per-permission overrides and ownership checks
- Theme engine -- template hierarchy with smart fallback (theme > module > core), partials, and hook injection points
- Multi-language -- built-in i18n with English and Russian, extensible via language files
- Atomic file I/O -- exclusive locking and temp-file-then-rename writes prevent corruption under concurrency
- Schema migrations -- lazy, automatic JSON document upgrades on read (no manual scripts needed)
- PSR-3 logging -- daily-rotated log files with channel support
- CSRF protection -- built-in token generation and verification
- Markdown support -- write content in Markdown with frontmatter or JSON
| Requirement | Details |
|---|---|
| PHP | >= 8.1 |
| Extensions | json, session, openssl |
| Web server | Apache with mod_rewrite or Nginx |
| File system | Write access to content/, storage/, uploads/ |
git clone https://github.com/belomaxorka/mantra.git
cd mantraphp -S 127.0.0.1:8000 index.phpOpen http://127.0.0.1:8000/ in your browser. The installer will guide you through creating an admin account and initial configuration.
After installation:
| URL | Description |
|---|---|
/ |
Public site |
/admin |
Admin panel |
- Point your web server document root at the project directory (where
index.phplives). - Ensure
content/,storage/, anduploads/are writable by the web server. - The included
.htaccesshandles URL rewriting and blocks access to sensitive files. For Nginx, configure equivalent rewrite rules.
mantra/
├── core/ # Core system classes and helpers
│ ├── classes/ # Application, Database, Router, View, Auth, ...
│ ├── helpers/ # Global helper functions
│ ├── lang/ # Core language files (en, ru)
│ └── schemas/ # Document schema definitions
├── modules/ # Modules (plugins)
│ ├── admin/ # Admin panel (core, always enabled)
│ ├── categories/ # Category management for posts
│ ├── seo/ # Meta tags, Open Graph, breadcrumbs
│ └── analytics/ # Google Analytics, Yandex Metrika
├── themes/ # Themes
│ └── default/ # Default Bootstrap 5 theme
├── content/ # Flat-file content storage
│ ├── pages/ # Page documents (*.json)
│ ├── posts/ # Post documents (*.json / *.md)
│ ├── users/ # User accounts (*.json)
│ └── settings/ # config.json
├── storage/ # Logs and cache (runtime)
├── uploads/ # User-uploaded files
├── docs/ # Developer documentation
├── index.php # Main entry point
└── install.php # Installer
Modules are self-contained extensions that live in modules/<name>/ and are enabled via config.json.
| Module | Type | Description |
|---|---|---|
admin |
Core | Admin panel with pages, posts, users, uploads, permissions, and settings management |
categories |
Feature | Category CRUD and post-to-category associations with public category pages |
seo |
Utility | Meta tags, Open Graph, Twitter Cards, breadcrumbs |
analytics |
Integration | Google Analytics (gtag) and Yandex Metrika integration |
modules/bookmarks/
├── module.json
├── BookmarksModule.php
├── lang/
│ └── en.php
└── views/
└── index.php
module.json:
{
"name": "bookmarks",
"version": "1.0.0",
"description": "Bookmark manager",
"type": "feature",
"dependencies": { "admin": ">=1.0" }
}BookmarksModule.php:
<?php
class BookmarksModule extends Module
{
public function init()
{
$this->hook('routes.register', array($this, 'registerRoutes'));
}
public function registerRoutes($data)
{
$data['router']->get('/bookmarks', array($this, 'index'));
return $data;
}
public function index()
{
return $this->view('index', array('title' => 'Bookmarks'));
}
}Enable it by adding "bookmarks" to modules.enabled in content/settings/config.json, or toggle it from the admin Settings panel.
For CRUD admin panels, extend ContentPanel -- see docs/ADMIN_PANELS.md.
Mantra uses a priority-based event bus. Modules register callbacks; the system fires hooks at key lifecycle points.
// Listen to a hook (in module init)
$this->hook('theme.head', function ($html) {
return $html . '<link rel="stylesheet" href="proxy.php?url=https%3A%2F%2Fgithub.com%2Fmy-style.css">';
});
// Fire a hook (from core or modules)
$result = app()->hooks()->fire('my.hook', $data, $context);| Hook | Purpose |
|---|---|
system.init |
System initialized, modules loaded |
routes.register |
Register routes on the router |
view.render |
Post-process rendered HTML |
theme.head |
Inject into <head> |
theme.footer |
Add scripts before </body> |
theme.navigation |
Add items to navigation menu |
admin.sidebar |
Add items to admin sidebar |
permissions.register |
Register module permissions |
Full reference: docs/HOOKS.md
Themes live in themes/<name>/ and provide templates, assets, and optional translations.
themes/my-theme/
├── theme.json
├── assets/
│ └── css/style.css
├── templates/
│ ├── layout.php # Main layout wrapper
│ ├── home.php # Homepage
│ ├── page.php # Single page
│ ├── post.php # Single post
│ ├── blog.php # Blog listing
│ ├── 404.php # Not found
│ └── partials/
│ ├── sidebar.php
│ └── pagination.php
└── lang/
└── en.php
Template hierarchy (resolved in order of specificity):
- Pages:
page-{template}>page-{slug}>page - Posts:
post-{template}>post-{category}>post-{slug}>post
Themes can override module templates by placing them at themes/{theme}/templates/partials/{module}/{partial}.php.
All settings are stored in content/settings/config.json and managed through the admin panel or edited directly.
Configuration reference
| Section | Key | Default | Description |
|---|---|---|---|
site |
name |
Mantra CMS |
Site title |
site |
url |
(auto-detected) | Base URL |
locale |
timezone |
UTC |
PHP timezone |
locale |
default_language |
en |
UI language (en, ru) |
theme |
active |
default |
Active theme directory name |
content |
format |
json |
Storage format (json or markdown) |
content |
posts_per_page |
10 |
Posts per page on blog/home |
modules |
enabled |
["admin"] |
List of enabled module IDs |
logging |
level |
debug |
Minimum log level (PSR-3) |
logging |
retention_days |
30 |
Auto-delete logs older than N days |
security |
password_hash_algo |
PASSWORD_DEFAULT |
PHP password hashing algorithm |
session |
lifetime |
7200 |
Session lifetime in seconds |
debug |
enabled |
true |
Show detailed errors |
If Mantra runs behind a reverse proxy (Nginx, Cloudflare, etc.), configure trusted proxies so client_ip() resolves the real client IP:
{
"proxy": {
"trusted_proxies": ["127.0.0.1", "::1", "10.0.0.0/8", "172.16.0.0/12"]
}
}logger()->info('User logged in', array('user_id' => 42));
logger('security')->warning('Failed login attempt');Logs are written to storage/logs/ with daily rotation ({channel}-YYYY-MM-DD.log). Log level is controlled by logging.level in config.
Dev tools require Composer (end users don't need it):
composer install
composer test # Run PHPUnit tests
composer lint # Check code style (PHP CS Fixer, dry run)
composer fix # Auto-fix code style| Document | Description |
|---|---|
| HOOKS.md | Hook system architecture and API reference |
| ADMIN_PANELS.md | Admin panel creation guide and ContentPanel API |
| VIEWS.md | Template rendering, partials, and theme override system |
| PAGINATION.md | Paginator API and integration examples |
Contributions are welcome! Please make sure to:
- Follow PSR-12 coding style
- Use Conventional Commits for commit messages
- Add tests for new functionality where applicable
- Run
composer lintbefore submitting a PR