Skip to content
Devix Open Source

Guide

Models and queries

The cast

use Devix\Phone\Laravel\Casts\AsPhoneNumber;

protected function casts(): array
{
    return [
        'phone' => AsPhoneNumber::class,                   // country from phone_country, then config
        'mobile' => AsPhoneNumber::class.':AE',            // national input is always AE
        'office' => AsPhoneNumber::using('office_country') // country from another attribute
    ];
}
  • Writing accepts a string or a PhoneNumber. Complete numbers are stored as E.164 (+971501234567), with ;ext=12 when there is an extension.
  • Reading returns a PhoneNumber (or null).
  • Input that isn't a number is stored exactly as typed, so nothing is lost. Validate first to keep the column clean.

A varchar(32) column is enough for E.164 plus an extension.

The trait

use Devix\Phone\Laravel\Concerns\HasPhoneNumbers;

class Customer extends Model
{
    use HasPhoneNumbers;
}

Attribute order stops mattering. Set the number before its country and the model still stores E.164, because numbers are normalised again when it saves:

$customer->phone = '0300 1234567';
$customer->phone_country = 'PK';
$customer->save(); // phone = +923001234567

Search however it was typed:

Customer::wherePhone('phone', '0300-1234567', 'PK')->get();
Customer::wherePhone('phone', '+92 300 1234567')->get();
Customer::wherePhone('phone', $request->phone('phone'))->get();

Input that isn't a number matches nothing, rather than everything.

Unique numbers

Because every number is stored the same way, Laravel's own unique rule works — validate against the E.164 form:

$e164 = $request->phone('phone')?->e164();

Validator::make(['phone' => $e164], ['phone' => Rule::unique('users', 'phone')->ignore($user)]);

API resources and Inertia

PhoneNumber is JsonSerializable:

{ "e164": "+971501234567", "international": "+971 50 123 4567", "national": "050 123 4567",
  "country": "AE", "type": "mobile", "valid": true, "reason": "valid" }
Updated 12 Sep 2026