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

Blog Laravel Package

litecms/blog

Blog module for LiteCMS that adds posts, categories, tags, and comments to your Laravel application. Includes an admin CRUD interface, slug-based routing, publishing controls, and Blade views, so you can launch a full-featured blog quickly without building content management from scratch.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and publish

composer require litecms/blog
php artisan vendor:publish --provider="Litecms\Blog\BlogServiceProvider"
php artisan migrate

The vendor:publish call is the first thing to inspect: open vendor/litecms/blog/src/BlogServiceProvider.php and read the publishes() array. Tag names are not standardised across LiteCMS packages, so prefer --provider= over guessing --tag=blog-config. You typically get a config file (config/blog.php), migrations, and Blade views.

Where to look first, in order

  1. src/BlogServiceProvider.php — route registration, view namespace, publishable assets, any admin-menu hooks.
  2. routes/web.php (or routes/admin.php) — route names and URI prefixes. Run php artisan route:list | grep blog to see what actually got registered.
  3. src/Models/Post.php — fillable fields, scopes (look for scopePublished), relationships to Category, Tag, User.
  4. database/migrations/ — column names matter more than anything else here; everything downstream (queries, views, SEO fields) depends on them.
  5. resources/views/ — the view namespace (e.g. blog::posts.index) determines how you override templates.

First use case: render a post index

Wire the package's own routes first to confirm it works end-to-end, then replace the frontend with your own controller:

// routes/web.php
Route::get('/blog', [\App\Http\Controllers\BlogController::class, 'index'])->name('blog.index');

// app/Http/Controllers/BlogController.php
public function index()
{
    $posts = \Litecms\Blog\Models\Post::query()
        ->published()
        ->with(['author', 'category'])
        ->latest('published_at')
        ->paginate(12);

    return view('blog.index', compact('posts'));
}

Create your first post through the LiteCMS admin panel (the package almost certainly registers admin CRUD). If no admin UI appears, the package is frontend-only and you own the write path — check the README before assuming a bug.

Implementation Patterns

Model extension over model patching. Never edit vendor/. If you need extra behaviour, subclass and swap the binding:

// app/Models/Post.php
class Post extends \Litecms\Blog\Models\Post
{
    public function getRouteKeyName(): string { return 'slug'; }

    public function scopeFeatured($q) { return $q->where('is_featured', true); }
}

// AppServiceProvider::register()
$this->app->bind(\Litecms\Blog\Models\Post::class, \App\Models\Post::class);

Verify the package resolves models from the container (it usually does via config('blog.models.post')); if it hardcodes the class, config override is the supported lever.

View overriding. Publish views, then edit resources/views/vendor/blog/.... Keep the override surface small — every overridden file is a merge conflict on upgrade. Prefer wrapping: your blog.index includes <x-blog.post-card :post="$post" /> built from scratch.

Read path vs write path. Split them deliberately. Public read routes are yours (full control over SEO, caching, pagination). Admin write routes stay with the package. This keeps upgrades cheap.

Query hygiene. Blog indexes are the classic N+1 trap — author, category, tags, and cover image all lazy-load per card. Always with() explicitly, and add a ->withCount('comments') if you show counts.

Caching with invalidation. Cache the published list and bust it from a model observer rather than TTL guessing:

Post::observe(new class {
    public function saved(Post $post)  { Cache::forget('blog.index'); }
    public function deleted(Post $post) { Cache::forget('blog.index'); }
});

Scheduled publishing. If there's a published_at column, drive visibility from it (where('published_at', '<=', now())) rather than a boolean flag. That gives you scheduled posts for free and one source of truth.

Headless / API usage. If the frontend is separate, ignore the package views entirely and expose your own PostResource. Check whether the model uses HasMedia/mediable; if so, expose cover_url as a computed accessor so the API shape stays stable if the storage driver changes.

Testing. Add your own factories regardless — packages of this size rarely ship them. RefreshDatabase plus a Post::factory()->published()->create() state is worth the ten minutes.

Gotchas and Tips

  • Zero adoption means you are the QA team. 0 stars / 0 dependents means no Stack Overflow safety net. Budget time to read src/ fully before committing. Pin the version exactly ("litecms/blog": "1.2.*") — a patch release can silently rename a column.
  • Slug uniqueness across soft deletes. A unique index on slug plus SoftDeletes means a deleted post's slug stays reserved. Decide early: hard-delete, or suffix slugs on collision (my-post-2).
  • Timezone drift on published_at. Store UTC, display in the user's zone. A post appearing "tomorrow" on the frontend but "today" in admin is almost always an uncast datetime column. Add 'published_at' => 'datetime' to $casts if missing.
  • Route collisions. LiteCMS often registers a catch-all page route. If /blog/{slug} 404s while the admin works, check route order with php artisan route:list --sort=...; your routes must load before the catch-all.
  • Migration ordering. The blog migrations may assume a users table and specific foreign-key types. On a fresh DB with UUID users and bigint posts, migrate fails. Check the key types before running.
  • XSS in the body field. WYSIWYG post bodies are usually rendered with {!! !!}. Sanitise on save (HTMLPurifier or a sanitize mutator) rather than trusting editors — a compromised admin account otherwise becomes stored XSS.
  • Storage link for uploads. php artisan storage:link is a silent prerequisite for cover images; broken image icons after deploy are nearly always this.
  • Config cache. After changing config/blog.php, run php artisan config:clear. In production, a stale cached config is the most common "the package ignores my setting" cause.
  • Debugging: php artisan route:list, php artisan vendor:publish --provider=... output, and Telescope's query tab to confirm eager loading actually fired. If a scope "doesn't exist", you're likely on a different model class than the one the package resolves.
  • Exit strategy. Given the maintenance risk, keep your integration thin: your own controllers, your own views, package used only for schema + admin CRUD. If the package dies, you delete a composer line and keep a working blog.
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.
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package