---
title: "Integrations — Teams for Laravel | Roundly"
description: "Multi-admin join sign-off via approvals, plus team contacts, addresses and team-to-team connections from sibling Roundly packages."
url: https://roundly-consulting.com/open-source/docs/teams-for-laravel/integrations
language: en
---

[All packages](https://roundly-consulting.com/open-source.md)

[Teams for Laravel](https://roundly-consulting.com/open-source/docs/teams-for-laravel.md)

# Integrations

Teams integrates six sibling Roundly packages. They’re hard dependencies, so their seams are always available on the bundled Team model:

| Package | What it adds to a team |
| --- | --- |
| `enums-for-laravel` | JoinRequestStatus and JoinPolicy gain labels(), options(), validationRule(), values() and case lookups. |
| `options-for-laravel` | Typed per-team settings — join policy, seat cap, default role, require-approval. |
| `contacts-for-laravel` | Support and billing emails, phones and URLs, with a primary per type. |
| `addresses-for-laravel` | A billing, physical and mailing address book with typed lookups. |
| `approvals-for-laravel` | Opt-in multi-admin sign-off on join requests, mirrored back onto the request. |
| `connections-for-laravel` | Team-to-team and team-to-user affiliations with permissions and expiry. |

Each keeps its own tables — the options, contacts, addresses, connections and approvals migrations you published during Installation.

## Multi-admin join sign-off

Opt in with TEAMS\_APPROVALS=true, then route a join request through the approvals engine instead of a single responder. The native joinRequests()->approve() / deny() path is untouched:

```php
use RoundlyConsulting\Approvals\Enums\ApprovalRule;
use RoundlyConsulting\Approvals\Facades\Approvals;
use RoundlyConsulting\Teams\Facades\Teams;

// .env → TEAMS_APPROVALS=true
$request = Teams::for($team)->joinRequests()
    ->requireApprovalFrom([$admin1, $admin2])
    ->rule(ApprovalRule::Quorum)
    ->quorum(2)
    ->open($user);   // opens pending; an ApprovalRequest is created

// Admins sign off through the approvals engine:
Approvals::for($request)->as($admin1)->approve();
Approvals::for($request)->as($admin2)->approve();   // quorum reached — the member is added
```

requireApprovalFrom(), rule() and quorum() return a new handle, so a staged handle can be kept and reused. When the engine resolves, SyncJoinRequestStatusFromApproval mirrors the outcome through Teams::for($team)->joinRequests() — so Teams::fake() records it: approved runs the add-member path and fires JoinRequestApproved; rejected marks the request Denied and fires JoinRequestDenied; cancelled and expired are no-ops. A join request already resolved by hand (or by expiry) keeps that outcome — the engine never overturns it. The listener acts only while teams.approvals.enabled is on. rule() and quorum() fall back to teams.approvals.rule and .quorum; with approvals disabled, open() follows the native flow.

## Contacts

```php
use RoundlyConsulting\Contacts\Enums\ContactType;
use RoundlyConsulting\Teams\Facades\Teams;

// The team's ContactBook — the same as Contacts::for($team):
Teams::for($team)->contacts()->email('billing@example.com')->label('billing')->primary()->add();
Teams::for($team)->contacts()->phone('+441234567891')->label('billing')->add();
Teams::for($team)->contacts()->url('https://status.example.com')->label('status')->add();
Teams::for($team)->contacts()->primary(ContactType::Email);   // Contact|null

// The HasContacts trait on Team works too:
$team->addEmail('support@example.com', 'support', primary: true);
$team->addPhone('+441234567890', 'ops');
$team->addUrl('https://example.com', 'website');
$team->primaryEmail();                                        // Contact|null
```

## Addresses

```php
use RoundlyConsulting\Addresses\DataTransferObjects\AddressData;
use RoundlyConsulting\Addresses\Enums\AddressType;
use RoundlyConsulting\Teams\Facades\Teams;

// The team's AddressBook — the same as Addresses::for($team):
Teams::for($team)->addresses()->add(new AddressData(
    city: 'London', street: '1 King St', postalCode: 'EC1A 1AA',
    countryIso: 'GB', type: AddressType::Billing, isPrimary: true,
));
Teams::for($team)->addresses()->primary(AddressType::Billing);

// The HasAddresses trait on Team works too:
$team->createAddress(city: 'Berlin', street: 'Example Str. 1', postalCode: '10115', countryIsoCode: 'DE');
$team->getPrimaryAddressOfType(AddressType::Billing);
```

contacts() and addresses() hand you the team’s ContactBook and AddressBook, so those packages’ facades — and their fakes — apply. Both packages ship an addAddress() trait method; on Team the structured addresses version wins, and the contacts free-text variant is available as addContactAddress().

## Team connections

```php
use RoundlyConsulting\Teams\Facades\Teams;
use RoundlyConsulting\Teams\Models\Team;

// The connections facade scoped to the team — the same as Connections::from($team):
Teams::for($team)->connections()->to($partnerTeam)->withPermissions('share:roster')->connect();
Teams::for($team)->connections()->to($partnerTeam)->disconnect();

// The HasConnections trait on Team works too:
$team->connectTo($partnerTeam, ['share:roster']);   // team ↔ team
$team->inviteConnection($partnerTeam);              // a pending invitation
$partnerTeam->acceptConnectionFrom($team);

$team->isConnectedTo($partnerTeam);
$team->connectablesOfType(Team::class);             // partner teams
$team->hasPermissionThroughConnection($partnerTeam, 'share:roster');
```

Team implements Connectable, so it can connect to other teams or to any Connectable model, such as a user using the connections package’s trait.

## 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](https://roundly-consulting.com/support-us.md)

By donating, you agree to our [donation terms](https://roundly-consulting.com/donation-terms.md).

[Support our open source work (opens in a new tab)](https://donate.stripe.com/dRmeVe8FX5PF1Qd9pXcEw00) [Join us on Patreon (opens in a new tab)](https://www.patreon.com/cw/roundly)

## 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.

[Get a quote in 48 hours](https://roundly-consulting.com/contact.md) [Browse all packages](https://roundly-consulting.com/open-source/docs/teams-for-laravel.md)
