Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Phone Number Laravel Package

kayacekovic/phone-number

A Laravel package for working with phone numbers, offering parsing, validation, and consistent formatting so you can store and display numbers reliably across your application.

View on GitHub
Deep Wiki
Context7

Getting Started

1. Install and pin the version

composer require kayacekovic/phone-number

The last release was 2021-11-24, which predates Nova 4. Before writing any code, open composer.json in vendor/kayacekovic/phone-number/ and read the require block — it will tell you three things you need immediately:

  • Which Nova major version it was built against (laravel/nova: ^3.0 almost certainly).
  • Whether it depends on giggsey/libphonenumber-for-php (the standard formatting/validation engine) or does its own regex work.
  • Whether assets need publishing (php artisan vendor:publish / nova:publish).

If you are on Nova 4 or 5, don't assume it works. Create a throwaway resource, drop the field in, and load the form before committing to it — Nova's field API and asset pipeline changed enough between 3.x and 4.x that a stale field can render as a blank input with no error.

2. Read the field class first

Open src/PhoneNumber.php. It is almost certainly 100–200 lines. Look specifically for:

  • public function component() — the Vue component name.
  • prepareForStorage() / resolve() / fill() — where normalization happens.
  • public function rules() — whether it appends validation rules itself or expects you to pass them.
  • Any defaultRegion / country-code property.

That one file is the whole package. Anything not visible there isn't implemented.

3. First use case

The minimal happy path — a single phone column on a resource:

use Kayacekovic\PhoneNumber\PhoneNumber;

public function fields(NovaRequest $request)
{
    return [
        ID::make()->sortable(),
        Text::make('Name'),
        PhoneNumber::make('Phone', 'phone')
            ->rules('required', 'string', 'max:32'),
    ];
}

With a migration that stores E.164 (never the display format):

$table->string('phone', 32)->nullable()->index();

Set a default region (via config, or a ->defaultRegion('US') style call if the field exposes one) so a bare national number like (555) 123-4567 can be parsed at all. Without a region, libphonenumber cannot resolve a number that lacks a country code — that's the first failure you will hit.

Implementation Patterns

Normalize on write, format on read. The field should store E.164 (+15551234567) and render a human-readable form. Verify which it does by saving a record and inspecting the raw column — don't trust the detail view. If the package only formats for display and stores the raw input, add the normalization yourself in the model so it applies everywhere:

public function setPhoneAttribute($value): void
{
    $this->attributes['phone'] = filled($value)
        ? phone($value, $this->phone_country ?? 'US')->formatE164()
        : null;
}

Country code as a sibling field. Most Nova phone fields expect a separate country selector. If this package has one, wire them together so the region is passed to the parser:

Select::make('Country', 'phone_country')
    ->options(['US' => 'United States', 'GB' => 'United Kingdom'])
    ->default('US'),

PhoneNumber::make('Phone', 'phone')
    ->rules('required', 'string'),

Add a migration column for phone_country (2-char ISO) if you go this route; you will need it later to re-parse or reformat existing numbers.

Validate outside Nova too. This is the single most important pattern. A Nova field's rules only fire on Nova form submissions. The same number arrives via API, imports, artisan commands, and other Nova fields — none of which run your field's rules. Duplicate the rule set into a FormRequest and a model cast:

'phone' => ['nullable', 'string', function ($attr, $value, $fail) {
    try {
        PhoneNumberUtil::getInstance()->parse($value, 'US');
    } catch (\Throwable) {
        $fail('Enter a valid phone number.');
    }
}],

Use it for display-only in read-heavy resources. If the field's component is a text input with JS formatting, it's fine in forms but adds nothing on index/detail. There, Text::make('Phone')->displayUsing(fn ($v) => phone($v, 'US')->formatNational()) is lighter and won't break if the field's JS is incompatible.

Backfill before you enforce. Pointing the field at an existing phone column with years of inconsistent data means validation suddenly rejects records that were saving fine. Write a one-off command that parses everything, logs failures to a table, and only then add required rules:

User::whereNotNull('phone')->chunkById(500, function ($users) {
    foreach ($users as $user) {
        try { $user->update(['phone' => phone($user->phone, 'US')->formatE164()]); }
        catch (\Throwable $e) { Log::warning('unparseable', ['id' => $user->id, 'raw' => $user->phone]); }
    }
});

Extensions points worth knowing about. Nova fields typically expose ->resolveUsing(), ->fillUsing(), and ->displayUsing(). If the package's normalization is wrong for your region (e.g. you need national format stored for a legacy integration), override it at the field level rather than forking:

PhoneNumber::make('Phone')
    ->fillUsing(function ($request, $model, $attribute) {
        $model->{$attribute} = phone($request->input($attribute), 'GB')->formatE164();
    }),

Gotchas and Tips

Abandonment risk is the headline issue. Zero stars, zero dependents, last commit 2021. Treat this as vendored code you happen to install via Composer, not a dependency with a support contract. Pin the exact version in composer.json, and be prepared to copy src/ into app/Nova/Fields/ if a Nova upgrade breaks it — the field is small enough that this is a 20-minute job and probably the right call for anything long-lived.

Nova 4/5 compatibility is unverified. Symptoms of an incompatible field: form renders but the input has no JS behavior (no formatting, no country picker), or you get a Vue component not found console error. Check nova-components in the browser console first. If the component fails, confirm assets were published and re-run php artisan nova:publish. If it still fails, the component is written against the Nova 3 mixins and needs porting.

Null and empty string will throw. libphonenumber throws NumberParseException on null and ''. If the field doesn't guard, a nullable phone column will 500 on save when left blank. Test explicitly: create a record with the phone field empty. If it breaks, wrap the parse in filled() or override fillUsing.

unique validation compares against the wrong string. If input is (555) 123-4567 and the stored value is +15551234567, unique:users,phone will pass when it should fail. Normalize in a prepareForValidation()/formattedRules hook before the unique check runs, or accept that uniqueness is only enforced post-normalization and add a DB unique index on the E.164 column.

Sorting and searching operate on the stored value, not the display. ->sortable() works fine (it hits the column), but Nova's index search will match +1555... and not the pretty format the user sees. That's usually correct — just don't be surprised when searching for (555) returns nothing.

max:32 is the right column width, not max:15. E.164 caps the digit count at 15, but once you account for +, optional separators if you store formatted values, and extensions (x1234), 32 is the safe varchar length. Sizing to 15 exactly will truncate on MySQL in non-strict mode and error in strict mode.

Debug order when a number won't save. (1) dd() inside the field's fillUsing/prepareForStorage to see what the parser receives. (2) Confirm a default region is set — a missing region is the cause of ~80% of "it says invalid but the number is fine" reports. (3) Test the same string directly against libphonenumber in tinker to isolate the field from the parser. (4) Only then look at the Vue side.

No tests means you own the regression suite. Add a feature test that posts a Nova resource update with three inputs — a valid international number, a valid national number, and garbage — and asserts the stored column. That's ~20 lines and it will catch every Nova upgrade breakage before your users do.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums
rlacerda83/query-parser
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings