Laravel Blog
A fully-featured, drop-in blog package for Laravel 11+ built on Livewire 4 and Tailwind CSS v4. Content saves to structured JSON — 100% compatible with Laravel's localization and AI translation pipelines.
Live Rich Editor
3-panel editor with tools, canvas and image sidebar. Bold, italic, highlight, font sizes, lists and images.
Translation Ready
Every text node passes through __() at render time. Works with any Laravel translation pipeline.
Image Manager
Full folder browser, multi-upload, alignment controls, size slider — all built in.
Why JSON instead of HTML?
Most blog editors save raw HTML. That makes translation impossible without parsing and re-rendering unpredictable markup.
Laravel Blog saves content as a structured JSON document where every text node is a discrete string. When rendering, each string passes through Laravel's __() helper — meaning your entire article body is automatically translatable via standard language files or any AI translation pipeline that hooks into handleMissingKeys.
json{ "type": "doc", "content": [ { "type": "paragraph", "content": [ { "type": "text", "text": "Hello world", "marks": [{ "type": "bold" }] } ] } ] }
Requirements
| Requirement | Version |
|---|---|
| PHP | 8.2+ |
| Laravel | 11 or 12 |
| Livewire | 4.x |
| Tailwind CSS | v4 |
| Storage link | php artisan storage:link must be run |
Installation
composer require warmar/laravel-blog
php artisan blog:install
The installer asks whether to enable comments and categories, then publishes config, migrations, views, models and services.
php artisan migrate
use App\Livewire\Blog\BlogHome;
use App\Livewire\Blog\BlogShow;
use App\Livewire\Blog\BlogAdmin;
Route::get('/blog', BlogHome::class)->name('blog.home');
Route::get('/blog/{slug}', BlogShow::class)->name('blog.show');
Route::get('/blog-admin', BlogAdmin::class)->name('blog.admin');
'admin' => [
'emails' => [
'[email protected]',
],
],
Configuration
phpreturn [ 'features' => [ 'comments' => true, 'categories' => true, ], 'admin' => [ 'emails' => [ '[email protected]', ], ], 'pagination' => [ 'per_page' => 10, ], 'categories' => [ // 'Laravel', // 'Tutorials', // 'News', ], ];
Categories
Categories are plain strings defined in config — no database table needed. Just add them to the array:
'categories' => ['Laravel', 'Tutorials', 'News'],
They populate the category dropdown in the admin editor and the filter sidebar on the blog home page. Each article stores its selected category as a varchar in the database.
File Structure
After publishing, files land in your application at:
Database
A single table data_articles is created:
| Column | Type | Notes |
|---|---|---|
id | bigint | Primary key |
category | varchar(255) | Nullable, indexed |
metatitle | text | SEO title |
metadesc | text | SEO description |
metakeywords | text | SEO keywords |
title | varchar(255) | Article title |
slug | varchar(255) | Unique URL slug |
article | longtext | JSON content |
published_at | timestamp | Null = draft |
created_at / updated_at | timestamp |
Admin Editor
Navigate to /blog-admin. Protect this route with your project's own middleware — the package includes no built-in auth logic.
3-Panel Layout
Left — Tools
Sticky toolbar: font size, bold, italic, underline, strikethrough, highlight with color picker, lists, image, new block, link.
Center — Canvas
Live editable surface. Div-based — no semantic heading elements, so no browser bold-context interference.
Right — Images
Every inserted image listed with alt text editor, alignment buttons (L/C/R) and a size slider (10–100%).
Keyboard Shortcuts
| Key | Context | Action |
|---|---|---|
| Enter | Paragraph | Line break within block |
| Enter | List | New list item |
| Shift + Enter | List | Exit list, new paragraph block below |
Highlight Color Picker
The HL button applies the currently selected highlight color to selected text. Click the thin color strip below the HL label to open the picker — choose from the native OS color wheel or type a hex value directly. The button preview updates live.
Image Manager
Click the 🖼 tool button to open the image manager modal.
- Folders — create, rename and delete folders. Navigate via the sidebar tree or folder grid.
- Upload — select multiple images at once. Uploaded immediately to storage/blog-media/.
- Select existing — click any image thumbnail to add it to the pending tray.
- Pending tray — add alt text per image, remove unwanted ones, then click Insert to place them at the cursor position.
- Image operations — rename ✎, copy ⧉ or delete × via hover overlay on each thumbnail.
APP_URL dependency, works on any hostname or port.
Categories
Enable in config and define your category list:
'features' => ['categories' => true],
'categories' => ['Laravel', 'Tutorials', 'News', 'Releases'],
Categories appear as:
- A dropdown select in the admin article editor
- A filter sidebar on the blog home page with article counts
- A pill badge on each article card and article header
Stored as a plain varchar(255) — no separate table, no migrations needed when adding new categories.
Comments
Enabled by default. Disable in config:
'features' => ['comments' => false],
Features
- Threaded replies (one level deep)
- Edit your own comments
- Delete your own comments
- Admins can delete any comment
- 3000 character limit per comment
- Login required — redirects to route('login')
JSON Schema
Block types
| Type | Description |
|---|---|
paragraph | Default text block (div-based) |
bulletList | Unordered list containing listItem blocks |
orderedList | Ordered list containing listItem blocks |
listItem | Individual list row |
image | Image with src, alt, align, width attrs |
spacer | Empty vertical space |
hardBreak | Line break within a block |
Text marks
| Mark | Renders as | Attrs |
|---|---|---|
bold | <strong> | — |
italic | <em> | — |
underline | <u> | — |
strike | <s> | — |
highlight | inline background color | color (hex/rgb) |
fontSize | inline font-size em | size: sm / md / lg / xl |
link | <a> | href |
RichTextRenderer
Renders JSON content to safe HTML. Used on the public blog show page:
phpuse App\Services\Blog\RichTextRenderer; {!! RichTextRenderer::render($article->article, translate: true) !!}
When translate: true (the default), every text node passes through __():
__("Your article text here")
Pass translate: false to show raw content without translation (e.g. in admin preview).
Localization
All user-facing strings in blade views are wrapped in __(). Article content is also translated through __() at render time via RichTextRenderer.
To translate article content automatically, hook into Laravel's missing key handler or use a package like Laravel AI Translate — strings are collected as they are encountered on first render.
php// In AppServiceProvider or a dedicated provider: Lang::handleMissingKeysUsing(function ($key, $locale) { // Queue for AI translation TranslationJob::dispatch($key, $locale); });
Customisation
All views, models and services are published to your application and are yours to modify freely. The package does not impose any constraints after publishing.
# Re-publish only views
php artisan vendor:publish --tag=laravel-blog-views --force
# Re-publish config
php artisan vendor:publish --tag=laravel-blog-config --force
# Re-publish everything
php artisan vendor:publish --tag=laravel-blog-app --force
Public Pages
/blog — Blog Home
- Hero header
- Live debounced search
- Category filter sidebar (from config)
- Article grid with category pill, date and excerpt
- Laravel pagination
/blog/{slug} — Article
- Sticky top bar with back link and admin controls
- Publish date + category badge
- Rendered article content via RichTextRenderer
- Comments section (if enabled)
/blog-admin — Admin Panel
- Article list with category, slug and status columns
- Full 3-panel article editor
- Image manager modal
- Protected by admin email check — no middleware required
Credits
Laravel Blog is built and maintained by Warmar.
Built with Laravel · Livewire · Tailwind CSS · Alpine.js
Released under the MIT License.