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 Team Guard Laravel Package

mominalzaraa/filament-team-guard

Filament Team Guard adds team-based access control to Filament panels: assign users to teams, switch the active team, and automatically scope resources, pages, and queries so each team only sees its own data. Ideal for secure multi-tenant admin panels.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and publish

composer require mominalzaraa/filament-team-guard:^2.1.1
php artisan vendor:publish --tag="filament-team-guard-config"
php artisan vendor:publish --tag="filament-team-guard-views"   # optional, only if you need to touch UI

Pin to ^2.1.1 rather than ^2.0 — anything below 2.1.1 has a broken customization path for team renaming (see Gotchas).

Where to look first

  1. config/filament-team-guard.php — the binding/namespace options for the team actions and the Livewire pages.
  2. Your panel provider (app/Providers/Filament/*PanelProvider.php) — the package registers its team pages/components here; this is where you enable/disable pieces and wire the panel into Jetstream teams.
  3. The published action namespace App\Actions\FilamentJetstream\* — this is the seam the package exposes for overriding behaviour. If UpdateTeamName exists there, the package treats it as your override.

First use case

The common entry point is: you have a Jetstream-style teams table (teams, team_user, team_invitations) and you want Filament to manage team membership and team settings without duplicating Jetstream's UI. Get that working end to end first — a panel page that lists the current user's teams, lets them switch, and renders the team settings/name form — before touching roles or resource scoping. The rename form is the fastest smoke test that the container bindings are correct, because it round-trips through the UpdatesTeamNames contract.

Implementation Patterns

Override behaviour via the contract, not by editing vendor code

The supported extension path is to bind your own implementation of Laravel\Jetstream\Contracts\UpdatesTeamNames (or the package's published action class) in a service provider:

// app/Providers/AppServiceProvider.php
use App\Actions\FilamentJetstream\UpdateTeamName;
use Laravel\Jetstream\Contracts\UpdatesTeamNames;

public function register(): void
{
    $this->app->singleton(UpdatesTeamNames::class, UpdateTeamName::class);
}

Do this in register(), not boot(). boot() runs after Livewire components resolve their dependencies in some flows, and you'll get the default implementation silently winning.

Publish-then-override, don't fork

Publish the action classes once, commit them, then treat them as application code. The package's regression tests for v2.1.1 explicitly cover "default binding" and "custom binding" paths, which tells you both are first-class — you should never need to subclass the Livewire component itself just to change what happens on save.

Team-scoped resources

Pair the team guard with Filament's tenancy: point the panel's tenant() at the current Jetstream team and let the guard enforce membership. Keep role checks in policies, not in the page classes — the package pages are thin, and sprinkling ->can() in resources gets unmanageable once you have more than a couple of team-owned models.

Testing your integration

Mirror what the package does: assert the contract is bound to your class, then assert the Livewire component calls through it.

it('resolves the custom team name updater', function () {
    expect(app(UpdatesTeamNames::class))->toBeInstanceOf(UpdateTeamName::class);
});

Livewire::test(UpdateTeamName::class)
    ->set('name', 'Renamed')
    ->call('updateTeamName')
    ->assertHasNoErrors();

expect($team->fresh()->name)->toBe('Renamed');

Use a real SQLite file or :memory: with migrations run, not the package's own isolated test config — the package's SQLite setup is scoped to its suite and won't include your Jetstream migrations.

Gotchas and Tips

The 2.1.1 fix matters if you customized team renaming. Before this release, the UpdateTeamName Livewire component called $team->update() directly, bypassing the UpdatesTeamNames contract entirely. If you published App\Actions\FilamentJetstream\UpdateTeamName and wondered why your validation, event dispatch, or audit logging never fired on rename — that was the cause, not your binding. Upgrade, then re-verify your override actually runs (a ray()/Log::info() in the action is the quickest proof). Reported upstream as issue #1, so expect this to have bitten others on 2.0.x.

Binding precedence is easy to get wrong. The order that matters: explicit container binding > published action class > package default. If your custom action isn't being used, dump app(UpdatesTeamNames::class)::class before assuming the package is at fault. Also clear php artisan config:clear after editing the config file's namespace keys — a cached config silently negates the publish.

Two namespaces look alike. Jetstream's own App\Actions\Jetstream\UpdateTeamName and the package's App\Actions\FilamentJetstream\UpdateTeamName are different classes with the same contract. Bind the one you actually edited; mixing them means the Filament page and Jetstream's API/profile page mutate teams through different code paths, and you'll get inconsistent behaviour that's painful to trace.

Livewire component state is not refreshed by the action. The component keeps the submitted name in its own property. If your custom UpdatesTeamNames normalizes input (trim, title-case, strip emoji), the form can show the un-normalized value until reload. Either normalize in the component before delegating or re-hydrate from $team->fresh() in the action's return path.

Don't put authorization inside the action. The contract is a pure "persist this name" seam. Authorization belongs in the component (which the package handles) or a policy — putting Gate::authorize in your action means Jetstream's non-Filament callers start failing too, since the contract is shared.

Extension points to prefer, in order: config toggles → contract bindings → published actions → published views. Reaching for the last two means you've taken on upgrade burden; the 2.1.1 fix is a good reminder that the first two are the ones with test coverage behind them.

Version pinning. Add the package to your upgrade checklist for Filament major bumps — the Jetstream contract layer is stable, but the Livewire components depend on Filament's form/page APIs, which churn.

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.
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
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core
nodisrupt/laravel-agent
sierratecnologia/media-manager