The Framework for
Content Applications
VelvetCMS Core is a lightweight, explicit, no-magic PHP framework. It gives you the tools to build content-driven applications without the bloat of heavy full-stack frameworks.
Explicit by Design
Build with the same primitives you know and love, optimized for content.
Expressive Routing
Map URLs to closures or controllers with middleware, groups, and rate limiting.
- Support for GET, POST, PUT, DELETE, PATCH methods
- Named routes with programmatic URL generation
- Required, optional, and wildcard parameters
- Per-route and global middleware pipelines
$router->get('/posts/{slug}', [PostController::class, 'show'], 'posts.show');
$router->post('/contact', [ContactController::class, 'submit']);
// Optional parameters
$router->get('/archive/{year?}', function (Request $request, ?string $year = null) {
return "Archive for " . ($year ?? date('Y'));
});
// Wildcard - captures everything including slashes
$router->get('/docs/{path*}', [DocsController::class, 'render']);
Dependency Injection
Explicit core wiring for speed. Autowiring available for your classes when you want it.
- Core services manually wired for maximum performance
- Autowiring available as a pragmatic fallback for your classes
- No static proxies or facades hiding dependencies
- Standard injection patterns you already know
class PostController
{
public function __construct(
private PageService $pages,
private CacheInterface $cache
) {}
public function show(Request $request, string $slug): Response
{
$post = $this->pages->find($slug);
return view('posts.show', ['post' => $post]);
}
}
Fluent Query Builder
Expressive SQL generation with joins, subqueries, and automatic query caching.
- Prepared statements everywhere โ SQL injection impossible
- Fluent chaining for complex queries
- Aggregates, ordering, limits, and offsets
- Cache integration with the
remember()pattern
// Fluent query building
$posts = db()->table('posts')
->select('id', 'title', 'slug')
->where('status', 'published')
->whereNotNull('published_at')
->orderBy('created_at', 'desc')
->limit(10)
->get();
// Find by primary key
$user = db()->table('users')->find(1);
// Pluck single column
$emails = db()->table('users')->pluck('email');
Module System
PSR-4 autoloading, dependency resolution, and manifest-based loading.
- Self-contained modules with routes, views, commands, and migrations
- Version constraints and conflict detection
- Service provider pattern for clean bootstrapping
- Extend every part of the application lifecycle
// module.json
{
"name": "blog",
"entry": "Vendor\\Blog\\BlogModule",
"version": "1.0.0",
"requires": {
"core": ">=1.0.0 <2.0.0"
}
}
// BlogModule.php
class BlogModule extends ServiceProvider
{
public function register(): void
{
$this->app->bind(PostRepository::class, ...);
}
}
View Engine
Blade-like syntax with layouts, partials, and inheritance. Namespace support for modules.
- Familiar
{{ }}and{!! !!}syntax - Control structures:
@if,@foreach,@include - Layout inheritance with
@extends,@section,@yield - Module namespaces for organized views
// Render a view with data
return view('posts.show', ['post' => $post]);
// Namespaced module views
return view('blog::posts.index', ['posts' => $posts]);
// Share data across all views
$engine->share('siteName', 'My Site');
Task Scheduler
Fluent cron-style scheduling. Run commands or closures on any schedule.
- Define schedules in PHP โ no crontab editing
- Schedule CLI commands or PHP closures
- Fluent frequency API:
->daily(),->hourly(),->everyMinute() - WebCron endpoint for environments without cron access
$schedule = $this->app->make('schedule');
// Run a CLI command daily at 3:00 AM
$schedule->command('cache:clear')->dailyAt(3, 0);
// Run a callback every hour
$schedule->call(function () {
// Sync data from external API
$this->apiSync->run();
})->hourly();
// Run every minute
$schedule->command('heartbeat:send')->everyMinute();
Smart Caching
APCu, File, or Redis. Cache queries, pages, routes, and compiled templates.
- Multiple drivers: File (default), Redis, APCu
- Graceful degradation โ app continues if cache fails
- Simple
remember()pattern for fetch-or-compute - Cache tags for granular invalidation
$cache = app('cache');
// Store for 5 minutes (300 seconds)
$cache->set('user:123', $userData, 300);
// Retrieve with fallback
$user = $cache->get('user:123', null);
// Remember pattern - fetch from cache or compute
$posts = $cache->remember('recent-posts', 600, function () {
return db()->table('posts')
->orderBy('created_at', 'desc')
->limit(10)
->get();
});
Markdown Engine
Pluggable drivers with frontmatter extraction, tables, and task lists.
- Drivers: CommonMark (default), Parsedown, or HTML pass-through
- YAML frontmatter for metadata (title, status, layout, custom fields)
- CommonMark extensions: tables, strikethrough, autolinks, task lists
- Custom template tags preserved in all drivers
---
title: Welcome to VelvetCMS
status: published
layout: default
category: announcements
---
Welcome to **VelvetCMS**, a modular content framework.
## Features
- Fast and lightweight
- Flexible content drivers
- Modern PHP architecture
Validation Service
Standalone validator for controllers, CLI, API, and imports.
- Reusable validation logic anywhere in your app
- Rich rule set: required, email, url, min, max, regex, in, same, date, array
- Custom error messages per field
- Throws
ValidationExceptionwith structured errors
// Basic validation
$validated = Validator::make($data, [
'email' => 'required|email',
'password' => 'min:8',
])->validate();
// In controllers
$validated = $request->validate([
'title' => 'required|min:3',
'status' => 'in:draft,published',
]);
// Handle errors
try {
$validated = Validator::make($data, $rules)->validate();
} catch (ValidationException $e) {
$errors = $e->errors(); // ['field' => ['Error message']]
}
Schema Builder
Database-agnostic migrations. Write once, run on SQLite, MySQL, or PostgreSQL.
- Fluent Blueprint API for defining tables and columns
- Generates appropriate SQL for each database driver
- Migration system with up/down methods for version control
- Column modifiers: nullable, default, unsigned
use VelvetCMS\Database\Schema\Schema;
use VelvetCMS\Database\Schema\Blueprint;
Schema::create('posts', function (Blueprint $table) {
$table->id();
$table->string('title');
$table->string('slug', 100)->unique();
$table->text('content');
$table->string('status')->default('draft');
$table->timestamp('published_at')->nullable();
$table->timestamps();
});
Multi-Tenancy
Single config switch. Host, path, or custom resolvers with full isolation.
- Enable with a single config toggle โ disabled by default
- Resolvers: hostname, subdomain, path segment, or custom callback
- Tenant-aware paths for content, views, storage, and modules
- Cache prefixing and session scoping prevent cross-tenant collisions
// config/tenancy.php
return [
'enabled' => true,
'resolver' => 'host', // or 'path', 'callback'
'host' => [
'map' => [
'acme.example.com' => 'acme',
'beta.example.com' => 'beta',
],
],
];
// Tenant-aware paths (automatic when enabled):
// Content: user/tenants/{tenant}/content
// Views: user/tenants/{tenant}/views
// Storage: storage/tenants/{tenant}
No Magic
No facades, no global state, no hidden behavior. Trace every part of your app.
- Explicit over implicit โ core services wired manually
- No static proxies hiding your dependencies
- Every part of the lifecycle is traceable and debuggable
- Minimal dependencies โ four runtime packages, all battle-tested
Scale with Your Content
Start with files. Evolve to a database. Never rewrite your frontend.
File Driver
Markdown files with frontmatter. Perfect for docs, blogs, and small sites.
- Human-readable content you can edit in any text editor
- Version control friendly โ track changes with Git
- Zero database setup required
- Easy to migrate from other static site generators
Hybrid Driver
Files for editing, SQL for metadata. Best of both worlds.
- Edit content as Markdown files โ familiar workflow
- Metadata indexed in SQL for fast queries and filtering
- Scales to thousands of pages with instant lookups
- Automatic sync between files and database
Database Driver
Full SQL storage for high-volume content applications.
- Content and metadata stored entirely in the database
- Ideal for CMS with admin panels and user-generated content
- Full query capabilities for complex filtering and search
- Supports SQLite, MySQL, and PostgreSQL
Security by Default
Every layer is hardened. You have to opt out of protection.
CSRF Protection
Automatic token validation on all state-changing requests.
- Tokens generated and validated automatically
- Built into the middleware pipeline
- Configurable exclusions for APIs and webhooks
XSS Auto-escaping
All output escaped by default. Opt-in for raw HTML when needed.
{{ $var }}automatically escapes HTML entities{!! $var !!}for trusted raw HTML output- Safe mode available for rendering untrusted templates
SQL Injection Prevention
Parameterized queries everywhere. No exceptions.
- Query builder uses prepared statements for all user input
- No raw SQL interpolation in the framework
- Even raw expressions are parameterized
Secure Sessions
HTTP-only cookies, regeneration on auth, encrypted storage.
- Cookies marked HTTP-only, Secure, and SameSite by default
- Session ID regenerated on authentication events
- Path traversal protection with realpath checks
Start Building
One command to get started.