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 World Map Widget Laravel Package

infinityxtech/filament-world-map-widget

A Filament widget that adds an interactive world map to your admin panel for visualizing location-based data. Customize markers and colors, then drop it into any Laravel/Filament dashboard with minimal setup.

View on GitHub
Deep Wiki
Context7

Getting Started

Install

composer require infinityxtech/filament-world-map-widget

Then publish whatever the service provider exposes (assets are almost certainly required for the map JS bundle):

php artisan vendor:publish --tag="filament-world-map-widget-config"
php artisan vendor:publish --tag="filament-world-map-widget-assets"
# or, if only one tag exists:
php artisan vendor:publish --provider="InfinityXTech\FilamentWorldMapWidget\WorldMapWidgetServiceProvider"

Where to look first

  1. vendor/infinityxtech/filament-world-map-widget/README.md — the canonical usage example.
  2. config/filament-world-map-widget.php (after publishing) — map library, height, colour palette, default resolution.
  3. src/Widgets/ — find the base widget class; the getData() / getMapData() method signature is the entire contract you need.
  4. resources/views/ — check which JS map library is bundled (jsVectorMap vs. amCharts vs. Leaflet) before you plan styling work.

With 13 stars and a single maintainer, treat the source as the documentation. Budget 15 minutes to read src/ end to end before wiring it into anything customer-facing, and pin the version in composer.json ("infinityxtech/filament-world-map-widget": "^1.0" — avoid dev-main).

First use case: "Users by country"

The fastest useful win is aggregating an existing model into a country => count array.

// app/Providers/Filament/AdminPanelProvider.php
->widgets([
    \App\Filament\Widgets\UsersByCountryMap::class,
    // ...
])
// app/Filament/Widgets/UsersByCountryMap.php
namespace App\Filament\Widgets;

use InfinityXTech\FilamentWorldMapWidget\Widgets\WorldMapWidget;
use App\Models\User;

class UsersByCountryMap extends WorldMapWidget
{
    protected static ?string $heading = 'Users by Country';
    protected int|string|array $columnSpan = 'full';

    protected function getData(): array
    {
        return User::query()
            ->whereNotNull('country_code')
            ->selectRaw('country_code, COUNT(*) as total')
            ->groupBy('country_code')
            ->pluck('total', 'country_code')
            ->toArray();   // ['US' => 512, 'DE' => 88, ...]
    }
}

The single most common failure at this step is key format: most bundled map libraries expect ISO 3166-1 alpha-2 (US, GB, DE), not USA/UK. The map renders blank with no error if you get this wrong.

Implementation Patterns

Subclass per dataset, not per config

Prefer one widget class per metric. Avoid mutating a shared instance's state; Filament widgets are Livewire components and statics leak across renders.

class OrdersByCountryMap extends WorldMapWidget
{
    protected static ?string $heading = 'Orders (30d)';
    protected static ?string $pollingInterval = '60s';

    protected function getData(): array
    {
        return Order::query()
            ->where('created_at', '>=', now()->subDays(30))
            ->selectRaw('country_code, COUNT(*) as total')
            ->groupBy('country_code')
            ->pluck('total', 'country_code')
            ->toArray();
    }
}

Header/footer widgets on resource pages

Put the map on a page rather than the dashboard when it's contextual:

// app/Filament/Resources/UserResource/Pages/ListUsers.php
protected function getHeaderWidgets(): array
{
    return [
        \App\Filament\Widgets\UsersByCountryMap::class,
    ];
}

Respect dashboard filters

If you expose a date picker or tenant selector, read it through Filament's page filters rather than inventing widget properties:

use Filament\Widgets\Concerns\InteractsWithPageFilters;

class OrdersByCountryMap extends WorldMapWidget
{
    use InteractsWithPageFilters;

    protected function getData(): array
    {
        $from = $this->filters['from'] ?? now()->subMonth();
        $to   = $this->filters['to']   ?? now();

        return Order::query()
            ->whereBetween('created_at', [$from, $to])
            ->selectRaw('country_code, COUNT(*) as total')
            ->groupBy('country_code')
            ->pluck('total', 'country_code')
            ->toArray();
    }
}

Cache the aggregate, not the widget

Map JS re-initialisation is cheap; the GROUP BY over millions of rows is not. Cache the array:

protected function getData(): array
{
    return cache()->remember(
        'users-by-country',
        now()->addMinutes(10),
        fn () => User::query()
            ->selectRaw('country_code, COUNT(*) as total')
            ->groupBy('country_code')
            ->pluck('total', 'country_code')
            ->toArray()
    );
}

If you cache and also poll, clear the cache key from a model observer so the map isn't perpetually 10 minutes stale.

Drill-down integration

Pair the map with a table widget on the same page so clicking a country can filter it. Since the package likely doesn't emit a selection event out of the box, the reliable pattern is a URL-driven filter:

// in the map widget, if a click hook exists or via a custom view:
protected function getData(): array
{
    // highlight the country selected in the query string
}

Check resources/views/ for a dispatched Livewire event before building a custom click handler — if none exists, use the query-string approach and a Link column in the companion table.

Colour scaling

Most of these packages expose a single-colour or gradient mode via config. For "choropleth" style maps, normalise your values yourself rather than relying on auto-scaling — a single outlier (US: 100000 against DE: 12) flattens the whole gradient to one shade. Consider log() scaling or capping at the 95th percentile.

Gotchas and Tips

Country code mismatch is the #1 silent failure. Add a normaliser and assert coverage:

$map = ['UK' => 'GB', 'USA' => 'US', 'GER' => 'DE'];
$data = collect($data)->mapWithKeys(
    fn ($v, $k) => [$map[$k] ?? strtoupper($k) => $v]
)->toArray();

Log any key that isn't in the map library's feature list — otherwise the country just never appears and you'll assume the widget is broken.

ONLY_FULL_GROUP_BY bites. selectRaw('country_code, COUNT(*) as total')->groupBy('country_code') is fine, but adding a sum() or a non-aggregated column (e.g. users.name) under MySQL strict mode throws. Keep aggregates to exactly the grouped column plus aggregate functions.

Index the column. Without an index on country_code, the map's query becomes a full table scan on every dashboard load. $table->index('country_code') — and for date-bounded maps, a composite (country_code, created_at).

Nulls create a phantom bucket. whereNotNull('country_code') plus where('country_code', '!=', '') — otherwise you get a "null" key that silently drops or, worse, renders as a legend entry.

Asset staleness after upgrade. Because the map JS ships as a publishable asset, composer update alone won't refresh it. After bumping the version, re-run vendor:publish --force for the assets tag and clear caches:

php artisan vendor:publish --tag="filament-world-map-widget-assets" --force
php artisan filament:assets
php artisan view:clear

Dark mode. Hardcoded hex colours in the published config/view will look wrong in Filament's dark theme. Use Filament's CSS custom properties (rgb(var(--gray-800)), rgb(var(--primary-500))) or the dark: Tailwind variant if colours are set in Blade.

Livewire re-render flicker. With $pollingInterval set, the whole widget re-renders and the map library re-initialises. If you see a flash every poll, guard the JS init (if (window.__mapInitialised) return;) or drop polling in favour of an explicit refresh button.

Bundled map library licensing. If it wraps amCharts 5, commercial use requires a licence — check package.json/resources/js before shipping to a client. jsVectorMap and Leaflet are MIT and safe. This is the one thing worth verifying before you commit, because it's expensive to swap later.

Filament v3 vs v4. With a package this small, verify composer.json constraints against your Filament major version. Widget base classes and panel registration changed between v3 and v4; a loose constraint can resolve to an incompatible combination. Pin both.

Testing. Widgets are Livewire components, so they smoke-test cheaply:

it('renders the map', function () {
    User::factory()->count(3)->create(['country_code' => 'US']);

    Livewire::test(\App\Filament\Widgets\UsersByCountryMap::class)
        ->assertSuccessful()
        ->assertSee('US');
});

Make getData() protected (or extract the query into a service) so you can unit-test the aggregation independently of the map rendering.

Maintenance risk. 13 stars, one maintainer, no security process. Wrap the widget in your own thin base class (App\Filament\Widgets\BaseWorldMap) so that if the package is abandoned you can replace the internals in one file rather than across every dashboard. Keep getData() returning a plain array — that's the least-coupled surface you can expose.

Extension point to check first: whether getData() is protected or public, and whether the view accepts a $this->options array for map config. If the config is constructor/property-based rather than array-based, you'll be writing subclasses for every visual variant — worth knowing before you design around it.

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.
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle
adachsoft/api-version-bundle
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager