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 Input Laravel Package

ysfkaya/filament-phone-input

A Filament 3 form field for international phone numbers, with country code selection, built-in validation, default country support, and configurable display and input formatting. Requires PHP 8.1+ and integrates directly into Filament forms.

View on GitHub
Deep Wiki
Context7

Getting Started

Install in three commands, then check the branch matches your Filament major.

composer require ysfkaya/filament-phone-input
php artisan filament:assets
php artisan filament-phone-input:install

The filament-phone-input:install command publishes the package's config/translations and (alongside filament:assets) the compiled intl-tel-input JS/CSS into public/. Both must run on every environment that renders the panel — locally, in CI, and in your deploy script. If you skip them the field degrades to a bare text input with no flags, dropdown, or formatting, and there is no error message telling you why.

Before writing any code, confirm which major you installed. The README carries a note that the 3.x branch is what you want for Filament 3 panels, while main targets the newer major. Since the package is a thin wrapper over Filament form/table/infolist components, a mismatched major fails loudly at class-resolution time — check composer show ysfkaya/filament-phone-input against your filament/filament constraint first, not after debugging a blank field.

Where to look first: the "Quick Preview" block in the README is effectively the complete public API — every fluent method on the component in one list. Skim it once, then treat it as your reference. The Troubleshooting section (one entry, about NumberParseException) and the Upgrade section are the other two places worth reading before you start, because both describe failure modes you will hit on real data.

First use case — swap your existing TextInput: this is a one-line change in a Filament resource and immediately gives you country dropdown, per-country masking, and E.164 normalization on save.

// Form
PhoneInput::make('phone')->required(),

// Table
PhoneColumn::make('phone')->displayFormat(PhoneInputNumberType::NATIONAL),

// Infolist
PhoneEntry::make('phone')->displayFormat(PhoneInputNumberType::NATIONAL),

Sanity-check the first record you save: open the DB row. The default inputNumberFormat is E164, so 0555 123 45 67 typed in a Turkish locale lands as +905551234567. Confirm that before you build anything downstream — every other decision (uniqueness, search, API payloads, SMS dispatch) depends on the stored shape.

Worth knowing up front: this package includes propaganistas/laravel-phone as a dependency, so you already have PhoneNumber casting and validation available on the model side without a second composer require.

Implementation Patterns

The three-surface triple. The package ships form, table, and infolist components that share one formatting enum. Use all three together so a number looks identical in the edit form, the index table, and the view page. PhoneInputNumberType::{E164, INTERNATIONAL, NATIONAL, RFC3966} is the same enum everywhere, which is the main reason to reach for PhoneColumn/PhoneEntry instead of a plain TextColumn with a format string.

Separate storage of dial code — the decision you'll make most often. Use countryStatePath('phone_country') when a downstream consumer needs the country independently of the number: SMS gateways that bill per-country, routing logic, or a legacy schema with a phone_country column. Pair it with ->countryColumn('phone_country') on the column and entry, otherwise the display component has no idea which country to parse against and falls back to guessing.

PhoneInput::make('phone')->countryStatePath('phone_country'),
PhoneColumn::make('phone')->countryColumn('phone_country'),
PhoneEntry::make('phone')->countryColumn('phone_country'),

If nothing downstream needs the split, don't split it. E.164 in a single column is unambiguous, indexes well, and makes ->unique() and ->searchable() behave.

Validation belongs on the field, not the model. validateFor(country: 'AUTO', type: libPhoneNumberType::MOBILE) gives you libphonenumber-grade validation in the form layer, which produces field-level error messages instead of 500s. Use the explicit array form (['US', 'GB']) when you know your markets — 'AUTO' is convenient but lets a user submit a valid number for a country you don't operate in. The lenient: true flag is your escape hatch for numbers that are plausible but not strictly valid (test data, ported legacy rows).

Set every format explicitly in one base class. inputNumberFormat (what's persisted), displayNumberFormat (read-only views), focusNumberFormat (what appears when the input is focused), and placeholderNumberType (the example number shown as placeholder) all default independently. Rather than repeat four fluent calls across thirty resources, subclass and set house defaults once:

class PhoneInput extends \Ysfkaya\FilamentPhoneInput\Forms\PhoneInput
{
    protected function setUp(): void
    {
        parent::setUp();
        $this->defaultCountry(config('app.phone_country', 'US'))
            ->inputNumberFormat(PhoneInputNumberType::E164)
            ->displayNumberFormat(PhoneInputNumberType::NATIONAL)
            ->focusNumberFormat(PhoneInputNumberType::E164);
    }
}

This also gives you one place to swap the package out later — which matters, given it's a 329-star single-maintainer package with no dependents driving its stability.

Constrain the country list per market. onlyCountries(['tr','us','gb']) for a single-market panel, excludeCountries([...]) for a global one that just needs to hide sanctioned/unsupported regions, countryOrder(['tr']) to pin your home market to the top of a long list, and countrySearch() once the list gets long enough to scroll. These accept closures, so a market selected elsewhere in the form can drive them.

Geo IP lookup — keep it, but make it yours. The component does a geo lookup on mount by default to guess the initial country. That's an outbound network call on every form render. For anything at scale, supply ipLookup() with a cached/header-derived resolution, and disableLookup() in local dev and test suites where the call will time out or leak IPs to a third party.

customOptions() is the escape valve. The wrapper doesn't cover every intl-tel-input option. Rather than fork, pass the raw option through customOptions(['foo' => true]) and let the JS layer handle it.

Gotchas and Tips

  • NumberParseException / "Number requires a country to be specified" is almost always legacy data with no area code, not a bug. Set ->defaultCountry('US') and the parser has something to work with. The README is honest that there's no way to infer a country from a bare digits string — plan a backfill for existing rows.
  • Validation needs a translation key. validateFor() emits Laravel-Phone's messages, which expect an entry in lang/*/validation.php. Without it users see a raw translation key. Add it in the same PR as the validation call.
  • showFlags(false) silently misbehaves unless combined with separateDialCode or disallowDropdown — the flag is what communicates the selected country when there's a dropdown. The README warns about this; it's easy to miss in review.
  • E.164 in, NATIONAL out is not a bug. The default pair (inputNumberFormat E164, displayNumberFormat NATIONAL) looks inconsistent until you remember one is storage and one is presentation. Do not "fix" it by switching input to NATIONAL: you'll lose unambiguous storage, break ->unique(), and make every API payload locale-dependent.
  • Country code casing will bite you. intl-tel-input speaks lowercase ISO2 (tr), propaganistas/laravel-phone expects uppercase (TR). Normalize at every boundary — validation, PhoneNumber casts, queue jobs that re-parse the stored value. Verify what countryStatePath actually writes in your version rather than assuming.
  • Reactive closures and a JS widget don't always cooperate. Changing onlyCountries/initialCountry from $get() works on initial mount, but intl-tel-input initializes once; a Livewire re-render may not re-init the dropdown. If you need the widget to rebuild, force it with ->key() on the component. Test this in the browser, not in a Livewire test — assertions on fillForm() bypass the JS entirely.
  • Docs drift in the README itself. The Quick Preview lists containerClass(...), while the Usage section documents customContainer(...). Check the method's actual presence in the trait for your installed version before copying either name into a base class.
  • Debugging an unformatted input: first check public/ for the published assets and re-run php artisan filament:assets after any upgrade; second, open the browser console for intl-tel-input initialization errors; third, inspect the hidden input the widget maintains alongside the visible one — that hidden field is what actually reaches Livewire state, so Livewire devtools will show you the real submitted value.
  • Searching and sorting follow the stored string. PhoneColumn::displayFormat() only changes rendering. A searchable() column of E.164 values won't match a user typing 0555, and sortable() sorts lexicographically by + prefix. If phone search matters, plan a normalized search column or an accessor rather than relying on the displayed format.
  • Extension points to prefer: a base PhoneInput subclass (house defaults), a custom ipLookup closure (cached country resolution), and customOptions (raw JS options). Avoid editing published assets — they're regenerated by filament:assets and your changes disappear on the next deploy.
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.
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package