binarytorch/blogged
Blogged is a Laravel package for adding a blog to your application. It provides models, migrations, routes, and an admin interface for managing posts, categories, tags, and comments, so you can organize and publish content without building core blog functionality from scratch.
Blogged is a batteries-included blog engine for Laravel: it ships migrations, Eloquent models (Article, Category, Tag), a Markdown editor UI, image upload handling, slug generation, publish/draft state, and a front-end blog controller with Blade views. The value is that you get an admin CRUD + authoring experience in about ten minutes instead of building it.
Compatibility, up front: v1.4.0 raised the ceiling to Laravel 7. That's the whole release — no new features, no API changes. So the package covers Laravel 5.5 through 7 (roughly PHP 7.1.3–7.4). If you're on Laravel 8+, nothing here helps you; see Gotchas.
Install and scaffold
composer require binarytorch/blogged
php artisan vendor:publish --provider="BinaryTorch\Blogged\BloggedServiceProvider"
php artisan migrate
php artisan storage:link # only if you'll use the editor's image upload
The service provider is auto-discovered on Laravel 5.5+. Publishing drops a config/blogged.php, the package migrations, and (optionally) the views into resources/views/vendor/blogged.
If composer resolution stalls, the constraint is the thing to check first: composer why-not binarytorch/blogged ^1.4 will tell you whether it's the illuminate/* pins or the Markdown parser dependency holding you back.
Read these three things first, in order
config/blogged.php — this is where middleware, route prefix/domain, per_page, the editor options, and the uploads disk live. Everything you'll want to change on day one is here.vendor/binarytorch/blogged/src/Models/Article.php — the fields, casts, scopes, and relations you'll be querying from your own code.vendor/binarytorch/blogged/src/Http/Controllers — the admin and front-end controllers, so you know which routes exist before you accidentally duplicate them.First use case
Log in (the admin routes are behind web + auth by default — see middleware in the config, and confirm your config/auth.php user provider matches your User model), visit the configured admin prefix (typically /blogged), create a Category, then an Article: title, Markdown body, tags, category, save as draft, hit Preview, then Publish. The front-end route (typically /blog) should now list it. That round-trip validates migrations, auth middleware, the editor assets, and the front-end views in one pass.
If the editor loads but images 404, it's almost always a missing storage:link or a disk/visibility mismatch — fix that before touching anything else.
Decide early: built-in routes or your own front end. The package's front-end routes are fine for a side project, but most real apps disable them ('routes' => false-style flag or by not registering them) and use the models inside their own controllers, layouts, and SEO stack. The admin side is where the package actually earns its keep; the public side is usually five lines of controller you'd rather own.
Query and render pattern (the one you'll write most often):
// app/Http/Controllers/BlogController.php
public function index()
{
$articles = Article::query()
->published() // check the exact scope name in the model
->with(['category', 'tags', 'author'])
->latest('published_at')
->paginate(15);
return view('blog.index', compact('articles'));
}
public function show(Article $article) // slug route-model binding
{
abort_unless($article->isPublished(), 404);
return view('blog.show', compact('article'));
}
Render Markdown once, at write time — not per request. The biggest day-to-day performance win with any Markdown blog is storing the rendered HTML alongside the source. Add a content_html column (or a cache keyed by updated_at) and hydrate it in a model observer/mutator:
static::saving(function (Article $article) {
$article->content_html = Str::markdown($article->content); // Laravel 5.5/6/7: use the package's renderer
});
Then your Blade view is just {!! $article->content_html !!}. Note that Str::markdown only exists from Laravel 9 — on the versions this package targets you'll call the parser directly (Parsedown/CommonMark) or reuse whatever the package's editor already wires up.
Wire the package into your app's navigation and auth. The user_id on articles gives you an author box and an "only the author or an admin can edit" policy. Add a Gate/Policy in front of the admin routes rather than relying on the default auth middleware alone — auth lets any logged-in user write, which is rarely what you want.
Extend the editor instead of replacing it. The editor config in config/blogged.php accepts toolbar options. Add what your authors actually need (heading levels, code fences, tables) before you consider swapping in a different editor — swapping means forking the package's JS and views, which is a maintenance tax.
Override the published views surgically. Publish views, then edit resources/views/vendor/blogged/layouts/* to extend your app's layout (@extends('layouts.app')) so the admin blends in. Don't rewrite the article form views unless you have to — they carry the upload and slug logic.
Programmatic content. For imports or seeded demo content, create articles through the model directly, setting title, content, a unique slug, category_id, user_id, and published_at. Write your own factory — the package doesn't ship one — and use it in RefreshDatabase tests so you can assert on the published() scope and slug binding without hitting HTTP routes.
Feeds and sitemaps fall out naturally: one query over published() feeding an RSS Blade view and a sitemap.xml route. Don't install a separate blog package for that.
Pin the constraint, not the package. Since the only change in v1.4.0 is a widened illuminate/* bound, treat the version as a compatibility marker, not a feature line. In a long-lived Laravel 7 app, composer require "binarytorch/blogged:^1.4" documents intent better than *, and it stops a future (unlikely) 2.x from sliding in.
composer why-not before assuming it installs, and if you're on a modern app, budget for a fork with Str::slug, Arr::except, Str::markdown/CommonMark, and updated migration/return types — or treat this package as a reference implementation and copy the ~15 files you need.str_slug, array_except, str_plural and friends were pulled out of the framework in 6.0 and moved to laravel/helpers. If the package calls them and laravel/helpers isn't in your tree, you get Call to undefined function at runtime — not install time. Either require that package explicitly or run grep -rn "str_slug\|array_except\|str_plural" vendor/binarytorch/blogged/src once and know what you're dealing with.route:cache may fail. Laravel 7 (like 5.5–7 generally) refuses to cache closure-based routes. If php artisan route:cache throws, it's almost certainly a closure route registered by the package's provider — move it to a controller in a local override, or skip route caching in that app.vendor:publish overwrites. Re-publishing after an "upgrade" will clobber your edited config and views. Publish once, commit the output, and never re-run blindly. Use --force only after a diff. This matters more than usual here: the recommended upgrade path for v1.4.0 is literally just bumping the constraint, so there's no reason to re-publish at all.php artisan route:list | grep -i blogged is your first debugging step for any "route not found" report.login route, you'll bounce between redirects. Set middleware to include your app's real auth stack, and confirm the guard is web.{!! !!} untrusted content.published() only checks a status flag, "publish at 9am tomorrow" doesn't work out of the box. Either add where('published_at', '<=', now()) to your own query or schedule an artisan command that flips drafts to published.storage:link; (2) writing to local instead of public visibility on the configured disk; (3) orphaned files when an article is deleted. Add a cleanup command for the third, and set upload_max_filesize/post_max_size for the first two if large images silently fail.models map, don't try to substitute your own Article class — extend the package model and change the config/route bindings, or fork. Verify before designing around it.category, tags, and the author relation yourself, or a 15-post page becomes 45 queries.config('blogged') in tinker to confirm the published config actually loaded → route:list for the prefix → check storage/logs/laravel.log for view-not-found errors (a strong sign views weren't published) → inspect the articles table for published_at/status values that don't match your published() expectation. After the v1.4.0 bump, also confirm php artisan --version is 7.x and that the lock file actually resolved the new constraint — a stale composer.lock is the usual reason "we upgraded" changes nothing.How can I help you explore Laravel packages today?