secondnetwork/kompass
Kompass is a Laravel package that adds an in-app REST client for testing your endpoints. Send requests, inspect responses, and debug APIs from a clean built-in UI, with a lightweight setup that keeps API work inside your Laravel app.
Kompass is a TALL-stack CMS drop-in for Laravel 11/12+: Livewire admin, Blade-rendered public pages, Tailwind 4 + DaisyUI/Preline for UI. Treat it as an application scaffold you own rather than a black-box SaaS — you'll be editing published stubs and overriding views almost immediately.
Install
composer require secondnetwork/kompass
php artisan vendor:publish --provider="Secondnetwork\Kompass\KompassServiceProvider"
php artisan migrate
npm install && npm run build # or bun install
Publish tags vary; run php artisan vendor:publish and pick the Kompass provider interactively the first time, then note which tag groups exist (config, migrations, stubs, views, assets). Pin the exact version in composer.json — the package is at ~10 stars, so treat minor bumps as potentially breaking.
Where to look first, in this order
src/KompassServiceProvider.php — component registration, morph maps, route/helper bootstrapping. This file is the source of truth; if a Livewire component 404s or a polymorphic relation returns null, the answer is here.routes/ in the package (usually registered from the provider) — how the public catch-all resolves a slug to a Page.src/Models/Page.php, Post.php, Datafield.php — read the boot() methods carefully; they flush cache on create/update/delete.src/Livewire/ — the admin surface (tables, editors).resources/views/ — Blade templates, all referenced as kompass::....src/database/migrations/ and stubs/ — schema and the default app files dropped into your project.First use case
Create a marketing page end-to-end: log into the admin route (check the provider for the prefix, typically /admin or /cp), create a Page with a slug, attach Blocks, then hit the public URL. That single loop exercises routing, morph relations, the Livewire editor, and Blade rendering — if any of those four is broken you'll find out in one pass.
Then immediately do the boring things: set a real APP_URL (the Vite config assumes port 5088 in dev), add the package's public disk/asset path to your deploy script, and commit the published config so config drift is visible in diffs.
Page + Block composition. The core pattern is a Page (routable, slugged) composed of ordered Block records via a polymorphic relation. Prefer creating block types as small Blade partials keyed by a type or component column rather than one giant switch — the admin table will scale better and you can register new block types without touching the renderer.
Polymorphic content across Page/Post/Datafield. All three share content fields through morph maps defined in the service provider. The practical pattern:
// In a migration or seeder — always use the morph alias, never the FQCN
$page->datafields()->create(['key' => 'seo_title', 'value' => '...']);
Keep the alias strings ('page', 'post') in a config file and reference them everywhere. This is the single most important convention to enforce in code review: raw class names in *_type columns will silently stop resolving the moment you add a custom morph map.
Custom content types. For anything beyond pages and posts (events, products, docs), don't fork the package. Subclass the base page model, register your morph alias in your own AppServiceProvider, and add a Livewire resource that reuses the package's table/editor traits. This keeps upgrades cheap.
Rendering and overrides. Public output goes through kompass:: views. Override per-view with php artisan vendor:publish --tag=kompass-views into resources/views/vendor/kompass/ — publish the specific files you need, not the whole tree, so you can tell at a glance what you've customized. After any view override that touches markup, re-run npm run build; the package ships compiled assets to public/assets/build and Tailwind 4's oxide scanner only picks up classes present at build time. Dynamic classes built in PHP ("bg-{$color}-500") will not be generated — safelist them or use a lookup map.
Admin extension. Register additional Livewire components in your own provider and mount them as admin routes behind the package's auth middleware/gates. The provider exposes gate registration (registerGates) — extend it rather than duplicating auth checks. Use Livewire 4 attributes (#[Locked] on IDs, #[Layout] for the admin shell) so your components inherit the same chrome as the built-ins.
Workflow that actually holds up day-to-day
DatabaseSeeder using updateOrCreate keyed on slug — safe to re-run across environments.kompass:sync-style artisan command early if you need to push content between local/staging; the package doesn't ship one and hand-editing prod content is how environments diverge.vendor/bin/rector process --dry-run for style, plus your own feature tests (see gotchas) — rector config is already in the repo, mirror it.There is no package test suite. The AGENTS.md states this outright. That means every upgrade is unverified by the maintainers against your usage. Mitigate with a thin feature suite of your own covering the three things that break: public route → 200, admin editor saves a page, block render output. Twenty minutes of tests here is worth more than any changelog.
Cache flush in model boot methods is a footgun. Page::boot() clears cache on create/update/delete. During bulk operations (Page::where(...)->update(...), updateOrCreate in a loop, queue imports) that's one cache flush per row. Wrap imports in a transaction and either disable the observer via Page::withoutEvents() or flush once at the end:
Page::withoutEvents(fn () => $seeder->run());
Cache::tags(['kompass'])->flush();
Also watch for recursion: if a cache-clearing listener touches a model that fires the same event, you'll get a stack overflow that looks like a random 500 on save.
Morph map migrations are one-way doors. Changing an alias in the provider orphans every existing row. If you must rename, add the old alias as a duplicate key in the morph map rather than rewriting *_type values — cheaper and reversible.
Slugs need a collision policy. The convention is Str::slug, but the package won't stop you from creating two pages with the same slug. Add a unique index on (slug, locale) in a follow-up migration and let the validator produce a clean error, or you'll get nondeterministic routing in production.
Livewire 4 + Tailwind 4 + DaisyUI/Preline is a moving target. Both major versions are new. Symptom/cause pairs you'll hit: styles missing after a Livewire navigation (asset not in the built bundle), dropdowns rendering but not opening (Alpine loaded twice — package plus your layout), and preline components initializing before Livewire morphs the DOM. Fix by initializing Alpine plugins once in a livewire:navigated listener, not on DOMContentLoaded.
Vite dev server is pinned to localhost:5088 with a rewritten APP_URL. If you run your app on :8000 or a Herd/Valet hostname, HMR assets will 404. Either align the app URL or override the dev server config; don't chase it as a CSS bug.
Migration collisions. The package ships its own pages/blocks/datafields migrations. If you already have a pages table, publish the migrations to database/migrations and rename before running, or the install fails halfway and leaves you with a partial schema. Always take a DB snapshot before the first install in an existing app.
Debugging order of operations when something renders wrong: php artisan optimize:clear (config/route/view cache hides provider changes) → php artisan route:list | grep -i kompass (did the catch-all register, and did it register after your own routes?) → check the morph map → check whether the view you're editing is your override or the vendor original (php artisan view:clear, then look at the compiled path).
Route ordering matters. The public catch-all is greedy. Any custom route (/sitemap.xml, /search, a custom post type) must be registered before the package's route file loads, otherwise it's swallowed. Declare them in routes/web.php above the provider's group, or guard with where() constraints on the slug pattern.
Extension points worth knowing: the service provider's component registration and gate hook (for custom admin tooling), resources/views/vendor/kompass/ (rendering), the stubs/ directory (scaffolding new installs — keep it in sync if you build a starter kit), and model boot() events (cache and derived fields). There is no documented event/plugin API beyond these, so prefer overriding over patching and keep a short UPGRADING.md in your fork noting every package file you've touched.
Vendor risk is real. Low star count, recent release date, and self-acknowledged absence of tests mean you should assume maintenance could stall. Keep your customizations in app/ and resources/views/vendor/ only, so a fork or an in-house replacement of the provider is a day's work rather than a rewrite.
How can I help you explore Laravel packages today?