Stacked colourful building blocks
Jim Hlad profile photo Jim Hlad July 14, 2025 3 min read

Quick do-it-yourself CMS with Laravel & Filament PHP

TLDR;

Nowadays it’s fairly easy to create your own mini-CMS for simple content needs using Laravel and a modern low-code toolkit like Filament PHP. This post outlines how I used Filament to quickly spin up a personal blog CMS for my own needs. For most websites though, it's usually best to choose a mature feature-rich CMS like WordPress.

I recently decided to re-launch my personal website to showcase some of the little projects I’ve been working on lately. Of course there are many CMS systems out there like WordPress, Strapi, etc., any of which would have done the job nicely. However since I wanted the site theme to be very minimalistic (literally just headings, paragraphs and maybe some images for the blog posts), installing a full-featured CMS like WordPress seemed like overkill to me. I knew I didn't need image galleries, media embeds or multi-column layouts, and I figured that I could hammer out a simple front-end using blade components in a few hours versus learning how to create a custom WordPress theme. More importantly it seemed like a good opportunity to play around with Filament 4 which is currently in beta, and learn about some of the ways to use their block builder.

What is Filament PHP?

Filament PHP is a well-designed admin panel toolkit for Laravel applications. It's built with modern Laravel practices in mind and offers a full-featured panel for managing data via Eloquent models. I like to think of it as "low-code meets Laravel". It has a huge library of building blocks and plugins (like forms, tables, buttons, notifications, calendar widgets, and more) which can be easily added and configured with some simple PHP code. The documentation is clean and the project is actively maintained.

In the past when web developers wanted to create a custom admin back-end for a project, this often involved rolling out our own custom components by hand. For example if your preferred tech stack was something like Laravel (PHP) and Vue.js, this meant either creating components like FormInput.vue, SelectMenu.vue, PrimaryButton.vue (and dozens of others) from scratch, or finding third party libraries which implement these components for you and then trying to configure the styling to match your preferences.

Nowadays with Filament, you can setup a basic blog post form and block editor with just a few lines of PHP code.

The Block Builder component

One of the really cool features of Filament is its and Block Builder component. This component makes it very easy to build flexible page content sections using repeatable and customizable blocks—similar to the “Gutenberg” editor in WordPress. Basically on the admin side, you can create a pre-defined set of blocks that the user is able to select from for the main content area of a post. These blocks can be re-ordered using drag and drop and for elements that need rich text you can use Filament's built-in implementation of the TipTap editor.

Filament block builder in admin panel
Filament block builder in admin panel

The resulting content gets stored in the database as a nice clean JSON string. Something like:

JSON
[
    {
        "type": "heading",
        "data": {
            "content": "TLDR;",
            "level": "h2"
        }
    },
    {
        "type": "paragraph",
        "data": {
            "content": "<p>Nowadays it’s fairly easy to create your own mini-CMS for simple content needs using Laravel and a modern low-code toolkit like Filament PHP. This post outlines how I used Filament to quickly spin up a personal site CMS for my blog posts and case studies.</p>"
        }
    },
    {
        "type": "horizontal_rule",
        "data": []
    }
]

Front-end integration

On the front-end, all you have to do is loop through the content and decide how you want to display each block. For my site, I used TailwindCSS to apply some basic styling to each of the elements. Here's an example of what that may look like for the paragraph and image elements:

PHP
@foreach($post->content as $content)
    @if($content['type'] === 'paragraph')
        <p class="text-gray-800 my-4 lg:my-6 leading-relaxed text-base lg:text-lg">{!! strip_tags($content['data']['content'], '<strong><em><b><strike><a><u><ul><li><ol>') !!}</p>
    @elseif($content['type'] === 'image')
        <figure class="my-8 lg:my-12 text-center">
            <img src="{{ Storage::url($content['data']['content']) }}" alt="{{ $content['data']['caption'] }}" class="w-full md:w-3/4 mx-auto h-auto rounded-md">
            <figcaption class="mt-2 text-gray-500 text-sm">{{ $content['data']['caption'] }}</figcaption>
        </figure>
    // ...
@endforeach

Right now I only have a handful of elements that I actually need. However if this list continues to grow, or if certain elements become more complex, it would be best to re-factor this code to use separate blade components per element (e.g. <x-paragraph />, <x-image />, etc.) to keep everything neat and tidy.

Caching the post data for performance

Adding some basic query caching is also a good idea in case there is ever a large traffic spike. The following logic will ensure that the post contents are fetched from the database at most once in any 90-minute window (assuming the content hasn't changed).

PHP
class PostController extends Controller
{
    public function show(string $slug)
    {
        $post = Cache::remember("post.{$slug}", now()->addMinutes(90), fn () => Post::where('slug', $slug)->published()->firstOrFail());

        return view('posts.show', [
            'post' => $post,
        ]);
    }
}

Or in fact even better, let's cache the entire view so that all of the looping and rendering logic within the blade file doesn't need to re-run either. That way the site should be able to run fairly efficiently even on a relatively low CPU server.

PHP
class PostController extends Controller
{
    public function show(string $slug)
    {
        return Cache::remember("post.{$slug}", now()->addMinutes(90), function () use ($slug) {
            $post = Post::where('slug', $slug)->published()->firstOrFail();

            return view('posts.show', [
                'post' => $post,
            ])->render();
        });
    }
}

In the event that the content changes, we can also add some simple cache-busting logic to the PostObserver.php class.

PHP
public function saved(Post $post): void
{
    Cache::forget("post.{$post->slug}");
}

Conclusion

In conclusion, Filament's block builder is a pretty cool feature that can be used to implement a simple blog post CMS fairly easily. It may also come in handy for scenarios where you want to roll out a CMS for an end client but retain tighter control over what is and isn't editable. However if you need more freedom or features (e.g. two-column layouts, embeddable media, larger selection of block types, content versioning system etc) then going with a full-featured CMS like WordPress is probably your best bet. There's no sense re-inventing the wheel, and mature CMS systems will be more future-proof for the majority of use cases.

Up Next

How to Add Dark Mode to Your Laravel Website (SSR) Using Tailwind 4.x . ' Featured Image'

How to Add Dark Mode to Your Laravel Website (SSR) Using Tailwind 4.x

In this post we'll see how simple it is to add a dark mode toggle to your Laravel website using Tailwind 4.x. Although there are a few different ways to accomplish this, this post focuses on...

August 13, 2025 • 3 min read
12 Simple Tips for Securing Your Web Apps . ' Featured Image'

12 Simple Tips for Securing Your Web Apps

In this post I share some simple tips you can use to improve the security of your web applications. Although many of these are best practices which apply to cloud-hosted web apps in general, I'll also share a few Laravel-specific code samples to...

August 1, 2025 • 7 min read