trovcms/trov
Trov is a Laravel CMS package for building and managing content-driven websites. It provides the foundations for editable pages, content management, and an admin experience inside a Laravel app, helping developers ship maintainable sites faster.
Trov is a start kit, not a library — you don't composer require it into an existing app, you take the repository as the seed of a new one. The practical flow:
git clone https://github.com/trovcms/trov my-site && cd my-site
composer install && npm install
cp .env.example .env && php artisan key:generate
# set DB_* credentials, then:
php artisan migrate --seed
php artisan storage:link
npm run dev # or build
php artisan serve
The admin panel is Filament-based and typically lives at /admin; create a login with php artisan make:filament-user if the seeder doesn't give you one.
Look here first:
composer.json — pin down the Laravel and Filament versions before anything else; this tells you what era of the ecosystem you're in.app/Filament/Resources/ — the content models the kit exposes (pages, posts, media, menus). This is the CMS surface area.app/Models/ — usually a Page with a slug, a JSON/blocks column, and relations to media and menus.routes/web.php — a catch-all slug route is the typical entry point for front-end rendering.database/seeders/ and database/migrations/ — the assumed content schema, which you will extend.First use case: log into /admin, create a Page, add a couple of content blocks via the form, publish it, then visit /{slug} and watch it render. Once that loop works end-to-end, you understand the whole kit: Filament form → model → Blade partial mapping.
Fork it and own it. Because this is scaffolding, the first thing to do is commit the pristine upstream state, then branch. Every subsequent change is yours and merge conflicts with upstream don't exist — there is no upstream to merge.
Content modeling by extension, not configuration. The normal day-to-day pattern is:
app/Filament/Resources/....Keep a mental model of which fields are "schema" (columns) vs. "content" (JSON inside a blocks/repeater column).
Blocks / page sections. Most kits of this shape use a Filament Builder or Repeater for page content, storing an array of {type, data} objects. The rendering pattern is a map from block type to Blade partial:
// config/blocks.php or a service provider
'hero' => 'blocks.hero',
'text' => 'blocks.text',
'gallery' => 'blocks.gallery',
Then a loop in the page view resolves each block. This is the single most useful extension point: adding a block type = one form schema entry + one partial + one map entry. Keep the type key stable forever.
Slug resolution and routing. The catch-all route is where performance and correctness live. Pattern: resolve Page::where('slug', $slug)->published()->firstOrFail(), render, and cache the resolved page object (not the HTML) behind the page's updated_at timestamp. Avoid putting menus or globals in the same query — load them once per request via a view composer or a small SiteContext service bound in the container.
Menus and navigation. Store menu trees in the DB (parent_id or nested set), expose them through a view composer so layouts can call $menu->items() without passing data through every controller.
Media. If the kit uses Spatie Media Library, use its conversions for responsive images rather than hand-rolling thumbnail logic; configure FILESYSTEM_DISK and the public disk URL correctly in production. If it uses plain Filament FileUpload, standardize on a single disk and a single directory convention early.
Deployment. Standard Laravel deploy plus: php artisan filament:cache-components (component discovery is slow in production), php artisan optimize, and a queue worker if media conversions run. Route caching works fine as long as the catch-all is last and nothing builds routes dynamically.
It is old. Last release was September 2022, which puts it in the Filament v2 / Laravel 9 era. Before you write a line of business logic, budget for a Filament v3/v4 and modern Laravel upgrade — the form/table schema APIs changed substantially (namespaces moved under Filament\Forms\Components etc., and many method signatures differ). Do the upgrade before customizing, or you'll do the work twice.
No vendor package, no maintenance contract. There's no composer update that fixes security issues for you. Treat everything in the tree as application code you are now responsible for. Check dependency versions against current advisories.
Silent authorisation failures. Filament hides navigation items and 403s actions when policies or canViewAny/canEdit return false. If a resource vanishes from the sidebar, it's almost always a missing policy or a model not registered with a policy — not a cache issue. php artisan about and the Laravel log are the fastest first checks.
Slug uniqueness with soft deletes. The classic bug: a unique validation rule on slug that ignores deleted_at, so a deleted page permanently blocks its slug. Scope the rule with ->whereNull('deleted_at'), or don't soft-delete pages.
Block schema drift. JSON-stored blocks don't get migrations. When you rename a field inside a block, write a one-off artisan command or migration to transform existing rows. Keep the block type string immutable; version the payload if you must change its shape.
Route cache surprises. The catch-all must be the final route in routes/web.php, and any route registered from a loop over DB content will break route:cache in production. Prefer a single catch-all over per-page routes.
Component discovery in production. After deploying new Filament resources or widgets, run php artisan filament:cache-components — otherwise new pages 404 while everything looks correct locally.
N+1 in resource tables. Filament tables load relationships lazily unless you tell them not to. Use ->modifyQueryUsing(fn ($q) => $q->with('author', 'media')) on the table, and eager-load the same relations in the block renderer, where it's easy to accidentally query per block.
Testing. Use RefreshDatabase with factories for pages and blocks, and write one feature test that traverses every registered block type through the renderer — that catches the orphaned-partial class of bugs before a client does. Filament's own testing helpers (livewire(EditPage::class)) cover the admin CRUD side.
How can I help you explore Laravel packages today?