Search by

kreetancraft / laravel-blog

Blog for Laravel — posts, categories, tags, authors, series and moderated comments, with SEO and a public read API. Livewire 4 + Flux UI.

Maintainers

Package info

github.com/Kreetancraft/laravel-blog

pkg:composer/kreetancraft/laravel-blog

Transparency log

Statistics

Installs: 10

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

0.6.0 2026-08-29 08:58 UTC

This package is auto-updated.

Last update: 2026-08-29 09:40:06 UTC


README

Posts, categories, tags, authors, series and moderated comments, with SEO metadata and a public read API. Livewire 4 + Flux UI.

Ships no layout, no CSS, no user model and no image handling — it renders into your application and works with whatever you already have.

Design decisions worth knowing before you install

It does not care which user model you have. Comments resolve their author through config('auth.providers.users.model'); every policy type-hints Authenticatable.

It names no permission of its own. Its screens ask the ordinary authorization question and its policies answer it. Until permissions exist anywhere in the app they are open, so it works on a bare install rather than failing closed.

It ships no image handling. Featured images, author avatars and series covers resolve through blog.image_resolver. With none configured they return null and the pickers hide themselves — the blog works, it just has no pictures.

Flux is a hard dependency. The views use <flux:*> throughout.

Installation

composer require kreetancraft/laravel-blog
php artisan migrate

Let Tailwind see this package

Required. Tailwind v4 generates only the classes it finds by scanning files, and it does not scan vendor/. In resources/css/app.css:

@source '../../vendor/kreetancraft/laravel-blog/resources/views';
@source '../../vendor/kreetancraft/laravel-seo/resources/views';

Skipping it fails confusingly rather than loudly — classes shared with your own views still work and only the ones unique to these packages go missing.

The editor

A TipTap editor ships with the package, bundled — no npm packages, no build step, nothing to publish. It is served from the package itself, so upgrading is composer update alone.

<x-blog::rich-text model="content" :label="__('Body')" :rows="20" />

What it does: bold, italic, underline, strikethrough, three heading levels, bullet, numbered and task lists, blockquote, code block, links, images, text alignment, colour and highlight, horizontal rule, clear formatting, undo/redo, source view and fullscreen — plus:

Tables Insert, resizable columns, add/remove rows and columns, header row, merge and split cells
Callouts Note, tip, warning and danger, as <div data-callout="…"> so a sanitiser leaves them alone
YouTube embeds Paste a URL; served cookie-free
Find and replace Across the whole document, with match case
Word and character count Live, under the editor
Slash menu Type / at the start of a block for a filtered command palette — headings, lists, table, image, video, callouts, divider. Arrow keys and Enter, or click
Paste cleanup Word and Google Docs paste a document, not a fragment — mso-* styles, <o:p> tags and a class on every element. Structure is kept, presentation dropped

The editor mounts its own picker through blog.media_picker_modal_view, defaulting to media::picker-modal. That is a modal only — the toolbar button is the trigger, so nothing visible is added to the page. It dispatches media-picked for the rich-text-image group and inserts what it receives.

Name a view that does not exist and nothing is mounted: the image button is inert rather than erroring. blog.media_picker_view is separate and is the field used for featured images, avatars and covers.

To replace the editor entirely, publish the views and edit resources/views/vendor/blog/components/rich-text.blade.php, or set blog.routes.serve_assets to false and load your own.

Publishing the views freezes them. vendor:publish --tag=blog-views copies these screens into your application, and a published copy wins over the package's — so upgrades stop reaching them. That is how a stray image card survived a release that removed it. Publish to restyle, by all means; just republish with --force after upgrading, or expect to merge by hand.

Permissions

Every policy declares a subject, so with kreetancraft/laravel-user-management installed one command creates all of them:

php artisan user-management:sync-permissions
Subject Abilities
post view, create, update, delete, publish
blog-comment view, create, update, delete, moderate
blog-category view, create, update, delete
blog-tag view, create, update, delete
blog-author view, create, update, delete
blog-series view, create, update, delete

Publishing is separate from editing on purpose: a writer may draft without being able to put it in front of readers. The taxonomy subjects are prefixed blog- because category and tag are words other packages will want too.

Posts and Comments links also appear in that package's sidebar, with nothing declared on either side.

SEO

kreetancraft/laravel-seo is a hard dependency — posts, categories, authors and series all carry meta, JSON-LD and sitemap entries. This package contributes itself to that one through container tags, so neither names the other in config:

  • its four SEO-enabled models appear on the bulk SEO screen
  • its URLs appear in GET /api/v1/sitemap
  • BlogJsonLd::posting() builds BlogPosting schema from the site-wide publisher node

Front-end paths default to /blog/{slug}, /blog/category/{slug} and so on. Override any of them in config/seo.php under paths.

Images

Install the media package and point two config values at it. That is the whole setup:

composer require kreetancraft/laravel-media-manager
// config/blog.php
'image_resolver' => \Kreetancraft\Media\Support\MediaImageResolver::class,
'media_picker_view' => 'media::picker-field',
// config/seo.php  — the same field, for og:image
'og_picker_view' => 'media::picker-field',

Featured images, author avatars and series covers now show a Choose button on their edit pages, and the rich editor's image button opens the same library. Nothing else to write.

Where images appear once wired

Post edit Featured image, and images inserted into the body from the editor
Author edit Avatar
Series edit Cover
Category edit og:image, through the SEO panel
Anywhere with the SEO panel og:image, previewed live in the Facebook and X cards

How it works. Neither package depends on the other. The blog asks a configured resolver for URLs and renders a configured view for picking; the media package supplies both. Point them at your own class and view instead and nothing here changes.

Leave both null and the blog still runs — images return null and the pickers hide themselves rather than rendering a button with nothing behind it.

Listings call Post::preloadImages() once per page, so images cost one query for the page rather than two per row.

The public API

Read-only, and only published posts:

GET  /api/v1/posts                      index, filterable by category, tag, author, series, featured
GET  /api/v1/posts/{slug}               detail, with JSON-LD, neighbours, related and approved comments
GET  /api/v1/categories                 GET /api/v1/categories/{slug}
GET  /api/v1/authors/{slug}             GET /api/v1/series/{slug}
GET  /api/v1/tags
POST /api/v1/posts/{slug}/comments      always lands in the moderation queue as pending

Rate limiters are configurable and unset by default — naming one your app has not defined would throw on routes this package registers automatically:

'api_rate_limiter' => 'api-read',
'api_write_rate_limiter' => 'api-sensitive',

Set blog.comments.enabled to false and the comment endpoint is not registered at all.

Scheduled publishing

php artisan blogs:publish-scheduled

Schedule it in routes/console.php; posts with a scheduled status and a due published_at become published and the API cache is flushed.

Requirements

PHP 8.2+, Laravel 12 or 13, Livewire 4, Flux 2, and kreetancraft/laravel-seo.

License

MIT.