Geolocation Plugin: Resolve IPs to Cities Locally

Learn how the geolocation plugin maps client IP addresses to cities using a local database, with no external calls. Ideal for developers and administrators using OpenClaw.

Read this when

  • You want to see where the people using your Gateway are connecting from
  • You are choosing or replacing the IP-geolocation database and need its license terms
  • A location is missing, wrong, or stuck and you need to know which layer failed

The geolocation plugin that ships with OpenClaw converts a connecting client's IP address into a general city location. It pulls its database on the first use and serves all responses from that local file, so no lookup forwards an address to any external service.

Its sole responsibility is mapping an address to a place. It does not choose which addresses are resolved, keeps no record of lookups, and plays no role in authorization. The Control UI relies on it to annotate the devices shown on a person's Activity card; any other feature needing a location can invoke the same endpoint.

Quickstart

This plugin comes bundled and runs by default. To observe it in action, open Activity, select a person, and examine their device entry. A remote client displays both its address and the resolved city:

openclaw-control-ui  MacIntel · 8.8.8.8 · Europe/Vienna  Mountain View, California ⓘ

Right after a fresh install, the city column stays empty while the database downloads; once ready, the entry populates on its own, no refresh needed. To test the plugin directly:

curl -s "http://127.0.0.1:18789/plugins/geolocation/lookup?ip=8.8.8.8" -H "Authorization: Bearer <GATEWAY_TOKEN>"
{
  "found": true,
  "city": "Mountain View",
  "region": "California",
  "country": "United States",
  "countryCode": "US",
  "attribution": { "text": "IP Geolocation by DB-IP", "url": "https://db-ip.com" }
}

Pick an address that routes correctly. Reserved blocks like 203.0.113.0/24 do not exist in the database and return {"found": false}:

{
  "found": false,
  "attribution": { "text": "IP Geolocation by DB-IP", "url": "https://db-ip.com" }
}

The initial call also triggers the database download, so allow up to a minute for it; subsequent calls respond from the local copy.

Why some clients never show a location

A location shows up only when the Gateway logged a usable public address for that client, which frequently never happens:

  • Loopback clients have ip stripped from connect handling entirely, so anything reaching the Gateway via an SSH tunnel or a local port forward carries no address to resolve.
  • Tailscale clients present a 100.64/10 carrier-grade-NAT address, while LAN clients present a private one. Both get recorded and shown, but no geolocation database covers them, so the plugin answers found: false for those ranges without ever loading the database. A Gateway that sees only tailnet or LAN traffic therefore never downloads it.
  • Mobile carriers, VPNs, and corporate egress resolve to the operator's exit point, not the individual. The result is confidently incorrect rather than absent.

That is why the device row also includes the client-reported time zone. A browser knows its own zone no matter how it reached the Gateway, so Europe/Vienna keeps functioning exactly where the address stops being useful. Treat the city as a rough guide and the zone as the stronger signal. See Presence for how both values are generated.

Configuration

All options are optional. The defaults constitute a functional configuration.

OptionDefaultPurpose
databaseUrlmonthly DB-IP City Lite buildMMDB source. {yyyy} and {mm} expand to a release month.
attributionTextIP Geolocation by DB-IPCredit shown next to every result.
attributionUrlhttps://db-ip.comLink target for the credit.
refreshDays30How stale the cached database may get before it is downloaded again.
{
  plugins: {
    entries: {
      geolocation: {
        config: {
          refreshDays: 7,
        },
      },
    },
  },
}

Monthly builds arrive a few days into the month, so the plugin attempts the current month and falls back to the prior one. A source that requires no month substitution is fetched exactly as written.

Using a different database

Set databaseUrl together with both attribution fields. The credit belongs to whichever dataset you point at, so altering the source without altering the credit misattributes the data:

{
  plugins: {
    entries: {
      geolocation: {
        config: {
          databaseUrl: "https://example.internal/geoip/city.mmdb",
          attributionText: "IP data by Example",
          attributionUrl: "https://example.internal",
        },
      },
    },
  },
}

Any MaxMind-format city database works, including a self-hosted mirror or a commercial build you already license. The cache file takes its name from the source URL, so switching sources cannot serve the previous provider's data under the new provider's credit.

Data license

The default database is DB-IP City Lite, licensed CC BY 4.0. That license mandates attribution, which is why the credit appears in every response and is rendered next to the value rather than hidden in settings.

OpenClaw downloads this database at runtime and never redistributes it, so the license applies to your deployment's use of the data, not to OpenClaw itself. Plugin code and the maxmind reader it uses are MIT. No free city-level IP database is MIT-licensed; the obligation lives with the data.

Expect city-level accuracy in the 55-80% range, and worse for the mobile, VPN, and CGNAT cases above.

How the database is managed

The download is lazy and demand-driven. It occurs on the first lookup of a public address, which in practice requires all of the following: an authenticated identity exists, an operator opened that person's Activity view, and that client connected from a routable address. A Gateway nobody inspects, or one reached only over loopback, a tunnel, a LAN, or a tailnet, never downloads anything.

On that first qualifying lookup the plugin fetches the database into <state-dir>/geolocation/, parses it before publishing it, and keeps it until it ages past refreshDays.

Four behaviors are worth knowing because they decide what you see during a failure:

  • The response is read against a compressed ceiling and inflated against an on-disk ceiling, both enforced while reading. A replaced source cannot allocate an unbounded body, and a compression bomb cannot inflate past the limit.
  • A body that does not parse as an MMDB is discarded without replacing a working database. A rate-limit page or truncated download cannot break a Gateway that was working a minute ago.
  • A failed refresh serves the cached copy and logs a warning. Stale data beats no data.
  • Concurrent first lookups share one download rather than each starting their own.

Troubleshooting

No location on any device row. Either the Gateway recorded no address, a row showing only a platform and time zone has no ip, which is expected for loopback and tunneled clients, or every address present is private or carrier-grade NAT, which the plugin answers without consulting the database. Nothing is broken in either case.

Every lookup returns 503. The database is unavailable, still downloading, or every candidate URL failed. Check the Gateway log for geolocation: downloaded or a geolocation database download failed line naming each URL it tried. A Gateway with no outbound network access cannot fetch the database; point databaseUrl at an internal mirror instead.

A found: false answer. The database has no entry for that address. This is a data limitation, not a failure. Note that 503 and found: false are deliberately different: one means the plugin could not answer, the other means the database has no place for that address.

A wrong city. Confirm the address is the person's, not a VPN or carrier exit. If it is genuinely wrong, DB-IP accepts corrections, or point databaseUrl at a commercial database with better coverage.

  • Presence, how connect records the address and time zone this plugin reads
  • Manage plugins, enabling, disabling, and configuring bundled plugins
  • Trusted proxy auth, how the Gateway determines a client address behind a proxy
1,242 words · updated Aug 25, 2026