anomaly/posts-module
A posts management module for the Anomaly/Streams platform, providing post types, fields, and CRUD workflows for creating, editing, and publishing blog or article content with Laravel-powered flexibility.
anomaly/posts-module is not a standalone blog package for a vanilla Laravel app. It is a module for the Streams Platform (the engine behind PyroCMS 3/4), so your starting point is an existing PyroCMS or Streams-powered project — not a fresh laravel new install.
Prerequisites
anomaly/streams-platform (v4.x of this module) or Laravel 5.7–8 with Streams 1.3 (v3.x of this module).composer update on this package without a ^ constraint is a recipe for a broken build.Install
composer require anomaly/posts-module:^4.0
php artisan module:install posts
The module ships as a Laravel package with a Streams service provider (PostsModuleServiceProvider), auto-discovery under package discovery. You usually don't need to add it to config/app.php on PyroCMS — the installer registers core modules for you. In a custom Streams project you'll need to register the module in config/streams.php (or via php artisan addon:install).
Where to look first
src/Post/PostRepository.php and PostModel.php — the entry point for querying.src/Post/PostPresenter.php — output formatting (URLs, summaries, etc.).src/Post/PostPlugin.php — the Twig-facing plugin used in themes.migrations/ — the Streams stream definition (fields, assignments). This is where you learn what fields exist out of the box (title, slug, summary, body, meta_title, meta_description, category, tags, enabled)./admin/posts (and /admin/posts/categories).First use case — create and render a post
/admin/posts/create, fill in title, summary, body; save. A posts stream entry is created and a slug is derived./admin/pages (Streams modules rely on the Pages module for routing) — or query entries directly in a controller/view:use Anomaly\PostsModule\Post\Contract\PostRepositoryInterface;
$posts = app(PostRepositoryInterface::class)
->where('enabled', true)
->orderBy('publish_at', 'DESC')
->paginate(10);
Or via the Streams API:
$entries = \Streams::entries('posts', 'posts')->where('enabled', true)->get();
Repository-first queries. Prefer the contract (PostRepositoryInterface) over the concrete class so you can bind a custom implementation later. The repository extends Streams' EntryRepository, so you get the full Eloquent/Streams query builder: where, orderBy, paginate, findBySlug, findByStrId, etc.
Twig plugin (theme layer). In PyroCMS themes, the module registers a posts plugin (PostPlugin) so you can do {{ posts().get() }} or {{ posts().categories() }} in Twig. Verify method names in src/Post/PostPlugin.php — the surface is small and undocumented.
Extending the post stream. Don't fork the module to add fields. Use the Streams admin UI (Content → Streams → posts → Assignments) to add text, integer, relationship, or custom field types. The PostModel reads through Streams' dynamic attributes, so $post->custom_field just works. For version-controlled fields, create a tiny add-on module with a migrations/ step that calls Streams::make('posts', 'posts')->addField(...).
Custom Post model. If you need domain methods, bind your own model class implementing PostInterface and extending EntryModel:
$this->app->bind(
\Anomaly\PostsModule\Post\Contract\PostInterface::class,
\App\Posts\CustomPostModel::class
);
View overrides. All admin views use the anomaly.posts_module::* namespace. Override either by publishing:
php artisan vendor:publish --tag=posts-module
or by creating files in resources/views/anomaly/posts_module/... — the module's ViewServiceProvider checks the app first.
Frontend rendering options. Three patterns dominate:
PostPlugin in a theme add-on to expose additional query methods without touching the vendor code.Categories and tags. A PostCategoryRepository is exposed alongside posts. Categories are a separate stream the module defines; use it in your UI rather than stuffing a string field into posts.
Coupling to Streams is total. You cannot install this in a plain Laravel app and expect Post::all() to behave like an Eloquent model. Everything flows through Streams::entries(), and without the platform service providers, migrations won't run. If you want a blog without Streams, look at spatie/laravel-tags + a hand-rolled model — this module is the wrong tool.
Stream migrations are painful to debug. The migrations/ files use Streams::make(...) which fails silently in odd ways if the posts namespace isn't registered yet. Run php artisan streams:compile if a field or assignment doesn't show up in the admin after a deploy.
Caching is aggressive. Streams caches entries, stream definitions, and field assignments. After changing fields or migrations, run:
php artisan cache:clear && php artisan view:clear && php artisan streams:compile
Otherwise you'll stare at "field not found" errors that are actually stale cache hits.
Slug uniqueness and locale. Slugs are unique per locale in a translatable stream. Creating posts programmatically without a slug field will auto-generate one, but you will hit collisions if you batch-import. Set slugs manually or catch QueryException during imports.
Route collisions with Pages module. If you assign the posts stream to a Page, make sure the page slug (blog, news, etc.) doesn't clash with a manually created route. The Pages module wins over custom routes — debug order with php artisan route:list.
The admin URL is /admin/posts, not /admin/post. Easy to miss; both work in some versions, but the router registers the plural.
Zero dependents means zero community answers. README is thin, issues are sparse. Budget time to read src/Post/PostRepository.php and migrations/ before you design anything. The MIT license is friendly for forking — the codebase is small enough that a private fork is often cleaner than trying to extend via bindings.
Don't edit vendor files. Because there are no community PRs landing, your local changes will vanish on the next composer install. Use the PostInterface binding, view overrides, or a small add-on module for anything non-trivial.
Version matrix matters. v3.x and v4.x differ in service provider registrations and admin UI conventions. Pin one and stick to it; cross-version upgrades on a 3-star package are a project, not a composer update.
How can I help you explore Laravel packages today?