GeoIP Redirect
Redirect first-time visitors to their country's language based on IP geolocation.
How It Works
- A first-time visitor (no language cookie) arrives at the site
- PerfLocale detects their IP address (supports proxies, Cloudflare, load balancers)
- The configured GeoIP provider returns the visitor's country code
- The country code is mapped to a language via the Country Mapping table
- If the mapped language is active and different from the default, a 302 redirect occurs
- 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.
| Provider | API Key | Free Tier | Notes |
|---|---|---|---|
| ipinfo.io | Optional | 1,000/day (no token), 50,000/month (with token) | HTTPS, default provider. Also works for paid plans. |
| ipinfo.io Lite | Required | Unlimited | Free signup, country-level data only |
| ipapi.co | Not needed | 1,000 requests/day | HTTPS, non-commercial use only on free tier |
| ipstack.com | Required | N/A | HTTPS requires paid plan |
| ip-api.com | Required | N/A | HTTPS 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
API keys can be stored in wp-config.php as constants:
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 defined as constants, the settings fields are disabled and show the constant name.
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
A table of active languages with comma-separated country codes. Auto-populated from each language's flag field. Examples:
| Language | Country Codes |
|---|---|
| English | US, GB, AU, NZ, CA |
| German | DE, AT, CH |
| Spanish | ES, MX, AR, CO, CL |
| Arabic | SA, AE, EG, MA |
Multiple countries can map to the same language. Each country code can only map to one language - the first match wins.
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.