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

Filament Phone Field Laravel Package

abanoubnassem/filament-phone-field

A Filament form field for phone numbers with country code selection, formatting, and validation. Drop it into any form to capture international numbers cleanly, with a searchable country picker and sensible defaults.

View on GitHub
Deep Wiki
Context7

Getting Started

1. Install and confirm the Filament major version it targets.

composer require abanoubnassem/filament-phone-field

The package is a thin wrapper around intl-tel-input, so it ships JS/CSS assets. If the README lists a publish tag, run it; otherwise php artisan filament:assets after install. Pin the version in composer.json — with ~21 stars and a single 2025-02-08 release, you do not want a surprise minor bump mid-project.

2. Where to look first, in this order:

  • vendor/abanoubnassem/filament-phone-field/README.md — the authoritative list of fluent methods; they map almost 1:1 to intl-tel-input options.
  • vendor/abanoubnassem/filament-phone-field/src/Forms/Components/ — the field class itself. It is a few hundred lines; read it once and you will know exactly which options are exposed and which you must pass through manually.
  • vendor/abanoubnassem/filament-phone-field/resources/ — the Blade view and the Alpine/JS initialiser. This is where you go when the widget misbehaves on a Livewire re-render.

3. First use case: swap a plain text input for a real phone field.

use Abanoubnassem\FilamentPhoneField\Forms\Components\PhoneInput;

public static function form(Form $form): Form
{
    return $form->schema([
        PhoneInput::make('phone')
            ->label('Phone number')
            ->required()
            ->defaultCountry('EG')
            ->preferredCountries(['EG', 'SA', 'AE']),
    ]);
}

Load the form, type a number, and confirm the flag dropdown appears and the field formats as you type. That is the whole "hello world" — the rest of this package is configuration of that widget plus the persistence decisions you make around it.

Verify the exact FQCN and method names against the README before pasting into a codebase. The component is exposed in the Forms\Components namespace, but option names follow intl-tel-input (defaultCountry, preferredCountries, onlyCountries, separateDialCode, …) and casing has shifted between package versions.

Implementation Patterns

Normalise to E.164 on save — do not store what the widget shows. The input's visible value depends on separateDialCode and formatOnDisplay, so it can be (201) 555-0123 or +20 100 123 4567 depending on config and user interaction. Strip the formatting at the boundary:

// In the Page class
protected function mutateFormDataBeforeCreate(array $data): array
{
    $data['phone'] = $this->normalisePhone($data['phone']);
    return $data;
}

Use giggsey/libphonenumber-for-php directly, or propaganistas/laravel-phone which wraps it:

use Propaganistas\LaravelPhone\PhoneNumber;

$e164 = (new PhoneNumber($raw, $countryCode))->formatE164();

If dial-code entry is split from the national number by separateDialCode, you must reassemble before parsing — a bare 01001234567 cannot be resolved without the selected country. This is the single most common data-corruption bug with this package.

Always mirror validation server-side. intl-tel-input validation is browser-only and trivially bypassed. Pair the field with a real rule:

PhoneInput::make('phone')
    ->required()
    ->rule('phone:INTERNATIONAL,AUTO')   // propaganistas/laravel-phone
    ->unique(ignoreRecord: true),

Client-side isValidNumber() is a UX nicety (instant feedback); the Laravel rule is the actual guarantee.

Build a shared field factory. Rather than repeating eight fluent calls in every resource, expose one method on a base resource or a small helper:

public static function phoneField(string $name = 'phone'): PhoneInput
{
    return PhoneInput::make($name)
        ->defaultCountry('EG')
        ->preferredCountries(['EG', 'SA', 'AE'])
        ->onlyCountries(config('app.supported_countries'));
}

This is also the seam where you later swap the package out if it goes unmaintained — one class instead of forty resources.

Read-side display. The widget is a form component. For tables and infolists, format the stored E.164 yourself with PhoneNumber::formatInternational(), or use a TextColumn with ->formatStateUsing(). Do not try to render the interactive widget in a table.

Country detection is opt-in, not automatic. initialCountry: 'auto' (auto-detect via IP) triggers an external geo-IP lookup. Enable it deliberately: it costs a request on every form load, leaks user IP to a third party (GDPR review needed), and fails silently to a fallback country behind a corporate VPN. A configured defaultCountry is the safer default for business apps.

Repeaters and modals. When the field lives inside a Repeater, Builder, or modal action, verify the widget initialises on the n-th added row. If it does not, the Alpine initialiser is running before the node exists — the fix is usually a wire:ignore wrapper around the field in a custom view, or an x-init that re-runs on livewire:navigated.

Gotchas and Tips

Livewire morphing destroys the widget. intl-tel-input replaces the <input> with its own DOM structure. Every Livewire re-render of the form (a dependent ->live() field, a ->reactive() sibling) can leave a dead, unformatted input behind. Symptoms: flag disappears, dropdown stops opening, validation stops firing. Wrap the field container in wire:ignore where practical, and test every form that has other reactive fields alongside the phone field — this is the bug you will hit.

Async utils script = false negatives. Formatting and isValidNumber() depend on the utils script loading after the core plugin. On a slow connection a user can submit before it is ready, so a "valid-looking" number fails locally while passing or failing inconsistently. Never depend on the client result; let the server rule be authoritative and keep ->required() independent of the widget's internal validity state.

separateDialCode changes what your backend receives. With it on, the stored string excludes the dial code and the country lives only in JS state. Unless you explicitly capture the selected country into a hidden field or a second column, you cannot reconstruct the number later. Either leave separateDialCode off, or add a companion country field.

CSS and Tailwind Preflight fight the plugin. Filament's reset strips list styling and default borders, which can flatten the flag dropdown. Also check z-index inside modals — the country list frequently renders behind the modal footer. Fix with a scoped stylesheet rather than editing vendor files.

Dark mode. Flag sprites are PNGs; they look fine, but the search box and dropdown background need explicit dark variants. Budget ten minutes for this rather than discovering it in a demo.

Debugging checklist, in order: browser console for intl-tel-input errors → confirm assets are present in public/ (php artisan filament:assets) → view-source the rendered field to confirm the init script is present → check whether the field is inside a wire:ignore-less reactive container.

Extension points. The escape hatches are ->extraInputAttributes() / ->extraAlpineAttributes() for passing raw intl-tel-input options through, and overriding the package view (php artisan vendor:publish then ->view('your.custom.view')) when you need different markup. Anything deeper means forking — which, given the maintenance profile, is a reasonable and cheap option.

Maintenance risk is the real gotcha. 21 stars, one release, no established issue backlog. It is MIT and small, so vendoring it or copying the ~200-line component into your app is a legitimate strategy if the field is load-bearing. At minimum: pin exact versions, add a smoke test that submits a phone number through the Livewire form, and know your fallback (a plain TextInput + propaganistas/laravel-phone gets you 80% of the value with zero third-party UI risk).

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.
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib