Introduction
Overview
ValPress is a modern, Laravel-based content management system (CMS) built as a pragmatic alternative to WordPress. It combines Laravel’s developer-first tooling with a familiar CMS experience: plugins, themes, hooks (actions and filters), a clean admin panel, a flexible content model, and an official Marketplace where developers can publish and sell their extensions. ValPress targets teams that want the productivity of a CMS without compromising on code quality, performance, or maintainability — and gives extension authors a platform to build a business around their work. For a detailed comparison of similarities and differences, see ValPress vs WordPress. For comparisons with other Laravel CMS platforms, see Statamic, October CMS, and Winter CMS.
ValPress embraces Laravel conventions: service container, middleware, routing, Blade, Eloquent, queues, and the full ecosystem of packages — while adding a lightweight CMS layer for content, media, theming, and extensibility.
What's new in ValPress 0.6
- Update Center — staged verify/apply pipeline with readable reports for core, plugin, and theme updates
- Snapshots & rollback — automatic core file snapshot before apply; restore from the admin UI
- Repair actions — fix common failures (caches, storage link, plugin migrations) from the update report
- Extension validation — plugins and themes implement
ValidatesUpdateto block unsafe updates - Plugin/theme updates in UI — update extensions from the Update Center without SSH
- Developer tooling —
php artisan vp:make-plugin-validation {slug}and a reference example plugin
See Updates & Maintenance and Update Pipeline for details.
Why It Matters
- Developer experience: Build with Laravel patterns you already know — no custom DSLs or hidden magic.
- Extensibility: Plugins, themes, and a robust hooks system allow clean customization without editing core files.
- A market for builders: ValPress was created not only as a CMS, but as the foundation for a new commercial ecosystem. The WordPress plugin and theme market is mature and crowded — established authors with large catalogs dominate discovery, making it difficult for new developers to gain traction and earn from their products. ValPress and its Marketplace offer a growing alternative: a curated platform where quality extensions can find an audience, and where new authors compete on merit rather than years of accumulated marketplace presence.
- Performance: Cache-friendly architecture and an intentionally small core keep request handling fast.
- Maintainability: Clear separation of concerns (core vs. plugins/themes), testable code, and Laravel-native tooling.
- Migration-friendly: Familiar database structure and Eloquent models ease data migrations and imports/exports.
How It Works
At a high level, ValPress bootstraps a Laravel application and layers CMS features on top.
Conceptual architecture (text diagram):
- HTTP Kernel (Laravel)
- Global middleware (auth, CSRF, caching)
- Route handling (web/admin)
- ValPress Core
- Post types and taxonomies (Eloquent models, repositories)
- Options/settings (configurable key-value storage)
- Media management
- Admin UI (Blade views + policies/gates)
- Hook system (actions/filters)
- Extensions
- Plugins (business features, integrations)
- Themes (presentation, Blade templates, assets)
Request lifecycle (simplified):
1) Request enters Laravel’s HTTP kernel and passes middleware. 2) Routes resolve to controllers (core or plugin-provided via registration APIs). 3) Hooks fire (e.g., action events around rendering or admin screens). 4) Controller composes data (Eloquent), renders Blade view (theme/admin), and returns a response. 5) Response may be filtered via output-related hooks.
Usage
Use ValPress in two primary modes:
1) Site building without custom code
- Install ValPress, choose a theme, enable plugins.
- Configure content types, menus, and settings from Admin.
2) Application development with custom code (recommended for teams)
- Create a plugin for domain-specific features and integrations.
- Register routes, controllers, Blade views, and migrations inside your plugin.
- Use hooks to extend or alter behavior without forking core.
3) Building and selling extensions
- Develop plugins or themes for the ValPress ecosystem.
- Publish to the ValPress Marketplace and reach users who are actively looking for ValPress-compatible products.
- See Plugins, Themes, and Licensing for publication requirements.
What you need to know first:
- Laravel basics: routes, controllers, Blade, Eloquent, config, env.
- ValPress extensions: plugins (logic), themes (presentation), hooks (glue).
Code Examples
The following examples illustrate typical, production-grade usage patterns. They are intentionally explicit and commented for clarity.
Example 1 — Minimal plugin structure with admin menu and content filter:
<?php // public/plugins/acme-contacts/plugin.php
/*
Plugin Name: Acme Contacts
Description: Manage and display contacts; demonstrates routes, admin menu, and a simple filter.
Version: 1.0.0
Author: Acme, Inc.
*/
// 1) Register Blade view namespace for this plugin
view()->addNamespace('acme-contacts', __DIR__ . '/views');
// 2) Add an Admin menu item via a filter (the Admin collects items through `valpress_admin_menu_items`)
add_filter('valpress_admin_menu_items', function (array $items): array {
if (!is_plugin_active('acme-contacts')) {
return $items;
}
$items['acme-contacts'] = [
'id' => 'acme-contacts',
'title' => __('Contacts'),
'url' => fn () => route('admin.acme_contacts.index'),
'icon' => 'bi-people',
'order' => 30,
'parent' => null,
];
return $items;
});
// 3) Filter post content on the frontend
add_filter('the_content', function (string $content): string {
// Append a disclosure notice — keep it simple and idempotent
$notice = '<p class="text-muted small">Content enhanced by Acme Contacts</p>';
return $content . $notice;
});
// 4) Register routes when ValPress initializes
add_action('valpress_init', function () {
// Use ValPress/Laravel routing to define admin endpoints for the plugin
\Route::middleware(['web', 'auth', 'can:manage-content'])
->prefix('admin/acme-contacts')
->name('admin.acme_contacts.')
->group(function () {
\Route::get('/', [\Acme\Contacts\Http\Controllers\ContactsController::class, 'index'])
->name('index');
});
});
Plugin controller and a view:
<?php // public/plugins/acme-contacts/src/Http/Controllers/ContactsController.php
namespace Acme\Contacts\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\View\View;
class ContactsController
{
// Display a paginated list of contacts stored in plugin tables or core posts
public function index(Request $request): View
{
// In a real plugin, fetch Eloquent models; shown as static array for brevity
$contacts = collect([
['name' => 'Ada Lovelace', 'email' => 'ada@example.com'],
['name' => 'Alan Turing', 'email' => 'alan@example.com'],
]);
return view('acme-contacts::admin.index', [
'contacts' => $contacts,
]);
}
}
{{-- public/plugins/acme-contacts/views/admin/index.blade.php --}}
@extends('admin.layouts.app')
@section('title', __('Contacts'))
@section('content')
<div class="container py-4">
<h1 class="h3 mb-3">@lang('Contacts')</h1>
<div class="card">
<div class="table-responsive">
<table class="table table-striped align-middle mb-0">
<thead>
<tr>
<th>@lang('Name')</th>
<th>@lang('Email')</th>
</tr>
</thead>
<tbody>
@foreach($contacts as $contact)
<tr>
<td>{{ $contact['name'] }}</td>
<td>
<a href="mailto:{{ $contact['email'] }}">{{ $contact['email'] }}</a>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
</div>
@endsection
Example 2 — Theme snippets (Blade) using template tags and hooks:
{{-- public/themes/acme-blog/views/single.blade.php --}}
@extends('theme::layouts.app')
@section('title', e($post->title))
@section('content')
<article class="container py-5">
<header class="mb-4">
<h1 class="display-5">{{ $post->title }}</h1>
<p class="text-muted">{{ $post->published_at?->format('M d, Y') }}</p>
</header>
{{-- Featured image (template tag provided by core) --}}
{!! the_featured_image($post, ['class' => 'img-fluid rounded mb-4']) !!}
{{-- Main content (already passed through `the_content` filters) --}}
<div class="content">{!! $post->content !!}</div>
{{-- Post footer hooks for plugins to inject sharing widgets, etc. --}}
@php(do_action('theme_post_footer', $post))
</article>
@endsection
Advanced Usage
- Custom post types (CPTs): Register programmatically in a plugin or via Admin UI to model domain entities (e.g., Events, Products). CPTs integrate with taxonomies, permalinks, and admin listing screens.
- Admin customization: Use filters such as
valpress_admin_menu_itemsto contribute structured menus and submenus. Use actions to enqueue assets on specific screens, or to hook into saving/publishing flows. - Routing: Keep plugin routes namespaced and behind relevant middleware (auth, authorization). Prefer controller classes to closures for testability.
- Caching: Leverage Laravel caches for expensive queries; clear affected caches in relevant action hooks.
- Testing: Place plugin tests under
public/plugins/{slug}/testsand run viaphp artisan testusing SQLite for speed.
Best Practices
- Never modify core files; implement features in plugins or themes.
- Keep plugins focused: one responsibility per plugin to ease maintenance and updates.
- Use hooks for integration points; avoid hard-coding references to other plugins.
- Follow PSR-12 and Laravel naming conventions; prefer dependency injection.
- Localize all user-facing strings with
__()and@langfor i18n. - Validate and authorize all admin actions; use policies/gates where appropriate.
- Ship database migrations with plugins that own tables; write idempotent migrations.
- Keep Blade templates slim; push data preparation to controllers/view models.
Common Mistakes
- Editing files under
app/Coreor vendor directories — customization should live in plugins/themes. - Registering routes without middleware, exposing admin endpoints publicly.
- Echoing raw HTML without sanitization; always escape or trust only sanitized content.
- Bypassing hooks and tightly coupling plugins; prefer decoupled communication through actions/filters.
- Mixing presentation and business logic in Blade views.
Summary
ValPress brings a Laravel-native development model to a CMS context. Build features as plugins, present them with themes, and connect everything through a simple hooks system. You retain the full power of Laravel — routing, middleware, Blade, Eloquent — while gaining a focused CMS layer that stays out of your way. For developers looking beyond client work, the Marketplace opens a path to sell extensions in an ecosystem that is still taking shape — a deliberate alternative to competing in saturated legacy marketplaces. This introduction set expectations and provided working examples you can adapt as you build.
Compatibility and versioning note: This document targets ValPress 1.x. When upgrading to 2.x, review the release notes for breaking changes, particularly around hook names, admin UI contracts, and any modified configuration keys. Prefer forward-compatible patterns (namespaced route names, DI in controllers, idempotent migrations) to reduce friction when upgrading.