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

Any stored kind outside the six built-ins — for example a whatsapp kind registered in config — types as Custom and takes its label, icon and rules from contacts.types.<kind>:

// config/contacts.php
'types' => [
    'whatsapp' => [
        'label' => 'WhatsApp',
        'icon' => 'chat-bubble',
        'rules' => ['required', 'string', 'regex:/^\+?[1-9]\d{6,14}$/'],
    ],
],
use RoundlyConsulting\Contacts\DataTransferObjects\ContactData;
use RoundlyConsulting\Contacts\Models\Contact;

$contact = $user->addContact(ContactData::fromArray([
    'type' => 'whatsapp',
    'value' => '+421900123456',
    'name' => 'Support',
]));

$contact->kind;         // 'whatsapp'  — the raw kind, as stored
$contact->type;         // ContactType::Custom
$contact->kindLabel();  // 'WhatsApp'
$contact->kindIcon();   // 'chat-bubble'

Contact::query()->ofType('whatsapp')->get();   // this kind only

The raw kind is kept alongside the type, so the registered label, icon and validation rules all resolve — the regex above rejects a bad value on addContact(). $contact->kind is what the registry is keyed by; $contact->type stays a ContactType for typing and is Custom for any kind outside the six.

Kind versus label

kindLabel() is the kind’s display name, distinct from $contact->label — your free-text label for one particular contact (for example Work). An unregistered kind is preserved but falls back to Custom’s label (Other) and icon (identification).

Independent kinds

Registered kinds are independent: each keeps its own primary contact and its own position sequence, and ofType('whatsapp') scopes to that kind alone — a new whatsapp primary never demotes a telegram one.

Adding and validating custom kinds

Every entry point keeps the raw kind: the fluent builder’s type('whatsapp'), ContactData::fromArray() with the kind as type, or the constructor’s kind argument. Pass the kind to ValidContactValue as well, or the registered rules never run:

use RoundlyConsulting\Contacts\DataTransferObjects\ContactData;
use RoundlyConsulting\Contacts\Enums\ContactType;
use RoundlyConsulting\Contacts\Facades\Contacts;
use RoundlyConsulting\Contacts\Rules\ValidContactValue;
use RoundlyConsulting\Contacts\Support\ContactRules;

// The fluent builder keeps the raw kind
Contacts::for($user)->type('whatsapp')->value('+421900123456')->add();

// The constructor form — pin the raw kind next to the Custom type
$user->addContact(new ContactData(ContactType::Custom, '+421900123456', kind: 'whatsapp'));

// Validate a form field against the registered kind's rules
$request->validate([
    'whatsapp' => ['required', new ValidContactValue(ContactType::Custom, 'whatsapp')],
]);

// ...or take the ready rule array for the kind
$request->validate([
    'whatsapp' => ContactRules::forValue(ContactType::Custom, 'whatsapp'),
]);

Overriding built-in kinds

The same registry overrides the six built-ins — set a label, icon or rules for email, phone, address, url, social or custom:

// config/contacts.php — override a built-in kind's label, icon or rules
'types' => [
    'url' => ['label' => 'Homepage', 'icon' => 'link'],
],

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.