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

Laravel Geo Genius Laravel Package

devrabiul/laravel-geo-genius

Laravel GeoGenius is a lightweight toolkit for IP geolocation, automatic timezone detection and conversion, locale and multilingual support, plus a country picker with phone formatting and validation. Works via cookies or headers and fully supports Livewire.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and publish:

composer require devrabiul/laravel-geo-genius
php artisan vendor:publish --provider="Devrabiul\LaravelGeoGenius\LaravelGeoGeniusServiceProvider"

That publishes the config (config/laravel-geo-genius.php) and a migration stub. Open the config file first — almost every behavioral question you'll have (detection strategy, cache TTL, phone input defaults, auto-translate) is answered there rather than in docs comments. Then run php artisan migrate if you want the timezone column, or skip the migration stub and add the column yourself with php artisan geo:add-timezone-column users.

Verify it works before wiring anything up:

// routes/web.php — temporary smoke test
Route::get('/geo-check', function () {
    $geo = laravelGeoGenius()->geo();
    return [
        'ip'       => $geo->getClientIp(),
        'country'  => $geo->getCountry(),
        'timezone' => $geo->getTimezone(),
        'lat'      => $geo->getLatitude(),
    ];
});

If those come back populated, the IP lookup + cache layer is healthy and everything else is just wiring.

First real use case — one middleware, one session key. The highest-value day-one integration is a middleware that resolves timezone and locale once per session and applies them globally, so views, date formatting, and translations all behave consistently without touching individual controllers:

// app/Http/Middleware/GeoContext.php
public function handle($request, Closure $next)
{
    $request->session()->put('genius_local_Language',
        $request->session()->get('genius_local_Language')
            ?? laravelGeoGenius()->language()->detectLocale()
    );

    app()->setLocale(session('genius_local_Language', config('app.locale')));

    return $next($request);
}

Register it in the web group, after StartSession — see Gotchas.

Implementation Patterns

Pick one access style and stick to it. The global helper laravelGeoGenius() is the documented recommendation and reads well in Blade; the service classes (GeoLocationService, TimezoneService) are better inside your own injected classes. Don't mix — half your codebase calling new TimezoneService() and half calling the helper makes the eventual caching/test work harder than it needs to be.

Persist timezone on the user, convert at the edges. The intended shape of a timezone-aware app with this package:

// Store — always UTC in the database
$order->created_at = now();

// Display — convert at render time
echo (new TimezoneService())->convertToUserTimezone($order->created_at);

Resolve the display timezone once per request ($tz->getUserTimezone(), backed by the user's timezone column when logged in, falling back to the detected one), then reuse it. Don't call getUserTimezone() in a loop over 200 rows — each call is a potential lookup.

Language switching as a dedicated route. This is the pattern the README endorses and it's the right one:

Route::get('/lang/{locale}', function (string $locale) {
    laravelGeoGenius()->language()->changeUserLanguage($locale);
    return redirect()->back();
})->whereIn('locale', ['en', 'bn', 'es']);

Whitelist the locale against your geo:add-language set — never pass a raw request value straight through, or you'll end up with locale directories created from user input. Everywhere else, read session('genius_local_Language', app()->getLocale()) rather than calling the package again.

Translation workflow is a build step, not a runtime feature. The realistic loop:

php artisan geo:add-language bn
php artisan geo:translations-generate --locale=en   # scans translate() calls
php artisan geo:translate-language-all bn --count=300

Run generation after you add new translate('...') strings, commit both messages.php and new-messages.php, and treat machine-translated output as a draft that goes through review for any customer-facing copy. The geniusTrans() / geniusTranslateNumber() helpers are display-layer only.

Phone input in Blade, validation on both ends.

{!! laravelGeoGenius()->initIntlPhoneInput() !!}
<input id="phone" type="tel" name="phone">

The helper injects the intl-tel-input scripts/styles and a hidden <span class="system-default-country-code" data-value="us"> that your JS reads for initialCountry. Critically: the client-side iti.isValidNumber() check is UX, not security. Mirror it server-side with your own validation rule using the submitted dial code — the package's job is the picker and formatting, not authoritative validation.

Restrict countries deliberately. If your product only serves a couple of markets, set only_countries_mode => true and list them in only_countries_array. It also fixes the "detected country isn't in our list" edge by falling back to initial_country. Leave it off for a global product — an over-restricted dropdown silently blocks legitimate signups.

Wrap the package behind your own service for anything non-trivial. A thin app/Services/GeoContext.php that calls the package and normalises the output pays for itself the first time you need to fake location in tests or swap out the IP provider. The package's offline fallback returns a "default structure," so your wrapper should decide what a null location means for your app (hide geolocation UI? default to app timezone?).

Gotchas and Tips

IP geolocation is not identity. It's city-level at best, wrong behind VPNs, corporate NAT, and mobile carriers, and useless on 127.0.0.1. Never let it drive anything with legal or financial consequence — tax jurisdiction, currency, shipping eligibility, age gating. Use it for defaults the user can override (language, timezone, preselected country in a dropdown), and always persist an explicit user choice that wins over detection.

Middleware ordering matters more than you'd expect. The v1.6 "session-first" strategy means the package reads and writes session data before hitting the IP API. If your geo middleware runs before StartSession, you'll either blow up or get a fresh session each request — defeating the whole caching improvement. Put it after StartSession in the web group, and note that API routes (no session) should fall back to the header/cookie strategy instead.

Caching is 7 days per IP by default (ttl_minutes). Great for API costs, bad for mobile users who moved cities or anyone who toggled a VPN. Be deliberate: flush the cached geo entry on logout, on explicit country change, or shorten the TTL if you serve a highly mobile audience. And remember the cache is keyed by IP — a whole office behind one NAT shares one lookup.

Localhost hits the network. For 127.0.0.1/::1 the package fetches your external IP (ipify) to make detection work in dev. That means slow test suites, flaky CI, and occasional timeouts. Fake the geo layer in tests — don't let feature tests make real HTTP calls to ipwho.is.

initIntlPhoneInput() echoes markup and <script>/<link> tags from a CDN. Three consequences: it will trip a strict CSP, it fails offline, and it breaks the usual @push('scripts') discipline. If you have a CSP, either allowlist cdn.jsdelivr.net or self-host the intl-tel-input assets and initialise it yourself using the country code from .system-default-country-code.

Re-initialise the phone widget on Livewire navigations. wire:navigate and dynamically added inputs won't re-run the original intlTelInput() call, so you get an unformatted field and iti pointing at a detached node. Bind init to Livewire's hooks (livewire:navigated, morph.updated) and guard against double-initialising the same input.

Cache your config after edits. Changing phone_input or translate settings in config/laravel-geo-genius.php and seeing zero effect usually means config:cache is stale — run php artisan config:cache (or config:clear locally). Same for env vars read through env() inside the config file, which return null once cached.

Number translation is a display transform, never a storage format. geniusTranslateNumber(12345) returns Bengali digits when the locale is bn. If you let that value reach a database column or an API payload, you've corrupted numeric data. Convert on output only, and be careful with inputs — accept both scripts and normalise to ASCII before validation.

Translation commands hit an external service. They'll be slow and rate-limited on a large catalogue, and --count exists precisely because of that. Run them in a queue job or a local/dev context, never in a deploy hook that could time out. Also: only strings wrapped in translate() are picked up by geo:translations-generate — plain __() calls won't appear, which is a common "why is my new string missing" confusion.

geo:add-timezone-column is check-then-add. It's safe to re-run, but it only adds a nullable column — it won't backfill existing rows. Pair it with a one-off job that resolves and stores timezones for existing active users, otherwise you're relying on detection forever.

Extension points are all config-driven. The detection strategy, cache lifetime, initial country, country restrictions, and auto-translate toggle are the supported knobs. Anything beyond that (custom IP provider, extra locale rules, your own translation backend), do in a wrapper service rather than forking — the package is at 182 stars with a single maintainer and a fast-moving session layer (v1.6 reworked it), so keeping your divergence in app/ is worth the indirection.

Offline/no-data path needs a conscious default. The package returns a default structure rather than throwing when APIs fail. Decide once — in your wrapper — what "we don't know where this user is" means: fall back to config('app.timezone') and config('app.locale') for the session, and let the UI degrade rather than showing a broken country picker with an empty selection.

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.
unabomber/timezone
delatbabel/contacts
astritzeqiri/g-recaptcha
kevinongko/laravel-genode
developeruz/laravel-analytics
robbiep/afterthedeadline
frankkessler/incontact-laravel-oauth2-rest
jlxh/laravel-userlog
davidmgilo/timetables
qylinfly/role-permission
reflect-digital/conduit
takashato/laravel-vietqr
aitor24/laralang
kraenzle-ritter/entity-relations
dindigital/din-image
pomirleanu/image-colors
sawmainek/laravel-api-generator
delatbabel/jsonresponses
mouhamedfd/permissions-generator
eduplus/qr