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
Projectmodel is the
- So in Filament the tenant is the
- A boolean column
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:
ProjectPanelProviderthis is the panel my customers use.
UserResourceto manage the users per project.
FeedbackResourceto 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
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
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
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.