Before installing anything, check your Nova version. This package's last release was 2021-03-26, which puts it in the Nova 3 / Vue 2 era. If you're on Nova 4 or 5, assume the compiled field component will not render until proven otherwise — run the install on a throwaway branch first.
composer require tapp/select-country-code
The package is tiny and has no dependents, so treat the source as the documentation:
vendor/tapp/select-country-code/composer.json and confirm the actual PSR-4 namespace (e.g. Tapp\SelectCountryCode\) and the service provider.src/ — there are usually only one or two classes: the Nova Field subclass and possibly a config/options source.dist/ for prebuilt JS. If it's absent or stale, you'll need to run npm install && npm run prod inside the vendor directory (or fork it).php artisan vendor:publish --provider="Tapp\..." and inspect the country list it exposes.First use case — a country code column on an address or user profile:
use Tapp\SelectCountryCode\SelectCountryCode; // verify from composer.json
public function fields(Request $request)
{
return [
SelectCountryCode::make('Country Code')
->rules('required', 'size:2')
->default('US'),
];
}
Pair it with a migration that stores the code compactly:
$table->char('country_code', 2)->nullable()->index();
Decide up front whether the field stores ISO 3166-1 alpha-2 (US, GB) or a dial code (+1, +44). The two are not interchangeable, and several countries share a dial code. Inspect the option values in the select before writing the migration.
Keep the field dumb, validate on the server. The dropdown is a UI convenience, not a constraint. Add an explicit rule so a crafted request can't store junk:
use Illuminate\Validation\Rule;
SelectCountryCode::make('Country Code')
->rules([
'required',
Rule::in(array_keys(config('countries'))), // or the package's own list
])
->required()
->hideFromIndex(),
Display names, store codes. Nova will render the raw code (US) in index/detail views. Resolve it to a human-readable name with league/iso3166 or symfony/intl rather than duplicating a country map:
SelectCountryCode::make('Country Code')
->displayUsing(fn ($value) => $value
? (new \League\ISO3166\ISO3166)->alpha2($value)['name']
: '—')
->onlyOnForms(),
Text::make('Country', fn () => $this->country_code
? (new \League\ISO3166\ISO3166)->alpha2($this->country_code)['name']
: '—'
)->onlyOnIndex(),
Compose with a phone field. The most common real-world pattern is a two-column pair: country code + national number. Store them separately and concatenate at the boundary (SMS, exports, API responses) through an accessor so you never rebuild the logic in three places:
// app/Models/User.php
public function getE164PhoneAttribute(): ?string
{
return $this->phone
? '+' . ltrim($this->dial_code, '+') . ltrim($this->phone, '0')
: null;
}
If you need E.164 specifically, prefer a library such as giggsey/libphonenumber-for-php for parsing/formatting rather than string concatenation.
Seeders, imports and API payloads. Normalize incoming codes to uppercase alpha-2 at the model boundary (mutator or form request prepareForValidation), otherwise us, US and U.S. all end up in the column and every where('country_code', 'US') silently misses.
Filtering. For Nova index filters, whereIn on the stored column is enough; you don't need the field for filtering:
public function apply(Request $request, $query, $value)
{
return $query->where('country_code', $value);
}
Reuse outside Nova. If the package exposes its country array via config or a static method, pull it into your own config/countries.php and use the same list for API validation, CSV import mapping, and Blade forms. If it doesn't expose anything, that's your signal to copy the list out and decouple.
Wrap it. Because maintenance is a risk, don't reference the vendor class in 30 resources. Create one App\Nova\Fields\CountryCode extends SelectCountryCode (or a static factory) so a future swap to Nova's built-in Select is a one-file change:
namespace App\Nova\Fields;
class CountryCode extends \Tapp\SelectCountryCode\SelectCountryCode
{
public function __construct($name, $attribute = null, $resolveCallback = null)
{
parent::__construct($name, $attribute ?? 'country_code', $resolveCallback);
$this->rules('required', 'size:2');
$this->default('US');
}
}
Compatibility is the biggest gotcha. A 2021 release predates Nova 4's Vue 3 rewrite. Symptoms of a mismatch: the field renders as a plain text input, the select is empty, or the browser console throws a Vue component registration error. Check the Nova JS console and the network response for the field's options before debugging your own code.
Compiled assets vs. source. If the select shows no options, verify the JSON payload first (Nova field metadata), then the rendering. If metadata is populated but the UI is blank, it's an asset problem — run php artisan nova:publish, clear public/vendor, and check the package's dist/ folder is actually present in vendor/.
Missing npm run prod is common in low-traffic packages. If dist/ is empty, the package expects you to build it. That's a strong argument for forking the repo into your own VCS and committing the built assets, rather than depending on a Packagist tarball forever.
Dial code vs. ISO code confusion. +1 covers US, CA, and a dozen Caribbean nations. If the package's option values are dial codes, you cannot derive a country from them — store both, or store only the ISO code and resolve the dial code with libphonenumber.
Don't trust the option list as validation. Whether the list is hardcoded, config-driven, or fetched, always validate server-side with Rule::in against the same source.
Column type and indexing. char(2) (or varchar(2)) with an index is right for alpha-2. Avoid enum — country lists change (Kosovo, newer ISO assignments) and altering the enum is a migration trap. If you store dial codes, use varchar(6) to accommodate +1-684-style values.
Null vs. empty string. Nova selects often submit "" for "no selection". Add a mutator to coerce to null, or nullable() plus 'nullable' in rules, otherwise whereNull('country_code') queries break.
Extension points are thin. With no dependents and 5 stars, expect no published contracts, no events, and no tests. Realistic options: (a) subclass and override the options source, (b) publish/copy the config and stop using the field's internals, or (c) swap to Nova's built-in Select:
Select::make('Country Code')
->options(collect((new \League\ISO3166\ISO3166)->all())
->mapWithKeys(fn ($c) => [$c['alpha2'] => $c['name']])
->sort()
->all())
->searchable()
->rules('required', 'size:2'),
That three-line replacement covers roughly 90% of what this package does and removes the maintenance question entirely. Evaluate honestly whether the field earns its keep before wiring it into many resources.
Debugging checklist. composer show tapp/select-country-code (installed version and requirements) → vendor/tapp/select-country-code/ (namespace, service provider, dist/) → Nova field metadata in the browser network tab → storage/logs/laravel.log for provider boot errors → php artisan nova:field sanity check with a built-in field to isolate whether the issue is the package or your resource.
How can I help you explore Laravel packages today?