---
title: "Bulk operations & sync — Connections for Laravel | Roundly"
description: "Connect, disconnect, grant and revoke across many connectables at once, or reconcile an exact set with from()->sync()."
url: https://roundly-consulting.com/open-source/docs/connections-for-laravel/bulk-operations
language: en
---

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

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

# Bulk operations & sync

Stage several connectables with toMany() and run one bulk verb over all of them:

```php
use RoundlyConsulting\Connections\Facades\Connections;

// Touch many targets in one call (each still emits its own event).
Connections::from($user)->toMany([$teamA, $teamB])->withPermissions('view')->connectAll();
Connections::from($user)->toMany($teams)->disconnectAll();
Connections::from($user)->toMany($teams)->grantAll('publish');
Connections::from($user)->toMany($teams)->revokeAll('publish'); // any status: pending, blocked, expired
```

- connectAll() runs in one transaction on the connection model’s own database connection — not the app default — so a failing item rolls every earlier one back, and returns the connections; every item still goes through the regular create path, so events and cache invalidation stay correct.
- disconnectAll() runs in one transaction and skips connectables with no connection.
- grantAll() creates missing connections, like grant(); called without arguments it uses the staged permissions.
- revokeAll() revokes on every target that has a connection in any status — pending, blocked and expired included, so a revoked permission can’t come back on accept() or extend(). Targets with no connection, or only a soft-deleted one, are skipped.
- toMany() accepts any iterable — an array or an Eloquent collection. A bulk verb without toMany() throws MissingConnectable.

## Reconciling an exact set

Connections::from($user)->sync() — or the trait’s syncConnections() — works like Eloquent’s sync(): it connects what is missing, disconnects extras and updates the overlap, all in one transaction — a failure rolls the whole reconcile back. Status rules apply as for connect(). It returns a SyncResult:

```php
use RoundlyConsulting\Connections\DataTransferObjects\SyncTarget;
use RoundlyConsulting\Connections\Facades\Connections;

// Reconcile to exactly this set: connect missing, disconnect extras, update overlap.
// Safe to repeat — a target detached earlier is revived, not re-inserted.
$result = Connections::from($user)->sync([$teamA, $teamC]);   // or $user->syncConnections([...])
$result->attached;  // list of newly connected (or revived) ids
$result->detached;  // list of disconnected ids
$result->updated;   // list of ids that already existed
$result->isEmpty(); // true when nothing was attached, detached or updated

// Per-target permissions, expiry or meta through SyncTarget DTOs. On an overlap, only what the
// target states changes: a plain model (or a null field) keeps the stored value, and the
// status is never touched.
Connections::from($user)->sync([
    new SyncTarget($teamA, permissions: ['view', 'edit']),
    new SyncTarget($teamB, ['publish'], now()->addDays(30)),
]);
```

Targets are plain connectables or SyncTarget DTOs — use a SyncTarget to give one target its own permissions, expiry or meta:

```php
use RoundlyConsulting\Connections\DataTransferObjects\SyncTarget;

new SyncTarget(
    model: $teamB,                       // required — a Connectable
    permissions: ['publish'],            // optional
    expiresAt: now()->addDays(30),       // optional CarbonInterface
    meta: ['source' => 'directory'],     // optional
);
```

## Things to know about sync

- Sync reconciles every connection the connector initiated, across all connectable types — connections to models missing from the list are disconnected, whatever their class. Pass the complete desired set.
- On an overlap, only what the target states changes: a plain model — or a SyncTarget field left null — keeps the stored permissions, expiry and meta, and the status is never touched.
- Detached connections are soft-deleted and fire ConnectionRemoved. Syncing one back in later revives it as a fresh connection and lists it under attached, so a sync is safe to repeat.
- An empty list disconnects everything the connector initiated.
- A model listed twice is written once, with its last target.

## 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/connections-for-laravel.md)
