dcemal / filament-jsvectormap
A FilamentPHP v5 form field and infolist entry for selecting and displaying countries on an interactive jsVectorMap.
Requires
- php: ^8.2
- filament/forms: ^5.0
- filament/infolists: ^5.0
- filament/support: ^5.0
- spatie/laravel-package-tools: ^1.16
- symfony/intl: ^7.0
Requires (Dev)
- orchestra/testbench: ^10.0 || ^11.0
- pestphp/pest: ^3.0
README
Pick countries on a clickable world map, right inside your Filament admin panel.
This is a plugin for FilamentPHP (the admin-panel toolkit for Laravel). It wraps the jsVectorMap JavaScript library (docs) so you get a nice interactive map without writing any JavaScript yourself.
You get two ready-made pieces:
MapSelect— a form field. Users click countries on the map (or search a list) to choose them. Selected countries light up.CountriesEntry— a read-only display for your "view" pages. Shows the chosen countries as flag badges, with an optional highlighted map.
What it looks like
Imagine a normal dropdown for picking countries — but above it there's a world map. Click a country on the map and it turns green and gets added to the list. Click it again to remove it. Type in the search box to filter. Pick a whole region (like "MENA" or "Latin America") in one tap. That's the field.
Before you start
You need a working Laravel app with Filament installed. If you don't have that yet, follow Filament's own guide first: https://filamentphp.com/docs/5.x/introduction/installation. Come back here once you can log in to your panel at /admin.
Requirements:
- PHP 8.2 or newer
- Filament v5
That's it — you do not need Node.js, npm, or any build step. The map's JavaScript is loaded automatically.
1. Install
Run this in your project folder:
composer require dcemal/filament-jsvectormap
Then publish the plugin's assets (its JavaScript and CSS) so the panel can use them:
php artisan filament:assets
Done. The plugin registers itself — there's nothing to add to a config file.
Tip: Run
php artisan filament:assetsagain any time you update the package.
2. Make somewhere to store the choices
A list of countries needs a column in your database. The map stores countries as two-letter codes (TR, BR, DZ, …).
If you're adding this to an existing table, create a migration:
php artisan make:migration add_target_countries_to_campaigns_table
Inside that migration, add a json column:
$table->json('target_countries')->nullable();
Run it:
php artisan migrate
Then, on your model (e.g. app/Models/Campaign.php), tell Laravel to treat that column as a list. This one line is important — without it the data won't save correctly:
protected $casts = [ 'target_countries' => 'array', ];
(If you only ever let people pick one country, use a plain string column instead and skip the cast.)
3. Add the map to your form
Open your Filament form (in a resource this is usually app/Filament/Resources/.../Schemas/...Form.php, or the form() method of the resource). Add the field:
use Dcemal\FilamentJsVectorMap\Forms\Components\MapSelect; MapSelect::make('target_countries') ->label('Target countries');
Reload the page — you'll see the map. That's the whole basic setup. 🎉
4. (Optional) Show it on a "view" page
If your resource has a read-only view page (an "infolist"), you can display the saved countries there:
use Dcemal\FilamentJsVectorMap\Infolists\Components\CountriesEntry; CountriesEntry::make('target_countries') ->label('Target countries') ->showMap(); // or ->hideMap() to show only the flag badges
Common options
All of these are optional — the field works fine with none of them. Chain the ones you want, like ->multiple()->height(400).
| What you want | Add this |
|---|---|
| Let people pick many countries (this is the default) | ->multiple() |
| Let people pick only one | ->single() |
| Show the list always open, not in a dropdown | ->inline() |
| Offer "select a whole region" buttons | ->regions(['MENA' => ['DZ','EG','SA','AE']]) |
| Only allow certain countries | ->only(['BR','MX','AR']) |
| Hide certain countries | ->except(['AQ']) |
| Show a country but stop it being picked | ->disabledCountries(['TR']) |
| Change the map height | ->height(400) |
| Change the "selected" colour | ->selectedColor('#16a34a') |
| Show the country name when hovering | ->tooltips() |
| Zoom in automatically on the chosen countries | ->focusOnSelection() |
| Show zoom +/- buttons | ->zoomButtons() |
| Country names in another language | ->locale('tr') |
A small, complete example:
MapSelect::make('target_countries') ->label('Target countries') ->multiple() ->regions([ 'MENA' => ['DZ','EG','MA','SA','AE','QA'], 'Latin America' => ['BR','MX','AR','CL','CO'], ]) ->selectedColor('#16a34a') ->tooltips() ->focusOnSelection();
Translating country names
Country names come from a built-in English list, and fall back to PHP's own translations for any language you set with ->locale(). To edit names or add a language, publish the files and edit the copies:
php artisan vendor:publish --tag="filament-jsvectormap-translations"
This creates files under lang/vendor/filament-jsvectormap/. Add a folder like tr/ for Turkish and edit countries.php / messages.php there.
Using a different map (advanced)
Out of the box, only the world map is available, because that's the only map the jsVectorMap library ships over the internet (CDN). If you need another map (a Mercator world, a single continent, or one country), download it from jsVectorMap's available maps and host it yourself.
Save the map files in your app's public/ folder, then point the field at them:
mkdir -p public/vendor/jsvectormap/maps # the core library: curl -o public/vendor/jsvectormap/jsvectormap.min.js https://cdn.jsdelivr.net/npm/jsvectormap@1.7.0/dist/jsvectormap.min.js curl -o public/vendor/jsvectormap/jsvectormap.min.css https://cdn.jsdelivr.net/npm/jsvectormap@1.7.0/dist/jsvectormap.min.css # the world map: curl -o public/vendor/jsvectormap/maps/world.js https://cdn.jsdelivr.net/npm/jsvectormap@1.7.0/dist/maps/world.js
MapSelect::make('target_countries')->assetsUrl('/vendor/jsvectormap');
The plugin looks for the map file at <assetsUrl>/maps/<map id>.js, using the exact name you pass to ->map(). One thing to watch: a few maps have an underscore in their id but a hyphen in their filename — for example the Mercator map id is world_merc, but its file is world-merc.js. Save it under the id you'll use:
curl -o public/vendor/jsvectormap/maps/world_merc.js https://cdn.jsdelivr.net/npm/jsvectormap@1.7.0/dist/maps/world-merc.js
MapSelect::make('target_countries')->map('world_merc')->assetsUrl('/vendor/jsvectormap');
Troubleshooting
The map is blank / nothing shows up.
First, make sure you ran php artisan filament:assets, then hard-refresh the browser (Ctrl/Cmd + Shift + R). If you changed ->map(...) to something other than world, that map isn't available from the CDN — see Using a different map above.
I see a raw label like filament-jsvectormap::messages.regions instead of words.
Run php artisan optimize:clear. If you published the translations earlier, your copy is older than the package — re-publish with --force, or add the missing lines to your file in lang/vendor/filament-jsvectormap/.
The chosen countries don't save.
Check two things: the column is a json column, and your model has the 'array' cast (see step 2). For single-select fields, use a string column and no cast.
Nothing changed after updating the package.
Run php artisan filament:assets and php artisan optimize:clear, then hard-refresh.
How it stores data
- Multiple countries → an array of codes, e.g.
["TR","BR","DZ"]. Use ajsoncolumn +'array'cast. - One country → a single code, e.g.
"BR". Use astringcolumn, no cast.
The CountriesEntry display understands both, so you don't have to configure it for either case.
Credits
- Built on jsVectorMap by Mustafa Omar — the JavaScript library that draws the map.
- A plugin for FilamentPHP.
License
MIT. Free to use in personal and commercial projects.