For a SaaS I'm working on, I wanted to allow myself, friends and partners to have forever free accounts. I'm using Laravel Spark for the billing and Filament for the app admin panel.
How to implement free accounts in Laravel Spark
There are multiple ways to do this, all depending on your individual setup. For the app I'm currently working on, here is how I have set it up:
- Per project billing with unlimited users per project
- The
Project
model is theBillable
in Spark - So in Filament the tenant is the
Project
- A boolean column
free_forever
in theprojects
table
Managing multi-tenancy in Filament
To setup multi-tenancy in Filament, there is excellent documentation to get you started, check it out:
To get you up to speed with Filament, I can highly recommend the Filament 3 From Scratch course on Laravel Daily. You need a paid subscription, but I can highly recommend it. Check out the Laravel Daily and Filament Daily channels on YouTube to understand why I highly recommend a premium subscription.
Handling free accounts in Filament multi-tenancy
Let me describe my case in greater detail first. Users can create a project and collect feedback from users. Every project is tied to a website and can have multiple users that manage the collected feedback. The basic structure in Filament looks like this:
ProjectPanelProvider
this is the panel my customers use.UserResource
to manage the users per project.FeedbackResource
to manage the feedback collected (this needs either a forever free account or a paid subscription).
My goal is to protect the FeedbackResource
by a paywall for paying customers and allow forever free users to access it.
I'm going to show 2 ways to implement this. The first one is only for demo purposes, because at the time I didn't know better. Thankfully the Filament team helped me out on twitter and pointed me into the right direction.
Getting the current tenant in isTenantSubscriptionRequired
on the FeedbackResource
I didn't know any better and couldn't use Filament::getTenant()
in the static method isTenantSubscriptionRequired()
, but I needed the current tenant Project
to check if free_forever
was set to true
.
The following code does work, but it's not ideal. It involves parsing the path
from the $request
and reading out the tenant id.
I knew it right away, there must be a better way to handle this and the smart folks at Filament sure had something better in store. So I asked on twitter and got pointed in the right direction.
2nd try, extending the VerifySparkBillableIsSubscribed
middleware
As pointed out by Filament on Twitter, there is a much better way. Let's see what I came up with implementing this:
The answer is actually to extend `VerifySparkBillableIsSubscribed`, bind the new middleware to the container, and then add in a check before for "free" users. @Filamentphp on Twitter
Let's extend the VerifySparkBillableIsSubscribed
middleware. I created a new middleware called VerifyProjectIsBillable
and added the following code to it:
While this is much much better already, in my case, I can further simplify this without needing to check the current route in the middleware. Again, this was pointed out by the fantastic Filament team on twitter.
3rd try's the charm, the code used to allow free users and enforce payment for billable users
I think you can still use isTenantSubscriptionRequired
on resources/pages without having to manually check the route name. That will remove the middleware for those routes. @Filamentphp on Twitter
Here is the code I'm using in my app. Adjust the resources and pages in Filament that you need to protect by implementing isTenantSubscriptionRequired
. Bind the middleware in the AppServiceProvider
and optionally implement helpers in your tenant model.
That's it. Feel free to send me suggestions and improvements or questions how to implement it in your case, I can't wait to see what you are building.