awcodes/trov
Trov is a lightweight Laravel package by awcodes that simplifies common development tasks with a clean, expressive API. Easy to install and convention-friendly, it integrates smoothly into existing Laravel applications.
Start by accepting what this is. Trov is an archived, pre-1.0 Laravel + Filament starter kit for CMS-style content, not a maintained package you composer require and forget. The realistic day-to-day posture is: clone it read-only, or fork and vendor it, and treat its conventions as a reference you own. Do not add it to a production composer.json on a floating constraint.
First hour: read the source, not the README. The README describes a generic "lightweight utility with a simple expressive API," which is not what the package is — the repo is a Filament CMS scaffold. Budget the first pass on:
composer.json — see the actual laravel/framework and filament/filament constraints. This tells you immediately whether composer require will even resolve on your stack. Run composer why-not awcodes/trov against your lockfile on a throwaway branch before anything else.src/TrovServiceProvider.php — what it registers, what it publishes (mergeConfigFrom, publishes tags), and whether it conditionally registers anything Filament-side.config/trov.php (or whatever the publish tag emits) — the config-driven surface is the whole "no code changes required" story; read every key and what reads it.database/migrations/ — the content model lives here. Tables, foreign keys, and any polymorphic/JSON field columns tell you more about the intended content model than any prose.src/Filament/Resources/ — how resources, pages, and navigation are wired.Then check the auth gap. v0.3.7 explicitly moved auth items to awcodes/filament-sentry. Grep the codebase for references to that namespace and confirm nothing dangling remains. "Who can access the admin" is now your decision, not an inherited default.
First use case to validate the pattern. Don't try to stand up a full CMS. Pick the smallest content type your roadmap actually needs — a Page with title, slug, body, and publish state — and get it end-to-end:
# On a throwaway branch, in a project matching the package's era
composer require awcodes/trov --dev # expect this to fail on modern stacks; that's data
php artisan vendor:publish --provider="Awcodes\Trov\TrovServiceProvider"
php artisan migrate
Then register one resource in a panel, create a record in the UI, and render it via a route + Blade view with route-model binding. If that loop works, the kit is demonstrating something real. If it doesn't, you've learned that in a day instead of a sprint.
Treat the content model as a schema to copy, not a base class to extend. The migrations and Eloquent models show the intended shape (slugs, publish flags, timestamps, possibly JSON field payloads). Lift that schema into first-party migrations and models. Extending package models couples you to a class hierarchy that will never be updated.
Model your Filament resources explicitly. A Filament CMS resource is conventionally Resource class + Pages/ (List, Create, Edit) + form and table schema. The reusable value in Trov is the structure — nav grouping, slug generation on the form, publish toggle in the table, filters by status. Port each of those into a resource you own:
// Illustrative shape — confirm the actual API against your Filament major
public static function form(Form $form): Form
{
return $form->schema([
TextInput::make('title')->required()->live(onBlur: true)
->afterStateUpdated(fn ($state, Set $set) => $set('slug', Str::slug($state))),
TextInput::make('slug')->required()->unique(ignoreRecord: true),
RichEditor::make('body'),
Toggle::make('is_published'),
]);
}
Isolate the CMS panel from your application panel. Give content editors their own Filament panel provider with its own path, guard, and navigation. This is doubly true here because auth was extracted — you're configuring Filament::auth() and panel access from scratch regardless. A dedicated ContentPanelProvider also means content edits don't collide with whatever your product admin is doing.
Own your authorization. Write policies for each content model. Filament's canViewAny/canEdit/canCreate on resources delegate to policies automatically. Do not assume any permission scaffolding survived the v0.3.7 auth extraction — verify by logging in as a low-privilege user and confirming the nav items disappear.
Keep config behaviour behind your own read layer. If you adopt the config-driven approach, read settings through a small first-party config facade or helper rather than sprinkling config('trov.…') through views and resources. When you swap the internals, one file changes instead of forty. Publish the config and edit your copy; never edit files in vendor/.
Render content with route-model binding and a cache in front. CMS content is read-heavy and write-light — exactly the profile for a short-TTL cache or an observer-driven cache flush on save:
Route::get('/{page:slug}', function (Page $page) {
return view('pages.show', ['page' => $page]);
})->name('page.show');
Bind on slug, not id, and add a where constraint so it doesn't shadow other routes.
Extend by replacement, not monkey-patching. The description promises "swap or override internals." In a Laravel package that means container bindings and extend() calls in your own service provider. Do the swap in your provider, keep the package's classes untouched, and you retain a clean diff if you ever re-vendor an update (you won't, but the discipline pays off when you port).
Installation is the first failure, not a runtime bug. A 2022-vintage Filament package will carry Filament 2.x / Laravel 9-era constraints. If you're on Laravel 10+ or Filament 3/4, expect Composer to refuse resolution long before you see a PHP error. Confirm with composer why-not and composer show awcodes/trov --all. If it resolves, verify the service provider actually booted: php artisan about and php artisan route:list | grep <panel-path>. A silently skipped provider that registers no routes is the failure mode that surfaces months later.
Filament 2 → 3/4 API drift is the real porting cost, not the CMS logic. The conceptual model (resources, pages, form schemas) survives. The method names don't — form()/table() versus older getFormSchema(), Filament\Resources\Form versus Filament\Forms\Form, action namespaces, ->schema() versus ->fields(). If you're porting, budget more time for mechanical Filament API translation than for understanding the content model.
The v0.3.7 auth split is a latent class-not-found. Any lingering reference to the old auth resources or pages will throw on boot. Grep for trov namespaced classes referenced from config, panel providers, and resources, and confirm every one still exists. Then decide explicitly whether you pull in awcodes/filament-sentry (also archived-era) or write your own panel access control — the latter is usually less work.
Publish tags and --force will bite you. Running vendor:publish with --force after you've customized a published migration or config silently overwrites your edits. Publish once, commit immediately, and note the exact command in your decision log so the next developer doesn't re-run it.
Config caching masks stale reads. If a config key change doesn't take effect, run php artisan config:clear. In production, remember any published config is baked into the cache at deploy — a config:cache without a redeploy won't pick up edits.
Migrations belong to you now. Don't plan on package migrations running as part of the package's lifecycle. Copy them into database/migrations/, rename if needed to avoid collisions, and treat them as first-party schema with your own rollback story.
Testing Filament resources is Livewire testing. Use livewire() helpers against the resource pages, and Filament::setCurrentPanel() in setUp (or the actingAs + panel resolution your Filament version provides) so the component resolves correctly. Assert the policy gates separately — a resource that renders for everyone is the quiet security bug.
Record the exact commit in your decision log. Pin to a SHA, vendor or fork it, and add an entry to your dependency register flagged "archived / forked." Then put a line on every Laravel and Filament upgrade checklist: patch our Trov-derived code. That recurring cost is the honest price of this pattern, and it's the thing that gets forgotten between majors.
Debugging shortcuts that save time: php artisan filament:about (or about plus the panel route list) for panel registration state; dive/laravel-debugbar for query count on content pages (a naive slug lookup in a Blade partial is the usual N+1 culprit); php artisan tinker against a content model to confirm casts — JSON field casts are where "the data looks wrong in the admin" almost always originates.
How can I help you explore Laravel packages today?