How Filament Accidentally Became My Personal OS
Published 2 days ago
I did not set out to build a personal operating system. I needed a portfolio site.
Like most portfolio sites, aw.codes needed a way to manage projects, articles, and the other content that made up the public site. It was a Laravel application, and using Filament for the administrative side was an easy decision. It gave me the forms, tables, resources, and other pieces I needed without requiring me to spend much time building a CMS that only I was ever going to use.
At that point, Filament had a very ordinary job. It was the admin panel for my website.
The things I was managing, though, were not really portfolio pieces. They were software projects, and software projects have a life well beyond the few fields needed to display them on a website. A project on aw.codes might be represented publicly by a name, description, and link, but that is only a small part of what the project actually is. It has a GitHub repository. It might have releases, issues, pull requests, contributors, and activity. If it is a PHP package, it probably exists on Packagist and has installation data of its own.
Once I started pulling some of that information into the application, the distinction between the website and the tools behind it started to blur. The application was no longer just storing the information I wanted to publish about my projects. It was becoming a useful place for me to understand those projects myself.
Filament just kept going along for the ride.
The Admin Panel Never Became the Problem
There is plenty to like about how quickly you can build a resource or put together a table with Filament. Those are usually the things we demonstrate when showing someone Filament for the first time, and for good reason. Being able to take an Eloquent model and turn it into a useful interface with relatively little code is one of the framework's biggest strengths.
But speed at the beginning of a project is only part of the story.
What has been much more interesting with aw.codes is how little Filament has cared that the application I am building today bears increasingly little resemblance to the application I originally built. I could pull package information from Packagist and make it part of the way I manage a project. I could integrate GitHub data, bring package health information from Plumb into the same application, and build aggregated views of issues and pull requests instead of visiting repositories individually.
Eventually, I started doing things that had almost nothing to do with operating a portfolio site at all. I could monitor other websites, keep an eye on uptime, and track SSL certificates so I knew when something I was responsible for needed attention.
The scope changed considerably, but I never reached a point where Filament itself became the thing holding the application back. That is a different kind of productivity than simply being able to scaffold CRUD quickly.
Filament Gives the Application a Vocabulary
Every time I add one of these ideas, there are really two problems to solve.
The first is the interesting problem. I need to decide how to get the data, how it should be modeled, whether it belongs in the database or should remain in the external service, whether it should be synchronized or fetched on demand, and how it relates to the projects I already manage. Laravel gives me plenty of good tools for solving those problems.
The second problem is much less interesting: how am I going to interact with it?
That question can consume a surprising amount of time when building software from scratch. A new feature needs a list, and that list probably needs filtering or search. Records need actions. Some information belongs in a form while other information would make more sense as a widget. Something needs a confirmation modal, and something else needs a notification after it runs.
Filament has already given me a vocabulary for expressing most of those things. A new idea can become a resource, a page, a widget, an action, or sometimes just another piece of an existing resource. Those primitives are already designed to work together, which means I am not designing a new application every time the scope grows. I am teaching an application I already use about one more part of my work.
That has turned out to be incredibly powerful for software that only needs to make sense to me.
Not Everything Needs to Be a Product
As developers, we are surprisingly good at talking ourselves out of building software for ourselves. There is almost always an existing tool that does most of what we need. There are project management applications, uptime monitors, GitHub dashboards, note-taking applications, analytics services, and countless other products that are more polished than anything I am going to build for myself on a Saturday afternoon.
Using one of those tools is often the right decision. There is, however, a meaningful difference between building a replacement for an existing product and adding a small capability to software you already own.
When I wanted a better way to see issues and pull requests across my projects, I did not need to build a GitHub client. GitHub remains the source of truth and the place where the actual work happens. What I wanted was a view of GitHub that understood my projects.
The same applies to Packagist. I have no interest in recreating a package repository. I want the information that matters to me alongside everything else I know about a package. Even monitoring fits the same model. There are far more capable monitoring products available than anything I need to build, but the requirement changes when all I want is a little operational awareness alongside the other things I am responsible for.
Filament makes the bar for building those capabilities remarkably low.
The backend for a small internal tool might already be straightforward. Fetch some data, normalize it, store what needs to be stored, and schedule whatever needs to happen periodically. Laravel makes that part comfortable. Without something like Filament, though, the interface carries a hidden cost.
Suddenly the little tool needs navigation, layouts, tables, filters, forms, actions, empty states, confirmation dialogs, notifications, pagination, responsive behavior, and all of the details that turn backend functionality into something I actually want to use. That is usually the point where a small idea stops being small.
Inside an existing Filament application, most of that cost has already been paid. If some new capability needs a table, I have a table system. If it needs filters or actions, those concepts already exist. If it deserves its own section of the application, I can give it a resource or custom page. If the information is better as a quick overview, it might become a widget instead.
I still have to design the feature, but I rarely have to design an application around the feature. An idea does not have to clear the same threshold it would if I were building an entirely new application. It can simply be useful enough to justify adding to the one I already have.
That threshold becomes even lower when the audience consists of one person.
I do not need to make my GitHub integration configurable enough to support every possible development workflow. It only needs to understand mine. I do not need a generic system for representing arbitrary software ecosystems because I already know what kinds of projects I maintain. If a particular action would save me time, I can add it without worrying about whether enough users would discover it or whether it complicates the interface for everyone else.
The application can be opinionated because its opinions are mine.
Over time, this changes the way I think about building tools for myself. Instead of asking whether an idea is important enough to justify building an application, I can ask whether having the information or action inside the application I already use would make my work easier. Sometimes the answer is a resource or a widget. Sometimes it is just another column on a table.
That can be enough.
The Scope Kept Moving
None of this happened according to a plan. There was no architecture document for turning aw.codes into an ecosystem management platform, nor was there a roadmap for all of the services the application should eventually understand. I added things when they became useful.
That matters because designing an application around requirements you already understand is one thing. What happens when those requirements keep changing is a much more interesting test of the tools you chose at the beginning.
With aw.codes, they kept changing.
The first expansion was fairly natural. If I already had projects in the CMS, why should I manually maintain information that other services already knew about them? A PHP package exists on Packagist. Its repository exists on GitHub. Those services know things about the project that a title, description, and URL in my database cannot tell me.
Bringing that information into aw.codes did not mean making it the source of truth for everything. Quite the opposite. GitHub should own GitHub data and Packagist should own Packagist data. What my application could provide was context.
It already knew which projects were mine. It knew how I categorized them and how they appeared on the public site. It could take information from external systems and present it within that context. A project resource could still contain the fields I used to manage the public website while also becoming an interface into the wider life of that project.
Once the application understood more about each project, another limitation became obvious. I maintain more than one of them.
GitHub is very good at showing me the issues or pull requests for a repository. Repository boundaries, however, are not always the boundaries I care about when I am trying to understand the health of everything I maintain. Sometimes the question I want answered is simply what needs my attention.
Because aw.codes already understood my projects and their relationship to GitHub, I could start building views across those boundaries. Issues from different repositories could exist together. Pull requests could be aggregated instead of requiring me to work through repositories one at a time.
This is something a personal application can do particularly well. A general-purpose service has to organize information in a way that works for millions of users. My application can organize the same information around the way I happen to work.
Filament made turning that perspective into an interface relatively mundane. The interesting work was deciding what I wanted to see and getting the data into the right shape. Once I had that, tables, filters, actions, badges, navigation, and the other pieces needed to make it useful were already familiar territory.
There was no new frontend architecture to invent. It was another part of the application.
Eventually, even the project boundary disappeared. Website monitoring is much harder to explain as a feature of a portfolio CMS than GitHub or Packagist integration, but at some point I wanted visibility into other sites I was responsible for. I wanted to know whether they were up and whether their SSL certificates were healthy, so that information found its way into the application too.
This is probably the point where calling aw.codes a CMS stopped being particularly useful.
The decision was not driven by some grand plan to consolidate my digital life. I already had an application I regularly used, and I already had a framework that made presenting operational information straightforward. Adding another capability was cheaper than creating another destination.
There are excellent dedicated tools for all of these jobs, and my application does not have to compete with any of them. It only has to bring together the parts of those systems that are useful to me. Over time, aw.codes became less concerned with where information originated and more concerned with whether that information helped me understand something I was responsible for.
That is probably the closest I can get to defining what I mean by a personal OS. It is not an operating system in any technical sense, and it is not an attempt to move my entire life into a Laravel application. It is a layer over the things that make up my development ecosystem, and somewhat accidentally, Filament became the interface for that layer.
The Framework Stayed Boring
There is a temptation when an application grows like this to assume that the architecture must become equally elaborate. That has not been the interesting part of this project.
Laravel does the work I would expect Laravel to do. It gives me models, queues, scheduled jobs, caching, HTTP clients, and the other pieces needed to communicate with external services and turn their data into something useful. Filament sits on top of that.
I do not need GitHub integration built into Filament. I need Laravel to talk to GitHub and Filament to give me a good way to interact with the result. I do not need Filament to be an uptime monitoring platform. I need something to perform the checks and Filament to give me a useful way to see their state.
That separation helps explain why Filament has held up so well as the scope changed. It has opinions about interfaces without having many opinions about what my application is allowed to be.
A resource does not particularly care whether its records represent blog posts, PHP packages, monitored websites, or something I have not thought of yet. An action only needs something useful to happen when I invoke it. Pages and widgets can become as specialized as the application requires without forcing the conventional parts of the application to become equally specialized.
The primitives can remain boring while the application built with them becomes increasingly specific.
More Than an Admin Panel
I still describe Filament as an admin panel framework. It is a useful description, and if someone needs an administrative interface for a Laravel application, Filament remains very easy to recommend.
After building aw.codes with it for this long, though, the description feels increasingly incomplete.
An admin panel suggests that there is an application somewhere else and Filament provides the interface used to administer it. That was certainly true when I started. The public website was the application, and Filament was where I went to manage its content.
I am not sure that distinction exists anymore.
When I open Filament today, I am not simply administering aw.codes. I am interacting with the software I built to help manage the things around it. That might mean managing content that eventually appears on the public site, looking at information about one of my packages, getting a broader view of issues or pull requests across projects, or checking on something operational that has no public representation on aw.codes at all.
For me, the panel has become the application.
That is also where my perception of Filament itself has changed. It would be easy to assume that Filament is something you use to get an application started quickly. Resources and generated forms get you through the early CRUD-heavy phase, then eventually the application becomes sufficiently custom that you replace those pieces with something purpose-built.
That has not been my experience. As aw.codes became more custom, I used more of Filament rather than less of it.
The straightforward parts can remain straightforward. A conventional model can still have a conventional resource, and there is no prize for replacing a perfectly good table because the rest of the application became more complicated. When something does need to behave differently, Filament gives me places to do that. A resource can become more specialized, an action can encapsulate a workflow, a widget can surface information without requiring another destination, and a custom page can exist when the resource metaphor is no longer appropriate.
There are certainly applications where Filament would be the wrong interface. A highly bespoke consumer experience has very different requirements from the software I am describing here. Internal software is different, though, and so is personal software.
The interface needs to be good, but it does not need to be novel. Familiarity can be more valuable.
When I add another capability to aw.codes, I already know how I am going to interact with it. I know how navigation behaves, where filters belong, how actions work, how forms behave, and what feedback I will get when something happens. The consistency that might feel restrictive in another application becomes an advantage in this one because I can spend my time on what the software does rather than continually deciding what software should look like.
There is a tendency among developers to treat flexibility as an unqualified good. If one approach lets us control every detail and another gives us a set of conventions, the completely flexible option can feel more powerful.
Technically, it probably is. But control has a cost because every decision a framework does not make becomes a decision I have to make instead.
For aw.codes, I do not want to spend an afternoon deciding how the table for some new internal tool should work. I want to spend that afternoon solving whatever problem made me want the tool in the first place.
Filament gives me enough flexibility to make the application mine without making me responsible for inventing its interface every time I add something. The constraints did not prevent the application from becoming more ambitious.
They made that ambition cheaper.
An Accidental Personal OS
I still hesitate a little to call aw.codes a personal OS because the phrase can imply something much grander than what I have actually built. It does not manage my entire life, it is not a replacement for every application I use, and I have no interest in making it one.
GitHub is still better at being GitHub. Packagist is still better at being Packagist. Dedicated services will continue to do things that would make no sense for me to recreate.
What aw.codes gives me is a layer across the parts of those systems that matter to my work. It knows which projects are mine and which packages belong to those projects. It can understand information coming from GitHub, Packagist, Plumb, and other services in the context of that ecosystem. It can give me views that those individual services cannot because none of them are supposed to understand the whole thing.
Increasingly, it can also understand things that do not belong to the public website at all. That is enough for the term personal OS to feel useful.
The interesting part is that none of this was planned. I chose Filament because I needed a CMS for a portfolio site. Then I needed the CMS to understand more about my projects. I needed better ways to understand those projects together, and eventually other things I was responsible for started finding their way into the same application.
At every stage, I could have discovered that the project had outgrown the tool I originally chose. I never really did.
Filament did not anticipate what aw.codes would become. Neither did I. What it gave me was a set of primitives that continued to be useful as the answer changed.
Filament is exceptionally good at helping you build an admin panel quickly, but speed at the beginning is no longer the reason I find it so valuable. Years later, after a little portfolio CMS became something I would never have known to design at the beginning, Filament is still making the next idea inexpensive enough to try.
That might be the best feature a framework can have.