Database schema & key types
One polymorphic table backs every contact of every owner. The table name comes from contacts.table:
| Column | Type | Purpose |
|---|---|---|
id | bigint | Auto-incrementing key. |
owner_type / owner_id | nullable morph | Polymorphic owner; owner_id follows key_type. Null for ownerless contacts. |
type | string, default custom, indexed | The raw kind — a built-in value or a registered custom kind. |
name | string | Display name ('' when not set). |
value | nullable string | The normalized contact value, at most 255 characters. |
label | nullable string | Free-text label, e.g. Work. |
category | nullable string, indexed | Grouping, e.g. Suppliers. |
is_primary | boolean, default false | Primary within owner + kind. |
position | unsigned int, default 0 | Order within the kind. |
verified_at | nullable timestamp | When the contact was verified. |
verification_token | nullable string | Hash of the pending token — hidden from arrays and JSON. |
verification_expires_at | nullable timestamp | Token expiry. |
verification_attempts | unsigned smallint, default 0 | Wrong guesses spent on the pending token; reset with every new token. |
meta | nullable jsonb | Arbitrary metadata, cast to a Collection. |
created_at / updated_at | timestamps | Standard timestamps. |
deleted_at | soft deletes | Deleted 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=uuidUnset 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 cryptoBy 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.