GeoIP Redirect

Redirect first-time visitors to their country's language based on IP geolocation.

How It Works

  1. A first-time visitor (no language cookie) arrives at the site
  2. PerfLocale detects their IP address (supports proxies, Cloudflare, load balancers)
  3. The configured GeoIP provider returns the visitor's country code
  4. The country code is mapped to a language via the Country Mapping table
  5. If the mapped language is active and different from the default, a 302 redirect occurs
  6. A cookie is set to prevent future redirects

GeoIP redirect takes priority over Browser Language Redirect. If both are enabled and GeoIP finds a match, the browser redirect is skipped.

Built-in Providers

All providers use HTTPS exclusively. Providers that only offer HTTPS on paid plans require a paid subscription.

ProviderAPI KeyFree TierNotes
ipinfo.ioOptional1,000/day (no token), 50,000/month (with token)HTTPS, default provider. Also works for paid plans.
ipinfo.io LiteRequiredUnlimitedFree signup, country-level data only
ipapi.coNot needed1,000 requests/dayHTTPS, non-commercial use only on free tier
ipstack.comRequiredN/AHTTPS requires paid plan
ip-api.comRequiredN/AHTTPS requires Pro plan

Settings

Enable GeoIP Redirect

Checkbox to enable/disable the feature. When disabled, no API calls are made.

GeoIP Provider

Select which API to use for IP lookups. Provider-specific fields (API keys) are shown/hidden based on selection.

API Keys

GeoIP keys can be supplied as environment variables (recommended for containers / managed hosts), as PHP constants in wp-config.php, or via the admin Settings page. PerfLocale resolves them in the priority order env > constant > database — the first non-empty source wins. The same uppercase canonical name is used for both env and constant:

# As environment variables (Docker, .env, etc.)
PERFLOCALE_IPINFO_TOKEN=your-token-here
PERFLOCALE_IPINFO_LITE_TOKEN=your-lite-token-here
PERFLOCALE_IPSTACK_KEY=your-key-here
PERFLOCALE_IP_API_KEY=your-ip-api-pro-key-here
// Or as PHP constants in wp-config.php
define( 'PERFLOCALE_IPINFO_TOKEN', 'your-token-here' );
define( 'PERFLOCALE_IPINFO_LITE_TOKEN', 'your-lite-token-here' );
define( 'PERFLOCALE_IPSTACK_KEY', 'your-key-here' );
define( 'PERFLOCALE_IP_API_KEY', 'your-ip-api-pro-key-here' );

When an env var or constant is set, the matching admin field is disabled and shows where the value comes from. See API Keys: Environment Variables & Constants for the full priority logic.

Custom IP Header

If your site is behind a CDN or reverse proxy that uses a custom header for the real visitor IP (e.g., Imperva, Sucuri, or a custom load balancer), use the perflocale/geo/visitor_ip filter to read it:

// Example: Imperva (Incapsula) uses X-Incap-Client-IP.
add_filter( 'perflocale/geo/visitor_ip', function ( string $ip ): string {
	if ( ! empty( $_SERVER['HTTP_X_INCAP_CLIENT_IP'] ) ) {
		return sanitize_text_field( wp_unslash( $_SERVER['HTTP_X_INCAP_CLIENT_IP'] ) );
	}
	return $ip;
} );

PerfLocale automatically detects Cloudflare (CF-Connecting-IP), standard proxies (X-Forwarded-For), and nginx (X-Real-IP). Use this filter only if your proxy sends a non-standard header.

Cache Duration

GeoIP results are cached per IP address using WordPress transients. Default: 24 hours. This prevents repeated API calls for the same visitor and respects provider rate limits.

Country Mapping

Zero-config in the common case. Enable GeoIP redirect, pick a provider, set the API key — done. The plugin auto-derives country codes from each language's locale (en_USUS, de_DEDE, fr_FRFR, etc.), so a typical multi-language site works out of the box without filling in the table.

The Country Mapping table only matters in two cases:

  1. Languages spoken across many countries — Arabic (ar), generic Chinese (zh), Spanish across Latin America. The plugin can't guess your intent here, so map the country codes you want routed to that language (e.g. SA, AE, EG, JO for Arabic).
  2. Custom routing overrides — e.g. you want Australian visitors served the British English translation: enter AU next to English (UK).
LanguageCountry Codes (typical)
English (UK)auto-derived: GB — or override with GB, AU, NZ
Germanauto-derived: DE — or extend to DE, AT, CH
Spanish (Spain)auto-derived: ES — or extend to ES, MX, AR, CO, CL
Arabic (locale ar)Must be set explicitly: e.g. SA, AE, EG, JO, MA

Multiple countries can map to the same language. Each country code can only map to one language — the first match wins.

The default language is intentionally never mappable

Visitors from countries that don't match any mapping stay on the default automatically — the default IS the catch-all. Mapping the default would over-redirect (e.g. visitors who explicitly chose another language). The Country Mapping table therefore hides the default-language row, and the server-side sanitiser drops any default-language entry submitted via a form, so the guarantee holds even against hand-crafted POSTs.

This works regardless of what your default language is. If your default is, say, Arabic (locale ar, no specific country), Arabic-speaking visitors from any country simply stay on the default Arabic version — no country code is needed for the default itself.

Developer Hooks

Filters

perflocale/geo/lookup_country

Bypass all built-in providers by returning a country code from your own source (local database, custom API, etc.).

// Use MaxMind GeoLite2 local database instead of API calls.
add_filter( 'perflocale/geo/lookup_country', function ( string $country, string $ip ): string {
	if ( ! class_exists( 'GeoIp2\Database\Reader' ) ) {
		return $country;
	}

	try {
		$reader  = new GeoIp2\Database\Reader( '/path/to/GeoLite2-Country.mmdb' );
		$record  = $reader->country( $ip );
		return $record->country->isoCode;
	} catch ( \Exception $e ) {
		return $country; // Fall back to built-in provider.
	}
}, 10, 2 );

Parameters:

  • string $country_code - Empty string (return a 2-letter code to skip built-in providers).
  • string $ip - Visitor IP address.

perflocale/geo/country_code

Modify the country code after it has been looked up by any provider.

add_filter( 'perflocale/geo/country_code', function ( string $code, string $ip ): string {
	// Custom logic here.
	return $code;
}, 10, 2 );

Parameters:

  • string $country_code - Two-letter country code (uppercase).
  • string $ip - Visitor IP address.

perflocale/geo/redirect_language

Override which language a visitor should be redirected to, after country-to-language mapping.

// Force all South American countries to Spanish.
add_filter( 'perflocale/geo/redirect_language', function ( string $slug, string $country, string $ip ): string {
	$south_america = [ 'AR', 'BO', 'BR', 'CL', 'CO', 'EC', 'GY', 'PY', 'PE', 'SR', 'UY', 'VE' ];

	if ( in_array( $country, $south_america, true ) && $country !== 'BR' ) {
		return 'es';
	}

	return $slug;
}, 10, 3 );

Parameters:

  • string $language_slug - Resolved language slug (or empty if no mapping found).
  • string $country_code - Two-letter country code.
  • string $ip - Visitor IP address.

perflocale/geo/providers

Register custom GeoIP providers alongside the built-in ones.

add_filter( 'perflocale/geo/providers', function ( array $providers ): array {
	$providers['my_provider'] = [
		'name'           => 'My GeoIP Service',
		'needs_key'      => true,
		'key_setting'    => 'geo_my_provider_key',
		'fetch_callback' => function ( string $ip, $settings ): string {
			$key = $settings->get( 'geo_my_provider_key', '' );
			$response = wp_remote_get( "https://my-api.com/lookup?ip={$ip}&key={$key}" );

			if ( is_wp_error( $response ) ) {
				return '';
			}

			$body = json_decode( wp_remote_retrieve_body( $response ), true );
			return $body['country'] ?? '';
		},
	];

	return $providers;
} );

Parameters:

  • array $providers - Associative array of provider definitions.

perflocale/geo/country_map

Filter the country-to-language mapping array.

// Programmatically add mappings.
add_filter( 'perflocale/geo/country_map', function ( array $map ): array {
	$map['JP'] = 'ja';
	$map['KR'] = 'ko';
	return $map;
} );

Parameters:

  • array $map - [ 'US' => 'en', 'DE' => 'de', ... ]

Actions

perflocale/geo/redirected

Fires after a GeoIP redirect is performed. Useful for analytics or logging.

add_action( 'perflocale/geo/redirected', function ( string $slug, string $country, string $ip ): void {
	error_log( "PerfLocale GeoIP: Redirected {$ip} ({$country}) to {$slug}" );
}, 10, 3 );

Parameters:

  • string $language_slug - Language the visitor was redirected to.
  • string $country_code - Detected country code.
  • string $ip - Visitor IP address.

Behavior Notes

  • Local/private IPs (127.0.0.1, 192.168.x.x, etc.) are skipped - no API call is made.
  • Bots and crawlers are excluded from redirects (same as browser redirect).
  • Caching prevents API abuse - each IP is looked up once per cache duration.
  • Empty results are cached too - prevents hammering the API for unresolvable IPs.
  • Cookie prevents re-redirect - once a visitor has a language cookie, GeoIP is skipped.
  • Priority: URL detection > Cookie > GeoIP redirect > Browser redirect > Default language.