Open source
Posts for Laravel
composer require roundly-consulting/posts-for-laravelOverview
A multilingual, SEO-ready blog engine for Laravel. Posts carry per-locale titles, perexes, bodies and slugs, move through a real draft → scheduled → published → archived lifecycle driven by one Posts facade, and are organised in nested categories and tags. Every post renders its own meta tags and schema.org JSON-LD, owns a featured image, a gallery and inline media, and can be liked and reported out of the box. MIT-licensed, with no third-party runtime dependencies — native Laravel plus our own packages.
What you get
Multilingual content
Per-locale titles, perexes, bodies, SEO meta and slugs in JSON columns, with a deterministic fallback chain.
Lifecycle through one facade
Draft, schedule, publish and archive with Posts::…, the injected manager or an action — an event on every transition and a recording Posts::fake().
Categories & tags
Nested categories with ancestors and descendants, find-or-create tags, and filters by model or translated slug.
URLs that stay put
Unique per-locale slugs backed by database indexes, locale-aware route binding, optional 301 slug history and a publish lock.
SEO & JSON-LD
Meta, Open Graph and Twitter tags plus schema.org BlogPosting data, filled from the post itself when you leave fields empty.
Featured, gallery & inline media
Three media buckets per post and [media:UUID] tokens in the body, resolved in one query against the post’s own files.
Likes & moderation
Likes and trending feeds, report-a-post with multi-moderator sign-off, and auto-unpublish when a report is upheld.
Documentation
Installation
Install via Composer, publish the posts migrations and those of the packages it builds on, then optionally the config and views.
Configuration
Every config key and its default — key types, tables, author, locales, slugs, SEO, JSON-LD, media and moderation.
Key types
Two independent key settings — your author model’s key and the posts tables’ own ids — both fixed at migrate time.
Authors
Polymorphic authorship — add HasPosts to any bigint-, UUID- or ULID-keyed model and attach it to a post.
The Posts facade
One facade for every write and read — the draft() builder, lifecycle verbs, SEO, tags, publishDue() and slug lookups on the configured model.
DI, actions & DTOs
Inject PostsManager instead of calling the facade, or run one action class per write — with typed DTOs for controllers, jobs and imports.
Translations
Per-locale titles, perexes, bodies and SEO meta stored as JSON — write, read and the exact fallback order.
Publishing lifecycle
Draft, scheduled, published and archived — transition methods, query scopes, rules, scheduled publishing and status helpers.
Categories & tags
Nested, translatable categories and tags — assign, sync by name, filter posts by model or slug, and walk the category tree.
Slugs
Per-locale slugs generated from the title, unique per locale with database indexes, normalised manual input and backfill commands.
Routing & URL safety
Bind {post} by the current locale’s slug, keep old URLs alive with 301 slug history, lock published URLs and validate slug input.
SEO meta tags
Per-post meta, canonical, Open Graph and Twitter tags via SeoData, with config defaults and fallbacks from the post itself.
Structured data (JSON-LD)
schema.org BlogPosting or Article JSON-LD built from the post — headline, dates, author, publisher, image, tags and categories.
Featured image & gallery
Featured image, gallery and content buckets on every post via media-library, with og:image fallback and variant warming.
Inline media in content
Embed [media:UUID] tokens in the post body and render them as responsive images or links in one batched query.
Likes & reactions
Like, unlike and toggle posts, rank popular and trending feeds, and hydrate each viewer’s state in a single query.
Reports & moderation
Report-a-post with dedup and guest reports, a moderation queue, multi-moderator sign-off and auto-unpublish on upheld reports.
Events
PostPublished, PostScheduled, PostArchived and PostDrafted — one per lifecycle transition, each carrying the post id.
Custom post model
Extend Post with your own subclass — the package creates, queries and binds it everywhere, and Posts::query() returns it.
Testing
Posts::fake() records every write instead of running it, with an assertion per verb — plus factories and real-run examples.
Requirements
PHP 8.4+ and Laravel 12 or 13; our own dependency packages install automatically, with no third-party runtime dependencies.
Show your open-source love
This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.
More ways to support, including cryptoBy donating, you agree to our donation terms.
Want this built into your product?
We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.