warmardev.com
✍️

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" }]
        }
      ]
    }
  ]
}
💡 No custom directives, no proprietary syntax. Just standard Laravel __() everywhere.

Requirements

RequirementVersion
PHP8.2+
Laravel11 or 12
Livewire4.x
Tailwind CSSv4
Storage linkphp artisan storage:link must be run

Installation

1
Install via Composer
composer require warmar/laravel-blog
2
Run the installer
php artisan blog:install

The installer asks whether to enable comments and categories, then publishes config, migrations, views, models and services.

3
Run migrations
php artisan migrate
4
Add routes to routes/web.php
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');
5
Set your admin email in config/blog.php
'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:

app/ ├── Livewire/Blog/ │ ├── BlogAdmin.php # Admin panel component │ ├── BlogHome.php # Blog listing component │ └── BlogShow.php # Single article component ├── Models/Blog/ │ ├── Article.php # Article model │ └── ArticleComment.php # Comment model └── Services/Blog/ └── RichTextRenderer.php # JSON → HTML renderer resources/views/ ├── layouts/blog/ │ └── blog.blade.php # Blog layout └── livewire/blog/ ├── blog-admin.blade.php ├── blog-home.blade.php └── blog-show.blade.php database/migrations/ └── xxxx_create_blog_table.php

Database

A single table data_articles is created:

ColumnTypeNotes
idbigintPrimary key
categoryvarchar(255)Nullable, indexed
metatitletextSEO title
metadesctextSEO description
metakeywordstextSEO keywords
titlevarchar(255)Article title
slugvarchar(255)Unique URL slug
articlelongtextJSON content
published_attimestampNull = draft
created_at / updated_attimestamp

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

KeyContextAction
EnterParagraphLine break within block
EnterListNew list item
Shift + EnterListExit 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.
ℹ️ Images are served via relative /storage/blog-media/... paths — no 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

TypeDescription
paragraphDefault text block (div-based)
bulletListUnordered list containing listItem blocks
orderedListOrdered list containing listItem blocks
listItemIndividual list row
imageImage with src, alt, align, width attrs
spacerEmpty vertical space
hardBreakLine break within a block

Text marks

MarkRenders asAttrs
bold<strong>
italic<em>
underline<u>
strike<s>
highlightinline background colorcolor (hex/rgb)
fontSizeinline font-size emsize: 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).

💡 The renderer escapes all output with htmlspecialchars before applying marks, so XSS is not possible through article content.

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-publishing with --force will overwrite your changes. Back up modified files first.
# 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.