Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Filament Saas Panel Laravel Package

tomatophp/filament-saas-panel

Filament SaaS Panel by TomatoPHP gives your Filament app a ready-made SaaS backend: manage plans, subscriptions, tenants, and billing from one clean admin panel. Ship multi-tenant SaaS features faster with prebuilt resources and pages.

View on GitHub
Deep Wiki
Context7

Getting Started

1. Confirm the stack before you type composer require. This package only pays off on Laravel + Filament + Livewire + JetStream teams. Check three things first:

  • Which Filament major the package targets, and whether your other panel plugins support it. Filament plugin version conflicts are the single most common failure mode here.
  • Whether JetStream teams are mandatory or whether you can bind the package to an existing org/workspace model. If you have a non-JetStream tenancy model, stop and evaluate before installing.
  • Whether the package migrations create users/teams/subscriptions-style tables or merely reference yours. This determines whether adoption is a one-day install or a multi-week reconciliation.

2. Spike in a throwaway app, not the main product. Stand up a scratch Laravel app that mirrors your auth and tenancy, then:

composer require tomatophp/filament-saas-panel
php artisan vendor:publish --provider="TomatoPHP\\FilamentSaasPanel\\FilamentSaasPanelServiceProvider"

Publish without --force, then diff the published migrations against your schema before running them. Decide explicitly: adopt their tables, bind to yours, or vendor only the invitation flow. Pin the exact version and keep the lock entry so the decision is reversible.

3. Register the panel plugin. Add the plugin to your Filament panel provider (->plugin(...)) and confirm the panel boots at /admin (or your configured path). Check the package README/src for the exact plugin class name and any required panel configuration — the README's "Team Invitation Links" section is also the upgrade-notes home for invitations, so read it before wiring anything.

4. Fix your URL and proxy config before testing invitations. Signed links derive from APP_URL. Set APP_URL, SESSION_DOMAIN, and TrustProxies correctly and force the scheme if you are behind TLS termination (URL::forceScheme('https') in a service provider). If this drifts, invitation links fail silently with a 403 and you will waste a day.

5. First use case: run the full invitation lifecycle end-to-end. Invite a teammate with a second email, open the link in a private window, confirm, accept. Then run the negative paths: unsigned URL, tampered signature, expired invitation, wrong email, cross-team invitation ID. That loop is the highest-value and highest-risk surface in the package, and it is the thing you would otherwise build and security-review yourself.

Where to look first, in order: the published config file → the migrations (schema ownership question) → the team pages / invitation action classes (the extension points) → the plans and subscriptions resources (usually plain Filament CRUD over your billing provider) → the README upgrade notes.

Implementation Patterns

Treat the panel as a control surface, not a billing engine. Plans and subscriptions screens are admin CRUD over state your billing provider (Stripe via Cashier, Paddle, etc.) owns. Keep money movement in webhooks and a service layer; let the panel read and, where safe, edit. If an admin changes a plan in the panel, reconcile to the provider in the same action — do not let panel state and provider state drift.

Extend by decorating, not forking. The team-page invitation actions are the intended extension point. Wrap the package's action classes in your own subclass or a thin application-level service that calls them, and put your policy, notification, and audit-logging logic there. Forking means you own the upgrade path; decoration keeps you on Composer.

Scope every team action to the team bound from the route, not the resource ID. The correct shape is: resolve the team from the route, verify the authenticated user's membership, then look up the invitation constrained by that team. If an invitation ID resolves across teams, you have an IDOR. Write a test that asserts a user in team A cannot accept or cancel an invitation for team B.

Keep GET idempotent. The confirm-before-accept/cancel step exists so mail scanners, link previewers, and chat unfurlers cannot mutate state. Preserve that: the signed link renders a confirmation page; acceptance and cancellation are POSTs behind that confirmation. Never "just accept on GET because it's simpler."

Use Filament tenancy for the tenant boundary. If you are on a Filament version with tenancy support, bind the tenant to your teams model and let Filament apply the tenant scoping, rather than hand-rolling where('team_id', ...) in each resource. For resources the package ships, check whether they already scope by tenant — if you add your own resources alongside them, match their scoping convention exactly.

Model your panel topology deliberately. A single admin panel where support and finance operate across all tenants is a different security posture from a tenant-facing panel where users manage their own team. Prefer two panels (or one panel plus explicit policies) over a single panel with conditional navigation. Gate entry with canAccessPanel() and a super-admin Gate::before.

Local dev and seeding. Seed plans and a demo tenant with factories so the panel is useful on first boot and so your feature tests have realistic fixtures. Keep seeders idempotent (updateOrCreate) so re-running after an upgrade does not duplicate plans.

Test the invitation lifecycle in your own suite. Do not assume the package's coverage covers your integration. At minimum: accept happy path, cancel happy path, wrong-email invitee, expired, unsigned, tampered signature, cross-team access, and an already-member user clicking a stale invite. Run this suite on every Composer upgrade — it is cheap and it is the regression surface you actually care about.

Localization. Publish the translation files early and wire them to your supported locales rather than retrofitting after screens are written. This is a cheap win for non-English admin consoles.

Keep the integration at arm's length. Put the package behind a small application-level boundary (your own actions/services for invitations and team lifecycle) so that if you later need to peel it out, or soft-fork it, you change one layer instead of chasing references across the codebase.

Gotchas and Tips

Signed links break silently under URL/proxy drift. Symptoms: 403 on a link that "should work," or a link that works locally and fails in staging. Check APP_URL, SESSION_DOMAIN, TrustProxies, and scheme forcing. Verify with php artisan route:list | grep -i invite that the middleware includes signed and that the route host matches what you are clicking.

Verify the validation ordering. The safe order is signature → invitation validity → invitee email match. If the email check runs first, an unauthenticated probe can enumerate whether an invitation exists. Read the package's invitation handling and confirm the order; if it is wrong, that is a reason to decorate the action rather than accept the default.

Mail scanners and prefetchers will hit your links. This is exactly why the confirm step exists — but any custom notification or landing page you add must preserve the GET-is-safe rule. Also expect scanners to generate 403/404 noise in your logs; filter it rather than chasing it.

Do not run php artisan migrate --force in a spike against a real schema. Publish, diff, and read the migration files. If the package owns users or teams tables you did not expect it to own, that is a decision point, not a nuisance.

Upgrade discipline is non-negotiable. A v4 line with patch releases that sometimes exist only to re-index Packagist, plus README-carried upgrade notes, means breaking changes can appear between majors. Pin an exact version in composer.json, read the README upgrade notes before every bump, and re-run the invitation test suite. Record the reason for the pin so the next person does not casually unpin it.

Bus factor is real. Low star count, zero listed dependents, small-maintainer ecosystem. You will be the first to hit some integration bugs, with little precedent to search. Budget for that: read the source of the screens you actually use rather than assuming documented behavior, and keep your decoration layer thin enough that a soft fork is a weekend, not a quarter.

Panel edits can desync billing state. If an admin cancels a subscription in the panel, that must propagate to the provider. Treat panel mutations of billing state as commands through your service layer, not as direct Eloquent writes.

Watch guest-vs-auth middleware on invitation routes. Whether the invite route is guest, auth, or verified determines what an existing logged-in user with a different email sees. Mismatched middleware produces confusing redirects rather than clear errors — decide the behavior explicitly and test it.

Config and translations need cache invalidation. After publishing config or lang files, run php artisan config:clear (and optimize:clear in deploy) or you will debug a change that never took effect.

Check APP_KEY consistency across environments. Signed URLs are keyed; rotating or mismatching APP_KEY between the environment that generated the invite and the one validating it invalidates every outstanding invitation. If you rotate keys, know that pending invitations die.

Time skew. Signed URLs have an expiry. Container clocks drifting between app and queue workers can make fresh invitations look expired. Ensure NTP on hosts and be generous with expiry windows for email-based flows.

Filament resource collisions. If you already have resources or pages with the same slug as the package's, one silently wins. Inventory navigation groups and slugs before registering the plugin, and rename conflicts on your side.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource