yajra/cms-core
yajra/cms-core is a lightweight CMS foundation for Laravel from Yajra, offering reusable contracts, repositories, and helpers for pages, menus, settings, and content management. It gives developers clean scaffolding to build custom CMS features and admin panels quickly.
Architecture fit. yajra/cms-core is not a CMS — it is a set of seams. It ships contracts, repositories, base Eloquent models, reusable traits, and an auto-discovered service provider, and explicitly expects us to supply storage decisions, routing, and the admin UI. The package renders nothing on its own; the actual CMS behaviour lives in sibling packages (cms-page, cms-menu, cms-media). That is the correct frame for the decision: this is a build-vs-buy call for the boring core of a back office, not an adoption of a product. It fits a Laravel modular monolith with package-driven content types. It fits poorly if the roadmap requires headless delivery, multi-tenancy, or a non-Laravel service boundary.
Integration feasibility. Mechanically easy: composer require, confirm extra.laravel.providers, publish config, publish migrations, run migrate. The real integration surface is contract binding — one dedicated provider reading a config map of contract → implementation gives us a single file listing every coupling in the application. Effort is roughly one engineer-day for the source audit and schema diff, three to five days to drive one bounded content type end-to-end through a repository contract, then per-module increments. The cost multiplier is that there is no external documentation: the source is the documentation, and every convention must be reverse-engineered and written down or it leaves with the engineer who found it.
Technical risk. Ranked:
| Risk | Severity | Why |
|---|---|---|
| Schema ownership is one-way | High | Publishing migrations is irreversible once the app writes to those tables; upstream edits silently diverge |
| Zero dependents, no community | High | No one to ask, no field-proven upgrade path, no compatibility signal |
| Single-maintainer lineage | Medium | Reputable author, but no guarantee of fixes for a Laravel major break |
| Undocumented global scopes | Medium | Base models commonly register published/tenant/soft-delete scopes that leak into unrelated queries |
| Auto-discovery absence | Low | Silent "interface not found" rather than a helpful error — costs 30 seconds to check, an afternoon to debug |
| Unoptimized default reads | Low | Default repositories likely ship without eager loading; N+1s appear on the first realistic admin listing |
Blast radius stays contained only if application code depends on our interfaces with a thin adapter to the package — the exit plan directory must be describable in one sentence.
Key questions (answer before any commit):
composer.json declare extra.laravel.providers, or must we register manually?src/ actually reads, or are classes hard-coded? This is the single most important thing to confirm before leaning on it.SoftDeletes are registered on the base models?Stack fit. Assumes Laravel, a modular monolith, and that we bring routing and the admin UI. Fits internal tooling, editorial workflow, and marketing/blog backends. Does not fit a customer-facing multi-year surface where we would be betting the product on a 19-star package.
Migration path and gates. Each phase has a go/no-go:
composer.json → src/Contracts → src/Models → src/Traits, publish config, dump the migrated schema. Gate: schema overlap ≥70%, and the model map is genuinely read by source.migrate. Gate: migration files carry Schema::hasTable() guards and are treated as our own thereafter.new-ing a package model in a controller, stop and reconsider.loadViewsFrom, loadMigrationsFrom, routes, menus), so replacing cms-core later is a per-module change, not a rewrite.cms-page/cms-menu/cms-media only after the core seam is proven. They are separate adoption decisions with separate risk.Compatibility. Pin exactly, no caret ("1.x.y"). Stand up a private VCS mirror with CI running our integration tests, so a Laravel break is patched by a composer update against our own repo rather than a fire drill. Note that config:cache freezes the model/repository map — the deploy script needs config:clear plus a re-cache. Published migrations are recorded by name, so upstream changes will not re-run; never edit a published migration after it has run in a shared environment. Add one test per published migration asserting the table shape survives migrate:rollback.
Maintenance. We own the schema from day one; the package becomes a pinned dependency we occasionally patch. Budget roughly one engineer-day per month for mirror upkeep and upgrade watching, and substantially more during a Laravel major. The leverage play is asymmetric: traits are the highest-value, lowest-risk export — slug generation, publish windows, and meta columns are exactly the code nobody wants to rewrite, and we can use them on our own models without inheriting the schema.
Support. There is no support model other than us. No external docs, no dependents to consult, no community. The author's reputation in the Laravel ecosystem is a genuine signal, but cms-core specifically shows 19 stars and zero dependents — low adoption, which means a compatibility break may stay broken upstream indefinitely. Plan for self-sufficiency, not vendor responsiveness.
Scaling. No new runtime services; this is PHP and schema. The two predictable performance cliffs are global-scope leakage and N+1 admin listings. Put ->with() in the repository so every caller benefits, and use DB::listen or Telescope in development. Treat settings as an opaque key/value store and wrap it in a Cache::remember decorator if reads land on a hot path — do not assume the package caches.
Failure modes and mitigations.
| Failure | Symptom | Mitigation |
|---|---|---|
| Provider order / cached config | Container resolution errors, only on fresh deploy | Dedicated provider; config:clear in deploy |
| Published migration drift | Silent schema divergence after upgrade | hasTable guards, new migrations only, rollback test |
| Global scope leakage | Reports quietly return half the rows | Grep addGlobalScope/SoftDeletes; assert in tests |
| Slug collisions | 500s on concurrent publish; broken inbound links on rename | Unique index on (slug, deleted_at); lock slug after first publish |
| N+1 default reads | Admin slows sharply with content volume | Eager-load in the repository |
| Package abandonment | Frozen at an old Laravel | Private mirror + one-directory exit plan |
Ramp-up. A new engineer needs five things: contract map, model map, scope list, migration list, and exit plan. Keep them in docs/cms-core-notes.md and treat that file as a deliverable, not an afterthought — it is worth more than the package. With it, productivity in two to three days; without it, one to two weeks of reading vendor source. Make the initial source audit a paired session and record findings immediately.
TPM recommendation. The opportunity score (0.57) and overall score (0.475) match the qualitative read: worth a one-week spike, not a strategic commitment. Proceed if — and only if — phase 1 clears the schema-overlap gate and the config map proves real. If it does, adopt the traits and contracts broadly, wrap every model, bind everything through one config key, and treat the exit plan as an acceptance criterion rather than a contingency. If it does not, the correct outcome is still a win: port the decomposition patterns into our own codebase for a one-engineer-day cost and keep the roadmap off a 19-star dependency.
How can I help you explore Laravel packages today?