Livewire CMS
The CMS powering this site. Built from scratch, mostly out of stubbornness.
A modern, flexible Content Management System built with the TALL stack (Tailwind CSS, Alpine.js, Laravel, Livewire) inspired by Statamic CMS.
Intro
I needed a portfolio site. The obvious move was WordPress, or Statamic, or any of a dozen tools that already exist and work fine. I used none of them.
Instead, I built my own CMS — collections, blueprints, entries, role-based access, audit logging, two-factor auth, the lot — because I wanted to understand every layer myself. Not as a career move. Just because that's the kind of thing I find interesting on a Saturday.
You're currently browsing it.
The Challenge
The interesting constraint here was building something that felt like a real CMS — not just a blog engine or a CRUD app with a fancy name — without it spiralling into an endless scope.
Statamic was the main reference point: its concept of Collections and Blueprints is genuinely elegant. The challenge was implementing something with that flexibility — dynamic field types, customisable content structures, configurable schemas — while keeping the codebase clean enough that future-me wouldn't hate past-me for it.
There was also the meta-problem: the CMS had to be good enough, actually, to run this website. So it wasn't purely academic. It had to ship. The Process
Built on the TALL stack — Laravel 12, Livewire 3, Alpine.js, Tailwind CSS 4 — with Flux UI for components and Pest for testing. SQLite is the default database, mostly for simplicity, though it's swappable.
The core data model ended up as three interconnected concepts: Collections (what type of content is this?), Blueprints (what fields does it have?), and Entries (the actual content). Once that clicked, much of the rest fell into place — dynamic forms, bulk actions, entry previews, and status management.
Code quality tooling — Pint, Larastan, Rector — was in from the start rather than bolted on later. That's a habit I've been building, and it made a difference.
Lessons Learned
Schema flexibility is harder than it looks. Building a genuinely dynamic field system—where a blueprint can define a text field, a rich text field, a date picker, or a dropdown—and having it render correctly in both the admin and the front end took more iteration than I expected. The gap between "this works in one case" and "this works for all field types consistently" is wider than it appears from the outside.
The other thing: building for yourself is both easier and harder than building for a client. Easier because you can make decisions without a committee. Harder because there's no external pressure to stop adding things. Scope discipline has to come entirely from you, and I'm still working on that.
Still in active development. Probably always will be.