---
title: "Syncing contacts — Contacts for Laravel | Roundly"
description: "Reconcile an owner’s contacts of one kind to an exact list — update matches in place, create new ones, delete the rest, order by input."
url: https://roundly-consulting.com/open-source/docs/contacts-for-laravel/syncing
language: en
---

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

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

# Syncing contacts

Contacts::for($owner)->sync() is built for a “manage contacts” form: hand it the full list for one kind and it works out what to add, update and remove:

```php
use RoundlyConsulting\Contacts\Facades\Contacts;
use RoundlyConsulting\Contacts\DataTransferObjects\ContactData;
use RoundlyConsulting\Contacts\Enums\ContactType;

$phones = Contacts::for($user)->sync(ContactType::Phone, [
    new ContactData(ContactType::Phone, '+421900000000', label: 'Mobile', isPrimary: true),
    new ContactData(ContactType::Phone, '+421900111222', label: 'Office'),
]);

$phones->pluck('position');   // [0, 1] — input order

// A registered custom kind syncs as itself
Contacts::for($user)->sync('whatsapp', [new ContactData(ContactType::Custom, '+421900123456')]);
```

- Existing contacts whose normalized value matches an item are updated in place — label, category, meta and position; isPrimary: true promotes one. An address-only item (a blank value with a structured address) matches on its rendered address, so an unchanged item keeps its id and its verification.
- Items with no match are created through the add action (normalized, validated, auto-primary).
- Existing contacts of that kind missing from the list are soft-deleted.
- If the primary is among the deleted and no item asks for isPrimary, the first synced contact becomes the primary (auto\_primary). Whenever the primary moves — this way or through an item flagged isPrimary — the returned collection is read back fresh, so it carries the stored primary flags.
- position follows input order, and the returned collection keeps that order.
- Every change fires its own ContactAdded, ContactUpdated or ContactDeleted event.

The kind is a ContactType or a raw kind string, so a registered custom kind such as whatsapp syncs as itself. Each item is stored under the kind you pass to sync(), whatever type the DTO carries. Syncing an empty list removes every contact of that kind.

## From a controller

```php
public function update(Request $request): RedirectResponse
{
    $validated = $request->validate([
        'phones' => ['array'],
        'phones.*.value' => ContactType::Phone->rules(),
        'phones.*.label' => ['nullable', 'string'],
    ]);

    Contacts::for($request->user())->sync(ContactType::Phone, array_map(
        fn (array $row) => new ContactData(ContactType::Phone, $row['value'], label: $row['label'] ?? null),
        $validated['phones'] ?? [],
    ));

    return back();
}
```

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