---
title: "The HasContacts trait — Contacts for Laravel | Roundly"
description: "Add HasContacts to any model for one-line addEmail/addPhone helpers, primary lookups, ordered per-kind collections and the contacts relation."
url: https://roundly-consulting.com/open-source/docs/contacts-for-laravel/has-contacts
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)

# The HasContacts trait

Add the HasContacts trait to any Eloquent model — a user, a company, an order. No schema change per model:

```php
use Illuminate\Database\Eloquent\Model;
use RoundlyConsulting\Contacts\Concerns\HasContacts;

class User extends Model
{
    use HasContacts;
}
```

## Adding contacts

The trait is sugar over the same manager the Contacts facade uses — every helper goes through it, so values are normalized and validated, auto-primary applies, events fire and Contacts::fake() records the call:

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

// Add — normalized + validated, primary guaranteed unique per kind.
$user->addEmail('A.Person@Example.COM ', label: 'Work', primary: true);
$user->addPhone('+421 900 000 000', label: 'Mobile');
$user->addUrl('example.com');
$user->addAddressContact('12 Main St');

// Any kind, from a DTO
$user->addContact(new ContactData(ContactType::Social, '@janedoe', label: 'Mastodon'));
```

- addEmail(), addPhone(), addUrl(), addAddressContact() — each takes (string $value, ?string $label = null, bool $primary = false).
- addContact(ContactData $data) — any kind, including registered custom kinds.
- addStructuredAddress(AddressData $data, ?string $label = null, bool $primary = false) — see Structured addresses.
- addAddress() — deprecated alias of addAddressContact(), removed in the next major release.

## Alongside HasAddresses

The deprecated addAddress() collides with HasAddresses::addAddress(AddressData) from addresses-for-laravel, so a model using both traits is a PHP fatal error at class load until you resolve it. Keep the addresses method and reach the contacts one by its own name:

```php
use Illuminate\Database\Eloquent\Model;
use RoundlyConsulting\Addresses\Contracts\Addressable;
use RoundlyConsulting\Addresses\DataTransferObjects\AddressData;
use RoundlyConsulting\Addresses\Traits\HasAddresses;
use RoundlyConsulting\Contacts\Concerns\HasContacts;

class Company extends Model implements Addressable
{
    use HasAddresses, HasContacts {
        HasAddresses::addAddress insteadof HasContacts;
    }
}

$company->addAddress(AddressData::make(          // addresses: a structured Address
    city: 'Bratislava', street: 'Hlavna 1', postalCode: '81101', countryIso: 'SK',
));
$company->addAddressContact('12 Main St');        // contacts: an address contact
```

## Reading contacts

```php
$user->primaryEmail()?->value;                    // 'a.person@example.com'
$user->primaryPhone()?->value;                    // '+421900000000'
$user->primaryContact(ContactType::Url);          // ?Contact — enum or string kind
$user->contactsOfType('phone');                   // ordered EloquentCollection<Contact>

$user->contactBook();                             // the ContactBook — same as Contacts::for($user)

$user->contacts;                                  // Collection — every contact
$user->contacts()->primary()->verified()->get();  // scopes chain on the relation
```

- contacts() — the MorphMany relation; every Contact scope chains on it.
- contactsOfType(ContactType|string) — one kind, ordered by position then id.
- primaryContact(ContactType|string) — the primary of a kind, or null.
- primaryEmail() / primaryPhone() — shortcuts for the two most common kinds.
- contactBook() — the owner’s ContactBook, the same object Contacts::for($user) returns.

## The Contact model

Each Contact exposes its kind, value, flags and metadata:

```php
$contact->type;          // ContactType::Email
$contact->kind;          // 'email' — the raw stored kind
$contact->value;         // 'a.person@example.com'
$contact->label;         // 'Work' — free-text label for this one contact
$contact->name;          // display name ('' when not set)
$contact->category;      // ?string
$contact->is_primary;    // bool
$contact->position;      // int — order within the kind
$contact->verified_at;   // ?Carbon
$contact->meta;          // Collection (JSON column)
$contact->owner;         // the owning model (MorphTo), or null

$contact->isVerified();  // bool
$contact->kindLabel();   // 'Email'
$contact->kindIcon();    // 'envelope'
```

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