webreinvent/vaahcms
VaahCMS is an open-source modular CMS built on Laravel. It provides a ready-made admin panel, CRUD generator, module and theme management, role-based permissions, APIs, and media tools to help teams launch and manage web applications faster.
VaahCMS is a Laravel application platform rather than a drop-in library — you get a full app skeleton with an admin panel, module/theme system, and a headless CMS on top of Laravel.
Minimum setup
composer create-project webreinvent/vaahcms my-app
cd my-app
cp .env.example .env
php artisan key:generate
# configure DB in .env
php artisan vaah:install # interactive: publishes config, migrates, seeds admin user
npm install && npm run build # or `npm run dev` for hot reload
php artisan serve
Log in at /vaahcms with the credentials you set during install.
Where to look first
config/vaahcms.php — the platform's knobs (route prefixes, middleware, module/theme paths, API settings).Modules/ and Themes/ — everything you build lives here; the core in vendor/webreinvent/vaahcms is off-limits.php artisan route:list — confirms the actual admin and API route prefixes in your install (they vary by version/config).First use case: create a Products module via the admin UI's CRUD generator (or php artisan vaah:module), define fields, and you have a working list/create/edit/delete UI plus backing tables in minutes. If you don't need the UI, define a content type in the headless CMS instead and consume it over the JSON API.
Module-centric development. Treat every feature as a module. A module is self-contained: Http/Controllers, Models, Database/Migrations, Database/Seeders, Routes/, Vue/ (or Resources/), Config/, and a service provider registered through VaahCMS's module registry (stored in vh_modules with is_active). Activate it in the admin UI or via the artisan activation command, and always ship a seeder for permissions/roles your module needs.
CRUD generation as a starting point, not a destination. The generator gives you models, controllers, migrations, and Vue views wired together. Immediately afterwards: tighten validation rules in FormRequests, move business rules into services/actions, and replace the generated table columns with what you actually need. Generated code is scaffolding.
Headless CMS pattern. Define content types (with fields, taxonomies, relations) in the admin, author entries, then fetch them from a frontend (Next.js, Nuxt, mobile) via the CMS API. Authenticate with Sanctum tokens; keep a dedicated "API" role with tightly scoped permissions rather than reusing an admin token. Cache responses at the edge/CDN — CMS content changes infrequently and the API is read-heavy.
Theme development. Themes own the public-facing Blade layouts and assets. The usual pattern is Blade shells that mount Vue islands for interactivity, with data passed via @json(...) or a window global. Keep theme layout inheritance shallow — deep @extends chains across themes are painful to debug.
Cross-cutting logic. Use Laravel service providers and module boot methods for hooks; register policies, event listeners, and scheduled commands inside your module's provider so the module stays portable. Prefer events/listeners over editing core files.
Integration flow for an existing Laravel app. If you're adding VaahCMS to a mature app, run install into a branch, migrate the existing schema alongside, and mount VaahCMS routes under a subdomain or prefixed path via config/vaahcms.php. Keep your domain models in app/ and let modules depend on them rather than duplicating.
Environments/CI. Script the install non-interactively, run php artisan migrate --force, php artisan optimize, npm ci && npm run build, and php artisan storage:link. Seed admin credentials from env in CI only.
vendor/. Upgrading VaahCMS will wipe changes. Everything custom goes in Modules/, Themes/, or app/. If you truly need to override core behavior, extend the class and rebind it in a provider.php artisan config:cache can hide newly added modules if discovery reads config at boot. After adding/activating a module: php artisan cache:clear && php artisan config:clear, then reactivate if the admin still doesn't see it.php artisan migrate may silently skip them. When a table is missing, check the module is active and its provider is loaded before hunting for a schema bug.npm run dev after adding new Vue files so the glob picks them up.vh_roles, vh_permissions, vh_role_user), not Spatie. Permissions are string slugs; seed them per module and assign them to roles in the module seeder, otherwise the admin menu 403s for non-super-admins.webreinvent/vaahcms constraint, read the changelog before bumping, and test module activation against a fresh DB — schema and route conventions have shifted across 2.x → 3.x.config/cors.php must both include your frontend origin; a 419 on token requests is usually Sanctum, a CORS error is cors.php. Confirm the actual API base path with php artisan route:list --path=api.acme_products) and namespace models to avoid clashing with core tables during migration.storage/logs/laravel.log → php artisan route:list (is the route even there?) → php artisan about / module activation status → browser console for Vue build issues. The community docs are thin; reading a core module's source is often faster than searching.How can I help you explore Laravel packages today?