NewWe open-sourced 50+ Laravel packages
Custom AI apps, agents and automation — Roundly ConsultingRoundly
All packages
Teams for Laravel

Configuration

Once the migrations are published and run, the package works with zero configuration — every key has a default, most of them env-backed. Publish config/teams.php only to change them. The published file in full:

use RoundlyConsulting\Teams\Models\Invite;
use RoundlyConsulting\Teams\Models\JoinRequest;
use RoundlyConsulting\Teams\Models\Member;
use RoundlyConsulting\Teams\Models\Team;
use RoundlyConsulting\Teams\Models\TeamRole;

return [

    'models' => [
        'team' => Team::class,
        'member' => Member::class,
        'invite' => Invite::class,
        'team_role' => TeamRole::class,
        'join_request' => JoinRequest::class,
    ],

    'key_type' => env('TEAMS_KEY_TYPE', 'bigint'),

    'roles' => [
        'provider' => env('TEAMS_ROLES_PROVIDER', 'array'),
        'owner' => 'owner',
        'admin' => 'admin',
        'default' => env('TEAMS_DEFAULT_ROLE', 'member'),
        'per_team' => env('TEAMS_PER_TEAM_ROLES', false),
        'cache' => [
            'enabled' => env('TEAMS_ROLES_CACHE', false),
            'store' => env('TEAMS_ROLES_CACHE_STORE'),
            'key' => env('TEAMS_ROLES_CACHE_KEY', 'teams.roles'),
            'ttl' => env('TEAMS_ROLES_CACHE_TTL', 3600),
        ],
    ],

    'invites' => [
        'expires_after' => env('TEAMS_INVITES_EXPIRES_AFTER', '7 days'),
        'code_length' => env('TEAMS_INVITES_CODE_LENGTH', 32),
    ],

    'members' => [
        'prune_after' => env('TEAMS_MEMBERS_PRUNE_AFTER', '30 days'),
        'expiring_within' => env('TEAMS_MEMBERS_EXPIRING_WITHIN', 7),
    ],

    'join_requests' => [
        'prune_after' => env('TEAMS_JOIN_REQUESTS_PRUNE_AFTER', '30 days'),
    ],

    'approvals' => [
        'enabled' => env('TEAMS_APPROVALS', false),
        'rule' => env('TEAMS_APPROVALS_RULE', 'unanimous'),
        'quorum' => env('TEAMS_APPROVALS_QUORUM'),
    ],

    'gate' => [
        'register' => env('TEAMS_REGISTER_GATE', true),
        'prefix' => env('TEAMS_GATE_PREFIX', 'teams'),
        'owner_ability' => env('TEAMS_GATE_OWNER_ABILITY', 'owner'),
    ],

    'notifications' => [
        'queue_connection' => env('TEAMS_NOTIFY_CONNECTION'),
    ],

];

Every key

KeyDefaultEnvPurpose
models.teamTeam::class—Team model. Must extend the packaged Team.
models.memberMember::class—Membership model.
models.inviteInvite::class—Invite model.
models.team_roleTeamRole::class—Per-team role override model.
models.join_requestJoinRequest::class—Join-request model.
key_typebigintTEAMS_KEY_TYPEKey type of the polymorphic columns — bigint, uuid or ulid. Read by the migrations; anything else throws InvalidConfigurationException.
roles.providerarrayTEAMS_ROLES_PROVIDERRole storage: array (in code) or database (the team_roles table); anything else throws.
roles.ownerowner—Role given to a team’s creator and to a new owner.
roles.adminadmin—Role the previous owner is demoted to on transfer.
roles.defaultmemberTEAMS_DEFAULT_ROLERole used when none is supplied, e.g. approving a join request.
roles.per_teamfalseTEAMS_PER_TEAM_ROLESEnable per-team role overrides. Off = global roles, zero extra queries.
roles.cache.enabledfalseTEAMS_ROLES_CACHECache the database role map; flushed on every role change.
roles.cache.storenullTEAMS_ROLES_CACHE_STORECache store; null = the default store. Tags are used when supported.
roles.cache.keyteams.rolesTEAMS_ROLES_CACHE_KEYCache key for the role map.
roles.cache.ttl3600TEAMS_ROLES_CACHE_TTLCache lifetime in seconds, 1–31536000.
invites.expires_after7 daysTEAMS_INVITES_EXPIRES_AFTERDefault invite expiry when none is supplied, also used on resend — a positive interval such as 7 days or P7D.
invites.code_length32TEAMS_INVITES_CODE_LENGTHLength of the random invite code, 8–128 (URL-safe alphanumerics).
members.prune_after30 daysTEAMS_MEMBERS_PRUNE_AFTERZero or positive interval after a membership’s expiry before teams:members:prune or model:prune deletes it.
members.expiring_within7TEAMS_MEMBERS_EXPIRING_WITHINDefault window in days (1–3660) for teams:members:expiring and MembershipExpiringSoon.
join_requests.prune_after30 daysTEAMS_JOIN_REQUESTS_PRUNE_AFTERZero or positive interval after a resolved request’s last update before model:prune deletes it.
approvals.enabledfalseTEAMS_APPROVALSRoute join requests through approvals-for-laravel for multi-admin sign-off.
approvals.ruleunanimousTEAMS_APPROVALS_RULEDefault ApprovalRule when the handle sets none: unanimous, quorum, any or weighted. Anything else throws InvalidConfigurationException.
approvals.quorumnullTEAMS_APPROVALS_QUORUMDefault quorum (a positive integer) when the join-request handle sets none.
gate.registertrueTEAMS_REGISTER_GATERegister the Gate hook and the Blade directives.
gate.prefixteamsTEAMS_GATE_PREFIXAbility prefix, e.g. teams.manage-billing.
gate.owner_abilityownerTEAMS_GATE_OWNER_ABILITYShort ability that resolves to ownership: teams.owner.
notifications.queue_connectionnullTEAMS_NOTIFY_CONNECTIONQueue connection for the publishable event subscriber stub.

Environment

The common switches are env-driven, so you rarely need the published file at all:

TEAMS_KEY_TYPE=uuid                  # set before you migrate
TEAMS_ROLES_PROVIDER=database
TEAMS_ROLES_CACHE=true
TEAMS_DEFAULT_ROLE=member
TEAMS_PER_TEAM_ROLES=true
TEAMS_INVITES_EXPIRES_AFTER="14 days"
TEAMS_MEMBERS_EXPIRING_WITHIN=14
TEAMS_APPROVALS=true
TEAMS_REGISTER_GATE=true

Strict values

The config file passes env values through raw, and the package reads them strictly. The bool keys take env strings as they come: true/1/on/yes switch a flag on and false/0/off/no switch it off; the int keys take integer strings such as "3600". A key that is not set — absent, null, or blank ('' or whitespace, as a bare TEAMS_APPROVALS= line gives) — takes its default. Anything else throws an InvalidConfigurationException naming the key instead of quietly reading as a default:

  • a mistyped switch (TEAMS_APPROVALS=disabled), role provider (databse) or approval rule;
  • an integer that is not one (abc, 7.5) or is out of range — an invite code shorter than 8 or longer than 128, for example;
  • an interval Carbon cannot parse (seven days) or that is negative — invites.expires_after must also be above zero;
  • a non-string role key, cache store or key, gate prefix or owner ability.

php artisan about shows such a value as INVALID instead of failing.

Morph key type

key_type is read by the migrations, so set it before you migrate. It types the polymorphic owner, member, invited_by, requester and responded_by columns — use uuid or ulid when the models behind them use UUID or ULID primary keys. Your morph targets must share one key type; any other value throws an InvalidConfigurationException when the migrations run.

Show your open-source love

This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.

More ways to support, including crypto

By donating, you agree to our donation terms.

Want this built into your product?

We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.