PHPackages                             ojessecruz/simple-blog - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Parsing &amp; Serialization](/categories/parsing)
4. /
5. ojessecruz/simple-blog

ActiveLibrary[Parsing &amp; Serialization](/categories/parsing)

ojessecruz/simple-blog
======================

A Laravel lightweight blog to quickly publish and share ideas.

v0.7.5(1mo ago)1138MITPHPPHP ^8.3CI passing

Since May 2Pushed 1mo agoCompare

[ Source](https://github.com/ojessecruz/simple-blog)[ Packagist](https://packagist.org/packages/ojessecruz/simple-blog)[ Docs](https://github.com/ojessecruz/simple-blog)[ GitHub Sponsors](https://github.com/ojessecruz)[ RSS](/packages/ojessecruz-simple-blog/feed)WikiDiscussions main Synced 3w ago

READMEChangelogDependencies (28)Versions (23)Used By (0)

Simple Blog
===========

[](#simple-blog)

A ready-to-use Laravel blog — minimal reading-focused public listing, admin CRUD with Livewire, Markdown with HTML escaping, and draft preview in a new tab. Zero opinions on authentication: you plug it in via Laravel middleware (Gate, guard, or any combo).

Requirements
------------

[](#requirements)

- PHP 8.3+
- Laravel 11 / 12 / 13
- Livewire 3.5+ or 4
- Tailwind CSS in the consuming app (the package ships Tailwind classes, it does not compile its own CSS)

Installation
------------

[](#installation)

```
composer require ojessecruz/simple-blog
```

### Quick install (recommended)

[](#quick-install-recommended)

One command publishes the config &amp; migrations and wires Tailwind up for you:

```
php artisan simple-blog:install
```

It is idempotent — safe to re-run — and will:

- publish `config/blog.php` and the migrations;
- add the Tailwind source `@import` to `resources/css/app.css` (Tailwind v4), right after your `@import "tailwindcss";`;
- offer to run the migrations.

Afterwards, protect the admin routes (see below) and run `npm run build`. To wire things up by hand instead, follow the manual steps.

### Manual installation

[](#manual-installation)

Publish and run the migrations:

```
php artisan vendor:publish --tag="simple-blog-migrations"
php artisan migrate
```

Publish the config (optional, but recommended):

```
php artisan vendor:publish --tag="simple-blog-config"
```

Publish the views (optional, only if you want to customize):

```
php artisan vendor:publish --tag="simple-blog-views"
```

Make Tailwind aware of the package views, otherwise it won't compile the classes they use and the blog renders unstyled.

**Tailwind v4** (CSS-based config) — import the package's CSS entrypoint from your app's main stylesheet. It registers the package views as a source, and because the path is resolved relative to the package file, your app never has to know the package's internal structure:

```
/* resources/css/app.css */
@import 'tailwindcss';
@import '../../vendor/ojessecruz/simple-blog/resources/css/simple-blog.css';
```

**Tailwind v3** (`tailwind.config.js`) — add the views to the `content` array:

```
content: [
    // ... your existing paths ...
    './vendor/ojessecruz/simple-blog/resources/views/**/*.blade.php',
],
```

If you published the views (`--tag="simple-blog-views"`), they live in `resources/views/` and are already covered by your app's default Tailwind sources — but keep the import above if you rely on any **non-published** views (e.g. the admin panel).

Quickstart
----------

[](#quickstart)

Three steps and your blog is up.

### 1. Protect the admin routes

[](#1-protect-the-admin-routes)

The package does **not** embed any authorization logic. You plug it in via middleware in `config/blog.php`. Examples:

```
// Via a specific Gate
'admin_middleware' => ['web', 'auth', 'can:manage-blog'],

// Via a separate guard
'admin_middleware' => ['web', 'auth:admin'],

// Combo of your app's own middleware
'admin_middleware' => ['web', 'auth', 'verified', 'super.admin'],
```

If you go with a Gate, define it as usual in `AuthServiceProvider`:

```
Gate::define('manage-blog', fn ($user) => $user->is_admin === true);
```

### 2. Configure the author model

[](#2-configure-the-author-model)

Point it to your app's User:

```
// config/blog.php
'author_model' => App\Models\User::class,
```

And make User implement the `Author` contract:

```
use Jessecruz\SimpleBlog\Contracts\Author;

class User extends Authenticatable implements Author
{
    public function getBlogAuthorName(): string
    {
        return $this->name;
    }

    public function getBlogAuthorInitials(): string
    {
        $words = preg_split('/\s+/', trim($this->name)) ?: [];
        $initials = array_map(
            fn (string $w) => mb_strtoupper(mb_substr($w, 0, 1)),
            array_slice($words, 0, 2),
        );

        return implode('', $initials);
    }

    public function getBlogAuthorAvatarUrl(): ?string
    {
        return $this->avatar_url; // or null if you don't have avatars
    }
}
```

### 3. Access it

[](#3-access-it)

- Public: `https://yourapp.com/blog`
- Admin: `https://yourapp.com/admin/blog`

Done.

Routes
------

[](#routes)

The package registers:

MethodURLNameDescriptionGET`/blog``blog.index`Public listingGET`/blog/category/{slug}``blog.category`Posts in a categoryGET`/blog/{slug}``blog.show`Individual postGET`/admin/blog``blog.admin.index`Admin list (filter/search)GET`/admin/blog/create``blog.admin.create`Create formGET`/admin/blog/{slug}/edit``blog.admin.edit`Edit formGET`/admin/blog/{slug}/preview``blog.admin.preview`Preview a draft/scheduled postGET`/admin/blog/categories``blog.admin.categories`Categories CRUDThe `/blog` and `/admin/blog` prefixes are configurable (`route_prefix`, `admin_route_prefix`).

Configuration
-------------

[](#configuration)

See `config/blog.php` (published) — every key has comments explaining what it does, with examples. Summary:

- **`route_prefix`** / **`admin_route_prefix`** — where to mount the routes
- **`public_middleware`** / **`admin_middleware`** — middleware stacks
- **`author_model`** — User model
- **`layouts.public`** / **`layouts.admin`** — Blade layouts wrapping the content
- **`cta_view`** — optional view rendered at the end of each post (e.g. pricing, newsletter)
- **`public_back_url`** / **`admin_back_url`** — where the "back" links point (`null` hides them)
- **`markdown`** — options passed to `Str::markdown()`

Customizing the layout
----------------------

[](#customizing-the-layout)

The package ships two neutral default layouts, both pulled from `config/blog.php`:

- **public** (`blog::layouts.public`) — used for `/blog` and `/blog/{slug}`. Plain HTML, no nav, content rendered through `@yield('content')`.
- **admin** (`blog::layouts.admin`) — used for `/admin/blog`. Slot-based, content rendered through `{{ $slot }}` so it can be pointed straight at modern Laravel layouts.

Most apps want the public side wrapped in their site chrome (header, nav, footer, SEO meta) and the admin inside their auth shell.

### Public layout: build it from scratch

[](#public-layout-build-it-from-scratch)

The simplest path is creating a dedicated layout for the blog with `@yield('content')`:

```
{{-- resources/views/layouts/blog-public.blade.php --}}

    @yield('title', config('app.name'))

    @stack('head') {{-- Required: lets the package emit SEO meta and JSON-LD --}}

    @vite('resources/css/app.css')

            {{ config('app.name') }}

        @yield('content')

        © {{ date('Y') }} {{ config('app.name') }}

```

Then point the config at it:

```
'layouts' => [
    'public' => 'layouts.blog-public',
    'admin' => 'blog::layouts.admin',
],
```

If your layout already loads its own CSS via `@vite`, set `'assets' => []` in `config/blog.php` to avoid loading it twice.

### Public layout: reuse a Blade component (slot-based)

[](#public-layout-reuse-a-blade-component-slot-based)

If your app already has a slot-based layout (Breeze, Jetstream, Folio, or a custom ``), create a small wrapper that bridges `@yield('content')` and `{{ $slot }}`:

```
{{-- resources/views/layouts/blog-public.blade.php --}}

    @yield('content')

```

```
'public' => 'layouts.blog-public',
```

Three lines of plumbing, your app's chrome wrapping the blog.

### Admin layout

[](#admin-layout)

The admin Livewire components use `->layout()` (slot-based), so the admin layout key can point at any slot-based view directly:

```
'admin' => 'layouts.app',  // your  view, no wrapper needed
```

If your admin layout is `@yield`-based instead, build a small slot bridge:

```
{{-- resources/views/layouts/blog-admin.blade.php --}}

    @vite('resources/css/app.css')
    @livewireStyles

    {{ $slot }}
    @livewireScripts

```

### Required directives in your layout

[](#required-directives-in-your-layout)

The package emits SEO meta tags, OG tags and JSON-LD via `@push('head')`. For these to render, your layout's `` must contain:

```
@stack('head')
```

Without it, `` and meta tags from posts won't appear in the head.

Customizing the visual style
----------------------------

[](#customizing-the-visual-style)

The package ships with `emerald` as the accent colour and `zinc` as the neutral. To brand it differently, publish the views and edit them directly. Three granularities are available:

```
# Everything (public + admin + layouts + icons)
php artisan vendor:publish --tag="simple-blog-views"

# Just the public side (index, show, public layout)
php artisan vendor:publish --tag="simple-blog-views-public"

# Just the admin side (Livewire admin views + admin layout)
php artisan vendor:publish --tag="simple-blog-views-admin"

# Just the layouts (public and admin shells, no inner content)
php artisan vendor:publish --tag="simple-blog-views-layouts"
```

The Blade files land in `resources/views/vendor/blog/`. From that point Laravel uses your published copies, so a global find-and-replace (`emerald-` → `blue-`, `emerald-` → `rose-`, etc.) is enough to retheme that slice.

Trade-off: published views are frozen at the version you published — bug fixes and new features that ship in later releases of the package won't reach them automatically. Publishing only the slice you actually want to customize (admin or public) keeps the rest tracking upstream.

Injecting a CTA into posts
--------------------------

[](#injecting-a-cta-into-posts)

Create a view (e.g. `resources/views/components/blog-cta.blade.php`) and point to it:

```
'cta_view' => 'components.blog-cta',
```

The view receives the `$post` variable and is rendered after the post content (on show) and below the feed (on index).

Models
------

[](#models)

The package exposes:

- `Jessecruz\SimpleBlog\Models\Post`
- `Jessecruz\SimpleBlog\Models\PostCategory`

Use them directly when needed (e.g. to generate a sitemap, export content):

```
use Jessecruz\SimpleBlog\Models\Post;

$posts = Post::published()->with('category')->latest('published_at')->get();
```

Testing
-------

[](#testing)

```
composer test
```

Changelog
---------

[](#changelog)

See [CHANGELOG](CHANGELOG.md).

License
-------

[](#license)

MIT — see [License File](LICENSE.md).

###  Health Score

43

—

FairBetter than 89% of packages

Maintenance91

Actively maintained with recent releases

Popularity16

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity48

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~2 days

Total

19

Last Release

43d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/e1de650c49d77234ee1d66c8c742a149ec30e99dbbe230035ae89cded326663b?d=identicon)[ojessecruz](/maintainers/ojessecruz)

---

Top Contributors

[![ojessecruz](https://avatars.githubusercontent.com/u/20089597?v=4)](https://github.com/ojessecruz "ojessecruz (41 commits)")

---

Tags

laravelmarkdownlivewireblogsimple-blogjessecruz

###  Code Quality

TestsPest

Static AnalysisPHPStan

Code StyleLaravel Pint

### Embed Badge

![Health badge](/badges/ojessecruz-simple-blog/health.svg)

```
[![Health](https://phpackages.com/badges/ojessecruz-simple-blog/health.svg)](https://phpackages.com/packages/ojessecruz-simple-blog)
```

###  Alternatives

[spatie/laravel-pdf

Create PDFs in Laravel apps

1.0k4.8M48](/packages/spatie-laravel-pdf)[filament/support

Core helper methods and foundation code for all Filament packages.

2331.0M270](/packages/filament-support)[rawilk/profile-filament-plugin

Profile &amp; MFA starter kit for filament.

3914.8k](/packages/rawilk-profile-filament-plugin)[tomshaw/electricgrid

A feature-rich Livewire package designed for projects that require dynamic, interactive data tables.

119.4k](/packages/tomshaw-electricgrid)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
