A modern, multilingual, SEO-ready blog/posts engine for Laravel with translatable content, a publishing lifecycle, categories, and tags.
roundly-consulting/posts-for-laravel is a Laravel package for a modern, multilingual, seo-ready blog/posts engine for laravel with translatable content, a publishing lifecycle, categories, and tags..
It currently has 5 GitHub stars and 5 downloads on Packagist (latest version 1.0.1).
Install it with composer require roundly-consulting/posts-for-laravel.
Discover more Laravel packages by roundly-consulting
or browse all Laravel packages to compare alternatives.
Last updated
A multilingual, SEO-ready blog engine for Laravel: translatable posts with a real publishing lifecycle (draft, scheduled, published, archived), nested categories and tags, per-locale slugs, SEO meta and schema.org structured data, and a polymorphic author of any key type. Featured images, likes and reports come built in through our own media, likes and reports packages.
Requires PHP 8.4 and Laravel 12 or 13.
composer require roundly-consulting/posts-for-laravel
php artisan vendor:publish --tag="posts-migrations"
php artisan vendor:publish --tag="media-migrations"
php artisan vendor:publish --tag="likes-migrations"
php artisan vendor:publish --tag="reports-migrations"
php artisan vendor:publish --tag="approvals-migrations"
php artisan migrate
If your author models are UUID/ULID-keyed, set POSTS_KEY_TYPE (and POSTS_PRIMARY_KEY_TYPE for
the posts' own ids) before migrating. Scheduled posts go live through
Schedule::command('posts:publish-scheduled')->everyMinute();.
Write a post in two languages and publish it (or ->save() a draft, ->schedule($at) it):
use RoundlyConsulting\Posts\DataTransferObjects\SeoData;
use RoundlyConsulting\Posts\Facades\Posts;
$post = Posts::draft()
->title('en', 'Hello world')->title('sk', 'Ahoj svet')
->perex('en', 'A short intro')
->content('en', '<p>…</p>')
->by($user) // any author model, bigint/uuid/ulid keyed
->tags(['laravel', 'php']) // found or created by name
->seo(new SeoData(canonical: 'https://example.test/hello-world'))
->publish();
$post->getTranslation('title', 'sk'); // 'Ahoj svet'
Read it back — slugs are generated per locale, and {post} route parameters bind by them:
use RoundlyConsulting\Posts\Models\Post;
Posts::findBySlug('hello-world'); // current locale → fallback → any locale
Posts::published()->latest('published_at')->paginate();
Posts::archive($post); // or unpublish() back to draft
Route::get('/posts/{post}', fn (Post $post) => view('posts.show', compact('post')))->name('posts.show');
And render it with its SEO tags and JSON-LD:
{!! $post->renderMetaTags() !!} {{-- <title>, description, canonical, og:*, twitter:*, robots --}}
{!! $post->renderJsonLd() !!} {{-- schema.org BlogPosting --}}
<h1>{{ $post->title }}</h1> {{-- current locale, with fallback --}}
{!! $post->renderContent() !!} {{-- raw HTML: trusted or sanitized content only --}}
The full documentation — configuration, every feature and its API, and testing — lives on our website: roundly-consulting.com/open-source/docs/posts-for-laravel
Release notes are in CHANGELOG.md. To contribute, see the contributing guide.
This package is free and open source, built and maintained by Roundly Consulting. If it saves you time, please consider supporting our open-source work — a one-time donation, a monthly pledge on Patreon or a crypto donation helps fund maintenance, new features and new packages.
The MIT License (MIT). See LICENSE.md.