Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Cms Core Laravel Package

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.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and establish ground truth in the first ten minutes.

composer require yajra/cms-core
composer show yajra/cms-core        # version, PHP/Laravel constraints, dependencies

There is no external documentation, no README worth the name, and zero dependents on Packagist. That means the source is the specification, and your first task is not to write code but to read four directories in this order:

  1. composer.json — check require constraints against your PHP/Laravel/database versions, and check whether extra.laravel.providers is declared. If it is, the service provider auto-registers. If it is not, nothing works until you add it manually to bootstrap/providers.php — and the failure mode is a silent "target [Contract] is not instantiable", not a helpful error.
  2. src/Contracts/ — this is the public surface. Every interface here is a seam the package expects you to bind. Note the exact FQCNs; you will be typing them constantly.
  3. src/Models/ — check $table, $fillable, $casts, and grep for addGlobalScope and SoftDeletes. Global scopes are the number-one source of "why did this query return half the rows" surprises.
  4. src/Traits/ — slugging, publish windows, meta columns. Even if you never depend on the package, these are the highest-value thing to borrow.

Publish selectively, then commit immediately.

php artisan vendor:publish --provider="Yajra\Cms\Core\...ServiceProvider" --list
php artisan vendor:publish --provider="..." --tag=config
php artisan vendor:publish --provider="..." --tag=migrations

Read the config file end to end before doing anything else — it is almost certainly where models, repositories, and table names are swappable. Then publish migrations and commit them before migrate runs. The moment those files land in your repo they are your migrations, your schema, and your upgrade risk.

Run the migration and read the schema.

php artisan migrate

Dump the resulting tables and read every column. You are answering two questions: does this package own tables your application will also want to touch, and is there a configurable table prefix so it does not collide with anything you already have?

First use case: one bounded content type through a contract, not a model.

Pick the smallest real thing — an Article, a Page — and drive it end to end:

// app/Providers/CmsServiceProvider.php
public function register(): void
{
    $this->app->bind(
        \Yajra\Cms\Core\Contracts\PageRepository::class,   // confirm exact name in src/Contracts
        \App\Cms\PageRepository::class
    );
}
final class PageController
{
    public function __construct(private readonly PageRepository $pages) {}

    public function index(): View
    {
        return view('pages.index', ['pages' => $this->pages->paginate(20)]);
    }
}

Controller → contract → your implementation → base model. That single request path is the entire integration in miniature. If it reads cleanly, the seams are real and you can scale the pattern. If you find yourself new-ing a package model inside a controller, stop — that is the coupling you will spend a quarter removing later.

What to look at next, in priority order: src/Contracts (your seams) → the config file (your swap points) → published migrations (your schema debt) → sibling packages (cms-page, cms-menu, cms-media) if you need ready-made admin views. Core alone renders nothing useful; it is a foundation, not a CMS.

Implementation Patterns

Never consume package models directly in application code. Every use Yajra\Cms\Core\Models\... in a controller is a migration cost you are choosing to pay. Subclass them:

namespace App\Cms;

class Page extends \Yajra\Cms\Core\Models\PageBaseModel
{
    protected $table = 'pages';

    // Re-state traits explicitly rather than inheriting them implicitly.
    // One file should show everything this model does.
    use \Yajra\Cms\Core\Traits\HasSlug;
    use \Yajra\Cms\Core\Traits\Publishable;

    public function scopePublished($query)
    {
        return $query->where('published_at', '<=', now());
    }
}

If the config exposes a model map ('models' => ['page' => ...]), swap it there so the package's own internals resolve your subclass. Verify in src/ that the package actually reads that config rather than hard-coding the class name — this is the single most important thing to confirm before you lean on it. If it hard-codes, you are extending for your own convenience only, and the package's internals will keep using its base model.

One provider, one config key, listing every coupling.

// config/cms.php
return [
    'repositories' => [
        \Yajra\Cms\Core\Contracts\PageRepository::class => \App\Cms\PageRepository::class,
        \Yajra\Cms\Core\Contracts\SettingsRepository::class => \App\Cms\SettingsRepository::class,
    ],
];
public function register(): void
{
    foreach (config('cms.repositories', []) as $contract => $impl) {
        $this->app->bind($contract, $impl);
    }
}

That file is your complete dependency manifest on this package. When you eventually replace it — and with 19 stars and a single maintainer, you should plan for that — the file is your deletion list.

Use the traits on your own models, not the package's. Traits are the best risk/reward export here. Slug generation, publish windows, meta columns: exactly the boring core nobody wants to rewrite. use them on your own models with your own schema and you get the behaviour without inheriting the migration ownership.

Repository for the read side of admin listings — with eager loading built in.

final class PageRepository implements \Yajra\Cms\Core\Contracts\PageRepository
{
    public function paginate(int $perPage = 20): LengthAwarePaginator
    {
        return $this->model->newQuery()
            ->with(['author', 'media'])      // never trust the default
            ->latest('updated_at')
            ->paginate($perPage);
    }
}

Default implementations in small packages are rarely optimised for admin listings. If the shipped repository omits with(), you will see N+1s on the first realistic index page. Overriding the repository is the intended escape hatch — use it on day one, not during a performance pass.

Compose content types as modules, one provider each.

// app/Modules/Blog/BlogServiceProvider.php
public function boot(): void
{
    $this->loadViewsFrom(__DIR__.'/resources/views', 'blog');
    $this->loadMigrationsFrom(__DIR__.'/database/migrations');
    // admin routes, menu registrations, content-type definitions
}

A modular monolith is the shape this package assumes. Keeping each content type in its own provider means replacing cms-core later is a per-module change rather than a rewrite.

Settings as an opaque key/value store, always with a default.

$settings->get('site.tagline', default: '');
$settings->set('site.tagline', $value);

Read src/ to confirm cache and flush semantics — do not assume it caches. If reads land on a hot path, wrap it in your own decorator with Cache::remember rather than hoping.

Test the boundaries and the schema, not the package internals.

public function test_published_scope_excludes_future_content(): void
{
    $this->freezeTime();
    Page::factory()->create(['published_at' => now()->addDay()]);
    Page::factory()->create(['published_at' => now()->subDay()]);

    $this->assertCount(1, Page::published()->get());
}

Add one test per published migration that asserts table shape survives migrate:rollback. Migration drift after an upstream change is the failure that hurts most, and it is cheap to catch early.

Gotchas and Tips

You are buying schema, not features. Publishing migrations is a one-way commitment. Before committing them, dump the resulting schema and diff it against what you would design yourself. If the overlap is below roughly 70%, take the traits and contracts as reference material and write your own tables — you will be maintaining them either way.

Published migrations cannot be safely un-published. Once migrate has run, Laravel records the migration by name; if upstream later edits that file, your database silently diverges from the package's assumption. Mitigation: never edit a published migration after it has run in any shared environment. Write a new migration instead. Add Schema::hasTable() guards where the package's migration might run against a partially-populated database.

Grep for global scopes before you trust any query. CMS cores routinely register published-state, tenant, or soft-delete scopes on base models and traits. A Page::all() in an unrelated report suddenly returns half the rows and nobody knows why. Search src/ for addGlobalScope and SoftDeletes. ->withoutGlobalScopes() in a test is a diagnostic tool, not a fix.

Provider order and config:cache. If you bind contracts in a provider that boots before cms-core registers its own, you get container-resolution errors that appear only on a fresh deploy. Publish config to config/cms.php, and remember config:cache freezes the model/repository map — changing it requires a re-cache. Put php artisan config:clear in the deploy script.

Confirm auto-discovery first. If extra.laravel.providers is missing from composer.json, nothing works until you register the provider manually, and the symptom is a silent "interface not found". Thirty seconds of checking saves an afternoon.

Pin exactly and mirror the fork. Use "yajra/cms-core": "1.x.y" with no caret. With effectively zero dependents, a break against a new Laravel release may stay broken upstream indefinitely. Set up a private VCS mirror with CI running your integration tests, so patching is composer update against your own repo rather than a fire drill during a framework upgrade.

Slug collisions are your problem. Trait-based slugging typically does not handle uniqueness across soft-deleted rows or concurrent writes. Add a unique index on (slug, deleted_at) or a saved observer that retries with a numeric suffix. Test the rename path explicitly — regenerating slugs on title change silently breaks every inbound link. Default to locking the slug after first publish.

Eager-load in the repository, not the controller. Put ->with() in the data-access layer so every caller benefits. Run Telescope or DB::listen in dev to catch the relations you missed. Any listing view that iterates items and touches a relation is an N+1 waiting to happen.

Do not let package Blade views own your admin UI. If you publish views, copy them into your own namespace immediately and treat the upstream copies as read-only reference. Editing vendor-published views in place is the classic upgrade trap — the next publish overwrites months of tweaks.

Write internal docs as you go. With no external documentation, every convention your team reverse-engineers and does not write down leaves with the engineer who found it. Keep a short docs/cms-core-notes.md: contract map, model map, global scope list, migration list, and the exit plan. That file will outlive the package.

Decide the exit plan before the first commit. Application code should depend on your own interfaces (App\Cms\Contracts\ContentRepository) with a thin adapter to cms-core behind it. When the replacement decision arrives — and given a single-maintainer lineage and 19 stars, it will — the diff should be confined to one directory. If you can describe that directory in one sentence, you have integrated it correctly.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle