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

Laravel Blog Laravel Package

bjuppa/laravel-blog

A lightweight blog package for Laravel that adds posts, categories, and tags to your application. Use Eloquent models and Blade views to publish, organize, and display articles quickly without building blog basics from scratch.

View on GitHub
Deep Wiki
Context7

Getting Started

bjuppa/laravel-blog is a batteries-included Eloquent content package: it ships the models, migrations, and config for blog entries plus categories/tags (and comments, if you use them). You don't scaffold anything — you install it and start writing rows.

composer require bjuppa/laravel-blog   # v1.11.2 adds Laravel 13 support
php artisan vendor:publish --provider="Bjuppa\LaravelBlog\BlogServiceProvider"
php artisan migrate

Where to look first, in order:

  1. config/blog.php — the published config is the real API surface (layout/view, route prefix, middleware, model bindings). Read it before writing any code.
  2. Bjuppa\LaravelBlog\Models\BlogEntry — the class you'll actually touch daily. Note its $fillable, $casts, and scopes.
  3. The package service provider — to see which routes/views it registers and which publish tags exist.
  4. Your own database/migrations — the published migrations are yours now; treat them as app schema.

First use case — get one entry rendering end to end before building any admin UI:

use Bjuppa\LaravelBlog\Models\BlogEntry;

BlogEntry::create([
    'title'        => 'Hello world',
    'slug'         => 'hello-world',
    'content'      => 'First post.',
    'published_at' => now(),
]);

Then hit the route the package registered (per your route_prefix) or your own controller. If that page renders, the rest is ordinary Laravel work.

Implementation Patterns

Separate "public" reads from "editor" reads. Public listings should always go through the published scope so future-dated posts stay hidden; admin screens deliberately don't.

// public
BlogEntry::published()->with(['category', 'tags'])->latest('published_at')->paginate(10);

// admin
BlogEntry::withTrashed()->latest()->paginate(50);

Wrap the package in your own controller + API Resource. Don't let views or JSON responses bind directly to vendor models — you'll want to change the shape later without fighting the package. A thin PostController that queries BlogEntry and returns a PostResource is the cheapest insulation you can buy.

Route resolution by slug, not id. Blog URLs are /blog/{slug}. Confirm the model's route key is the slug and that slugs are unique at the database level (unique index, not just a validation rule). If you override getRouteKeyName() in a subclass, you own that contract.

Authoring workflow: your admin, their storage. There is no admin panel. The pragmatic pattern is a small Filament/Nova/custom CRUD over BlogEntry, or seeders + a Markdown importer for a content team that lives in git. Either way, set published_at explicitly — don't rely on created_at.

Scheduling lives in published_at. Draft = null, scheduled = future, live = past. That single column drives visibility, ordering, and sitemap output, so build one query scope/helper around it and reuse it everywhere instead of scattering where('published_at', '<=', now()).

Eager load taxonomies. category and tags are relations; an index page without with() is a textbook N+1. Same for comments if you render counts.

Testing. There are no factories shipped. Write your own BlogEntryFactory in database/factories and use RefreshDatabase; state helpers like ->published() / ->draft() pay for themselves immediately.

Upgrading. v1.11.2 is a compatibility release (Laravel 13) plus added test coverage for BlogEntry casts — no migration or API changes. Bump with composer update bjuppa/laravel-blog -W and run your suite; the real work is the Laravel 13 upgrade itself (PHP 8.3+).

Gotchas and Tips

  • $fillable will bite you. Package models often guard columns you want to mass-assign (published_at, slug). Either extend the model and widen $fillable, or assign attributes individually. Silent dropping of published_at looks exactly like "scheduling doesn't work."
  • Don't strip $casts in a subclass. The v1.11.2 release explicitly added tests around BlogEntry casts — published_at behaving as a Carbon instance is load-bearing for the published scope. If you subclass and redefine $casts, merge rather than replace.
  • Future-dated posts vanish from public pages and that's correct. When a client says "the post disappeared," check published_at before you check anything else. Inversely, don't assume a fresh create() shows up publicly.
  • Timezone drift at the boundary. Store UTC, compare against now() in the app timezone, and add one test that publishes an entry exactly at the boundary. Off-by-hours scheduling bugs are the most common support ticket for this class of package.
  • Caching scheduled content. If you cache the index page or an RSS feed, key the cache on the next published_at too, or scheduled posts won't appear until something else invalidates it.
  • Catch-all route shadowing. A blog route with a permissive slug constraint can swallow /blog/tagged/foo or your own sub-routes. Register blog routes last, and constrain slugs ([A-Za-z0-9-]+).
  • Config and view caching. After editing config/blog.php run php artisan config:clear; after publishing/overriding views run view:clear. Most "my changes do nothing" reports are one of these two.
  • Published migrations are snapshots. Editing the vendor copy changes nothing. To alter blog tables, write a new migration in your app — never edit the published one in place, and never re-publish over a database that already ran them.
  • Debugging visibility: log the actual SQL (->toSql() / DB::listen) for an empty listing. Nine times out of ten the query has a published_at clause you forgot, or the row's published_at is null.
  • Extension points that are safe: subclass the model and point the config at your subclass, override published views, hook model events (saving to auto-generate slugs), and add global scopes for multi-tenant filtering. Avoid editing vendor files or patching the service provider.
  • Pin and watch. A package that just added Laravel 13 support should be constrained (^1.11) rather than left floating, so the next major doesn't surprise a deploy.
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.
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log
gregsphotography/site-builder-components
io-digital/clickatell
snowsoft/laravel-admin
oxygen/auth
relicsoftware/barakah-payment-gateway
tobya/work-with-composer
equipc/laravel-email-validator
equipc/laravel-email-database-log
adedsec/nova-translated-labels
wazza/laravel-db-encryption
hokny/laravel-generator
vonsogt/backup
jengo/queues
jeremykenedy/laravel-observability
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums