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

Nova Phone Field Laravel Package

mlsolutions/nova-phone-field

A Laravel Nova phone field for formatting and validating phone numbers in your admin panel. Adds a ready-made Nova field type so you can collect, display, and validate phone input cleanly without writing custom field logic.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and confirm it actually loaded

composer require mlsolutions/nova-phone-field

There is nothing to publish in the normal case — Laravel package discovery registers the field's provider, which pushes the compiled JS/CSS into Nova via Nova::script() / Nova::style(). Check the three things that break in order:

  1. The provider is registered (Laravel 11+ bootstrap/providers.php, older config/app.php) and listed under extra.laravel.providers in composer.json.
  2. dist/js/field.js (or equivalent) ships in the package. If the build artefacts are gitignored instead of committed, you cannot use this without building it yourself — check before you promise anyone a delivery date.
  3. php artisan nova:publish followed by a hard refresh. Nova caches its compiled JS aggressively; stale assets look identical to "the field is broken."

Where to look first

  • README.md for the exact class name (PhoneNumber vs PhoneField) and constructor signature. Namespace is MlSolutions\NovaPhoneField.
  • resources/js/field.js and the Vue components — this is the real documentation. The country-code → mask lookup table lives here. It tells you precisely which countries are supported and how the mask swaps.
  • LICENSE. Packagist reports NOASSERTION, i.e. no declared licence. Read it before this touches a client project.

First use case — replace one Text field

use MlSolutions\NovaPhoneField\PhoneNumber;

public function fields(Request $request)
{
    return [
        ID::make()->sortable(),
        Text::make('Name'),
        PhoneNumber::make('Phone'),
    ];
}

Create a record typing +55 11 91234-5678, then another typing +1 (415) 555-0132, and watch the mask change with the country code. That single round-trip answers the two questions that matter: does the mask re-initialise correctly from a stored value, and what string actually lands in the phone column. Inspect the database row directly — do not trust what the form renders back to you.

Implementation Patterns

Centralise the field definition. Phone fields multiply across User, Lead, Company, Contact. Wrap them once:

// app/Nova/Concerns/HasPhoneFields.php
protected function phone(string $name, string $attribute = 'phone'): PhoneNumber
{
    return PhoneNumber::make($name, $attribute)
        ->rules(['nullable', 'string', 'max:32'])
        ->help('Include the country code, e.g. +55 11 91234-5678');
}

Then $this->phone('Phone'), $this->phone('Mobile', 'mobile'). When the package needs swapping out, you edit one method — and with a zero-star, single-maintainer dependency, that swap is a realistic scenario.

Normalise on the way in, format on the way out. The mask is a browser-side convenience only; treat the posted value as untrusted. Store E.164, display formatted:

PhoneNumber::make('Phone')
    ->fillUsing(function ($request, $model, $attribute) {
        $model->{$attribute} = $this->normalisePhone($request->input($attribute));
    })
    ->displayUsing(fn ($value) => phone_format($value));

Every downstream consumer — Twilio, WhatsApp deep links, tel: anchors, CSV exports — wants +5511912345678, never the masked string. E.164 in the column also keeps search and de-duplication sane.

Pair with server-side validation, always. A mask constrains typing, not submissions. Anything reachable by curl needs a PHP rule. Use giggsey/libphonenumber-for-php (or a wrapper) rather than a hand-rolled regex:

->rules(['nullable', 'phone:AUTO,BR,US'])

Column type: string('phone', 32). Enough for E.164 plus an extension, and never an integer column — the leading + and national leading zeros get destroyed.

Be deliberate per view. The mask only runs on the form view. On index/detail you get the raw column unless you add displayUsing(). Raw E.164 is fine for dense index tables; formatted is better on detail pages.

Multiple phone fields per form (mobile + landline + fax) is common. Mask libraries that register a global singleton frequently break here — editing one field disturbs another. Test it explicitly. If it breaks, fall back to plain Text for the secondary numbers or instantiate with a unique key.

Filters and search need the same normaliser. If you build a Nova filter on the phone column, remember the column is E.164 while admins type formatted numbers. Run filter input through the identical helper used in fillUsing, or searches will silently miss.

Keep a fallback path open. A plain Text field plus a small mask in your own nova-components package is a couple of hours of work. Keeping all construction behind the phone() helper is what makes that swap cheap.

Gotchas and Tips

  • Paste is the weak spot. Dynamic-mask widgets often only re-evaluate the mask on keydown. Pasting +44 20 7946 0958 into an empty field can leave the Brazilian mask active and silently truncate the value. Test pasting a full international number into an empty field, and pasting a partial one.
  • Editing an existing record. If the mask isn't re-derived from the stored value on mount, opening a record and changing one digit mangles the number on save. Test: open an existing record, change one digit, save, inspect the column.
  • The mask is never stored. Strip non-digits server-side (preserving a leading +). Never assume the posted value matches what the user saw.
  • Country coverage is hardcoded. The table in resources/js/field.js is finite. Unsupported country codes typically fall through to a generic mask or none. Grep that table for the countries you actually serve before rolling out to an international customer base.
  • Version pinning is mandatory. Zero stars, zero dependents, one maintainer, NOASSERTION licence. Pin an exact version ("mlsolutions/nova-phone-field": "1.0.3") rather than ^. NOASSERTION is a genuine blocker for commercial or redistributed software — get the licence clarified in writing, vendor only the mask logic, or treat it as internal-only tooling.
  • No published test suite. Assume nothing about Nova 4 vs Nova 5 compatibility — the two majors changed asset and component conventions, and a 2025 release may target only the newer one. Smoke-test on the exact Nova major you run.
  • Extension points are thin. Expect defaultCountry(), onlyCountries(), maybe mask(). Anything more specific — per-country validation, carrier lookup, canonicalisation — belongs in your own helper on the model, not in the field.
  • Accessibility and browser compatibility are yours to verify. Assume no a11y audit and no cross-browser test matrix exists.
  • Debugging checklist when a number "doesn't save right", in order: what does the network request post → what does fillUsing/the mutator produce → what is in the column → what does displayUsing render. Ninety percent of issues are a mismatch between step 1 and step 3.
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.
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
frankyso/ipaymu
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core