filaascom / filaas
The Laravel SAAS starter kit
Requires
- php: ^8.3
- codeat3/blade-phosphor-icons: ^2.4
- dompdf/dompdf: ^3.1
- filament/filament: ^5.0
- laravel-notification-channels/webpush: ^10.5
- laravel/cashier: ^16.5
- laravel/framework: ^13.0
- laravel/reverb: ^1.0
- laravel/tinker: ^3.0
Requires (Dev)
- fakerphp/faker: ^1.23
- laravel/boost: ^2.2
- laravel/pail: ^1.2.5
- laravel/pint: ^1.27
- mockery/mockery: ^1.6
- nunomaduro/collision: ^8.6
- pestphp/pest: ^4.6
- pestphp/pest-plugin-browser: ^4.3
- pestphp/pest-plugin-laravel: ^4.1
This package is not auto-updated.
Last update: 2026-05-11 18:25:01 UTC
README
The Laravel SAAS starter kit.
composer create-project filaascom/filaas my-app
Auth, multi-tenant teams, per-team Stripe subscriptions, Filament v5 admin
panel, PWA + Web Push, Pest 4 browser tests — all wired up. The marketing
landing on / is built from the same Filament Component classes as the panel.
Drop in your product.
What's inside
- Laravel 13 + PHP 8.4
- Filament v5 — admin panel under
/app, clusters for Account / Team / Billing already shipped. The marketing landing on/uses the sameComponentsystem. - Livewire v4 + Tailwind v4
- Cashier 16 — Stripe Checkout, customer portal, signed webhooks. Three
tiers (
free,pro,studio) × two intervals (monthly, yearly), wired throughconfig/billing.php. Subscriptions are attached to the team, not the user. - Multi-tenant teams — owners, administrators, managers, invitations by email, ownership transfer, scheduled account deletion with a 30-day grace period and a daily prune job.
- PWA — service worker, manifest, install prompt, offline page, VAPID web-push subscriptions.
- Pest 4 — feature, arch, and browser tests (via
pest-plugin-browser) covering registration, login, account settings, team details, members, invitations, ownership transfer, and billing flows. Plus a smoke pass over every panel route. - AI-ready — Laravel Boost (MCP) and
CLAUDE.mdpre-configured with Pest, Filament, Tailwind, Cashier, and Nightwatch skills. - Plus — avatar + team-logo uploads, RBAC policies, password-reset emails,
sitemap, SEO + OpenGraph meta partials, Nightwatch observability, dark mode,
scheduled
users:prunejob.
Local development
After composer create-project, the post-create hook generates APP_KEY,
creates a SQLite database file, and runs migrations. You still need to:
cp .env.example .env # if not copied already php artisan storage:link # so uploaded avatars/logos resolve npm install && npm run build
Then either run components individually, or:
composer run dev # serves app + queue listener + log tailer + vite, all concurrent
SQLite by default — zero config to look around. Default panel: /app. Marketing
landing: /.
First time setting up after a clean clone (without
composer create-project)? Runcomposer run setup— it copies.env, generates the key, migrates, installs npm deps, and builds assets in one shot.
Stripe
Two layers stay in sync: your Stripe account (products + prices) and your
app (.env Stripe IDs + config/billing.php UI copy and amounts).
1. Account and API keys
Create a Stripe account at
dashboard.stripe.com/register, or use
an existing one. For development, either flip the Test mode toggle at the
top of the dashboard, or create a dedicated Sandbox (Settings → Sandboxes →
Create sandbox) for full isolation from live data. Test mode and sandboxes
both issue separate pk_test_… / sk_test_… keys.
From Developers → API keys, paste into .env:
STRIPE_KEY=pk_test_…
STRIPE_SECRET=sk_test_…
2. Create products and prices
Two products (Pro, Studio), each with a monthly and a yearly recurring price. Click them in the dashboard at Catalog → Products, or use the Stripe CLI:
stripe products create -d "name=Pro" stripe products create -d "name=Studio" stripe prices create -d product=prod_… -d currency=usd -d unit_amount=2900 -d "recurring[interval]=month" -d "nickname=Pro Monthly" stripe prices create -d product=prod_… -d currency=usd -d unit_amount=27800 -d "recurring[interval]=year" -d "nickname=Pro Yearly" stripe prices create -d product=prod_… -d currency=usd -d unit_amount=7900 -d "recurring[interval]=month" -d "nickname=Studio Monthly" stripe prices create -d product=prod_… -d currency=usd -d unit_amount=75800 -d "recurring[interval]=year" -d "nickname=Studio Yearly"
Paste each prod_… and price_… into .env:
STRIPE_PRODUCT_PRO=prod_…
STRIPE_PRICE_PRO_MONTHLY=price_…
STRIPE_PRICE_PRO_YEARLY=price_…
STRIPE_PRODUCT_STUDIO=prod_…
STRIPE_PRICE_STUDIO_MONTHLY=price_…
STRIPE_PRICE_STUDIO_YEARLY=price_…
The free tier needs no Stripe IDs. Run php artisan config:clear after
editing .env.
3. Edit config/billing.php to match
config/billing.php is the single source of truth for everything users see —
plan key, name, description, features array,
prices.{monthly,yearly}.{amount,label,period}, badge, highlighted, and
is_free. The pricing section on the marketing landing (/) reads from it via
PricingSection::make(), and so does the in-app
/app/{team}/settings/subscription page.
amount is in cents and must match the unit_amount of the corresponding
Stripe price — Stripe is what charges the card; the config is what renders.
To rename, reprice, or add/remove a tier:
- Edit the entry in
config/billing.php. - Update the matching
STRIPE_PRICE_<KEY>_<INTERVAL>env variable to point at the new Stripe price. - If you renamed the plan key (e.g.
pro→team), add the case inApp\Enums\BillingPlan. - Restart
composer run dev(or rerunnpm run build) so Vite picks up Tailwind classes touching any new section.
4. Webhooks
Cashier registers POST /stripe/webhook automatically.
Production — in Developers → Webhooks, add an endpoint at
https://your-domain/stripe/webhook listening for the customer.*,
invoice.*, and payment_intent.* event groups. Copy the endpoint's signing
secret into STRIPE_WEBHOOK_SECRET.
Local dev — forward events to your local app with the Stripe CLI:
stripe listen --forward-to http://saas.test/stripe/webhook
The first line printed (whsec_…) is your local signing secret. Drop it into
.env as STRIPE_WEBHOOK_SECRET and keep stripe listen running while you
test. php artisan config:clear after edits.
5. Testing checkout in dev
With composer run dev and stripe listen both running:
-
Log in (any seeded user — password is
password). -
Open the team's Subscription page at
/app/{team-uuid}/settings/subscriptionand click Choose plan. -
You land on Stripe Checkout. Use a test card:
4242 4242 4242 4242— succeeds4000 0027 6000 3184— requires 3D Secure authentication4000 0000 0000 9995— fails with insufficient funds
Any future expiry, any 3-digit CVC, any postal code.
-
On success you're redirected to
/billing/{team}/success. Watchstripe listen:customer.subscription.created,invoice.paid, andpayment_intent.succeededall return200. The team row picks up astripe_id, a row appears insubscriptions, and the Subscription page now shows Manage subscription on the active tier.
Replay an event without going through Checkout:
stripe trigger customer.subscription.created
Headless equivalents live in tests/Browser/Billing*Test.php — run with
php artisan test --compact tests/Browser.
Web Push (VAPID)
Generate VAPID keys once:
php artisan webpush:vapid
Paste the printed VAPID_PUBLIC_KEY and VAPID_PRIVATE_KEY into .env. The
VAPID_SUBJECT should be a mailto: address (already set in .env.example).
Service worker lives at /sw.js, manifest at /manifest.webmanifest.
MAIL_MAILER=log by default — invitations and password-reset emails land in
storage/logs/laravel.log. Switch to your provider in .env for real delivery.
Scheduled tasks
routes/console.php schedules users:prune daily — it permanently deletes
users whose deleted_at is older than 30 days (configurable with --days=N).
Wire your scheduler:
* * * * * cd /path-to-app && php artisan schedule:run >> /dev/null 2>&1
Tests
php artisan test --compact # everything php artisan test --compact tests/Browser # only browser tests
Browser tests use Pest 4 + the pest-plugin-browser package, which drives a
real Chromium. The first run downloads the browser binary automatically.
Customizing
Branding
- App name (panel title, mail-from, manifest) —
APP_NAMEin.env - Brand color across both panels —
brand_colorinconfig/app.php(anyFilament\Support\Colors\Color::*or hex array) - Wordmark —
resources/views/components/marketing/wordmark.blade.php - Auth-pages mark —
resources/views/components/simple-logo.blade.php - Default theme mode —
->defaultThemeMode(...)inapp/Providers/Filament/{App,Home}PanelProvider.php - Marketing landing tokens (colors, fonts) —
resources/css/filament/home/theme.css
Landing page
The landing on / is the Filament page app/Filament/Home/Pages/Home.php. Edit
content() to change copy or reorder sections. Each section is a class under
app/Filament/Schemas/Components/Marketing/ plus a Blade view under
resources/views/filament/schemas/marketing/.
PricingSection reads config/billing.php automatically. Replace
->plans(...) with your own tiers (or delete PricingSection::make()) if
you're not selling subscriptions.
Adding to the admin panel
php artisan make:filament-resource MyThing
Drop it into the panel directly, or attach it to a cluster:
protected static ?string $cluster = TeamSettingsCluster::class;
Top-nav, user-menu and tenant-menu items are arrays inside
app/Providers/Filament/AppPanelProvider.php::panel().
Teams & roles
Roles are an enum at app/Enums/TeamRole.php (Administrator, Member).
Permission checks live as methods on the enum and the Team model — extend both
to add a third role.
Every team operation (invite, accept, transfer ownership, change role, leave,
delete) is its own action class under app/Actions/Teams/.
Invitations don't expire by default. To add expiry, add expires_at to
team_invitations and check it in
app/Policies/TeamInvitationPolicy.php::accept().
Account deletion grace period
Default 30 days. Override with ACCOUNT_DELETION_GRACE_DAYS in .env
(config/account.php). The daily users:prune command in routes/console.php
permanently deletes users past the cutoff.
To send a "your account will be deleted" email, hook a notification into
app/Actions/Accounts/ScheduleAccountDeletion.php.
PWA
- Manifest (name, icons, theme color, start URL) —
app/Http/Controllers/Pwa/ManifestController.php - Icons —
public/icons/icon-{192,512}.png - Offline page —
resources/views/pwa/offline.blade.php
Avatars & team logos
Filament FileUpload definitions live in AccountSettings::saveAction() and
TeamDetails::saveAction(). Disks user-avatars and team-logos are in
config/filesystems.php. The default fallback (initials via ui-avatars.com) is
in app/Filament/AvatarProviders/.
Mail copy
- Team invitation —
resources/views/mail/team-invitation.blade.php - Password reset —
app/Notifications/ResetPassword.php(no Blade; edittoMail())
Run php artisan vendor:publish --tag=laravel-mail to restyle the layout.
SEO & sitemap
- Title, description, locale —
config/seo.php - Meta tags —
resources/views/partials/seo-meta.blade.php - Sitemap routes —
app/Http/Controllers/Seo/SitemapController.php(lists/,/privacy,/termsby default)
Pricing
Free until your project earns. Three tiers, same code on every tier — only the license changes:
- Free — for projects under $1k/month MRR. MIT-style license up to the cap.
- Pro — $29/month or $278/year, up to $10k MRR. Commercial license, continuous updates while subscribed.
- Studio — $79/month or $758/year, no revenue cap. Adds white-label rights and unlimited projects.
Buy at filaas.com/#pricing.
License
MIT-style up to your tier's cap, plus a commercial addendum for paid tiers.
Plain English in LICENSE.md.