warmar/laravel-blog

A blog/news plugin for Laravel + Livewire

Maintainers

Package info

github.com/warmar94/laravel-blog

Language:Blade

pkg:composer/warmar/laravel-blog

Statistics

Installs: 6

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v1.0.3 2026-03-27 18:24 UTC

This package is auto-updated.

Last update: 2026-03-27 18:24:21 UTC


README

Docs: https://warmardev.com/docs/laravel-blog.html

A fully-featured, drop-in blog package for Laravel 11+ built on Livewire 4 and Tailwind CSS v4. Write articles in a live rich-text editor that saves to structured JSON — making every piece of content 100% compatible with Laravel's localization system and AI translation pipelines out of the box.

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 Laravel's standard language files or any AI translation pipeline that hooks into handleMissingKeys. No custom directives, no proprietary syntax.

{
  "type": "doc",
  "content": [
    {
      "type": "paragraph",
      "content": [
        { "type": "text", "text": "Hello world", "marks": [{ "type": "bold" }] }
      ]
    }
  ]
}

Requirements

  • PHP 8.2+
  • Laravel 11 or 12
  • Livewire 4.x
  • Tailwind CSS v4
  • php artisan storage:link already run

Installation

composer require warmar/laravel-blog

Then run the install command:

php artisan blog:install

The installer will ask you:

  • Whether to enable comments
  • Whether to enable categories

It then publishes config, migrations, views, models and services to your application.

Run migrations

php artisan migrate

Add routes

In 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');

Protecting the admin route

The package does not include built-in auth — protect the admin route using your project's existing middleware:

Route::get('/blog-admin', BlogAdmin::class)
    ->name('blog.admin')
    ->middleware(['auth', 'verified']); // or your own admin middleware

Configuration

After publishing, your config/blog.php will look like this:

<?php

return [

    'features' => [
        'comments'   => true,
        'categories' => true,
    ],

    'pagination' => [
        'per_page' => 10,
    ],

    'categories' => [
        // 'Laravel',
        // 'Tutorials',
        // 'News',
        // 'Releases',
    ],

];

Categories

Categories are defined in config — no database table needed. Just uncomment or add strings to the categories array:

'categories' => ['Laravel', 'Tutorials', 'News'],

These populate the category dropdown in the admin editor and the filter sidebar on the blog home page. Each article stores its category as a plain string in the data_articles.category column.

File Structure

After publishing, files land in the following locations in your Laravel application:

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:

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 timestamp
updated_at timestamp

Comments are stored in article_comments.

The Admin Editor

Navigate to /blog-admin (protect with your own middleware).

Article list

  • All articles with title, category, slug and publish status
  • Edit or delete any article
  • One-click to view the live article

Editor — 3-panel layout

Left panel (tools) — sticky toolbar with:

  • Font size dropdown: S / M / L / XL
  • Bold, Italic, Underline, Strikethrough
  • Highlight with custom color picker (full color wheel + hex input)
  • Bullet list and ordered list
  • Insert image (opens Image Manager)
  • New block (+¶)
  • Insert link

Center panel (canvas) — live editable surface. Div-based — no semantic heading elements, so browser bold-context bugs don't interfere with formatting.

Right panel (inserted images) — every image in the article listed here. Per image:

  • Edit alt text
  • Set alignment: left / center / right
  • Set size: 10–100% slider

Keyboard behaviour

Key Action
Enter (in paragraph) Line break within same block
Enter (in list) New list item
Shift+Enter (in list) Exit list, new paragraph block below

Image Manager

Click the 🖼 button to open the image manager modal.

  • Folders: create, rename, delete. Navigate via sidebar tree or folder grid.
  • Upload: select multiple images at once. Uploaded to storage/blog-media/.
  • Select existing: click any image to add it to the pending tray.
  • Pending tray: add alt text per image, then click Insert to place at cursor position.
  • Image operations: rename, copy or delete via hover overlay.

Images are served via /storage/blog-media/... relative URLs — no APP_URL dependency.

Content Blocks (JSON Schema)

Block type Description
paragraph Default text block (div-based)
bulletList Unordered list with items
orderedList Ordered list with items
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 Description
bold <strong>
italic <em>
underline <u>
strike <s>
highlight Inline background color (custom, stored as rgb/hex)
fontSize sm / md / lg / xl (em-based)
link Anchor with href

RichTextRenderer

use App\Services\Blog\RichTextRenderer;

{!! RichTextRenderer::render($article->article, translate: true) !!}

When translate: true (default), every text node passes through __(). Pass translate: false to show raw content without translation.

Public Blog Pages

Blog Home (/blog)

  • Search bar (live, debounced)
  • Category filter sidebar (from config)
  • Article grid with category pill, date, excerpt
  • Laravel pagination

Blog Show (/blog/{slug})

  • Sticky top bar with back link
  • Publish date and category badge
  • Rendered article content via RichTextRenderer
  • Comments section (if enabled)

Blog Admin (/blog-admin)

Protect with your own middleware. No built-in auth.

Comments

Disable in config:

'features' => ['comments' => false],

Features:

  • Threaded replies (one level deep)
  • Edit and delete your own comments
  • 3000 character limit
  • Login required — redirects to route('login')

Localization

All blade strings use __(). Article content is also translated through __() at render time.

To translate article content automatically, hook into Lang::handleMissingKeysUsing() or use a package like Laravel AI Translate.

Customisation

All views, models and services are published and yours to modify. To re-publish:

php artisan vendor:publish --tag=laravel-blog-views --force

⚠️ This overwrites your changes. Back up first.

Credits

Laravel Blog is built and maintained by Warmar.

Built with Laravel · Livewire · Tailwind CSS · Alpine.js

License

MIT

Docs: https://warmardev.com/docs/laravel-blog.html