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

Database schema & key types

One polymorphic table backs every contact of every owner. The table name comes from contacts.table:

ColumnTypePurpose
idbigintAuto-incrementing key.
owner_type / owner_idnullable morphPolymorphic owner; owner_id follows key_type. Null for ownerless contacts.
typestring, default custom, indexedThe raw kind — a built-in value or a registered custom kind.
namestringDisplay name ('' when not set).
valuenullable stringThe normalized contact value, at most 255 characters.
labelnullable stringFree-text label, e.g. Work.
categorynullable string, indexedGrouping, e.g. Suppliers.
is_primaryboolean, default falsePrimary within owner + kind.
positionunsigned int, default 0Order within the kind.
verified_atnullable timestampWhen the contact was verified.
verification_tokennullable stringHash of the pending token — hidden from arrays and JSON.
verification_expires_atnullable timestampToken expiry.
verification_attemptsunsigned smallint, default 0Wrong guesses spent on the pending token; reset with every new token.
metanullable jsonbArbitrary metadata, cast to a Collection.
created_at / updated_attimestampsStandard timestamps.
deleted_atsoft deletesDeleted contacts stay retrievable.

Two composite indexes cover the hot paths: (owner_type, owner_id, type) for per-kind reads and (owner_type, owner_id, type, is_primary) for primary lookups.

Owner key type

The owner is an optional polymorphic relation, so owner_id must match your owner models’ keys. Set key_type to uuid or ulid when those models use HasUuids or HasUlids. It is read when the migration runs, so set it before you publish and migrate:

# set before you publish and run the migration
CONTACTS_KEY_TYPE=uuid

Unset or blank reads as bigint, which produces exactly the same columns as Laravel’s nullableMorphs(). Any other value throws InvalidConfigurationException when the migration runs, so a typo never silently builds bigint columns for UUID or ULID owners.

Soft deletes

Contacts use soft deletes, so a deleted contact stays retrievable via withTrashed(). A soft delete trashes the contact’s structured addresses and its relationship edges in both directions with it, and restore() brings back exactly those — not ones you had deleted separately before. A restored primary comes back as a secondary if its kind promoted another contact meanwhile. forceDelete() removes the addresses and edges for good.

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.