Whereami.nvim

July 10, 2026 ยท View on GitHub

CI Neovim 0.9+ License GitHub stars Last commit Ask DeepWiki

Check the approximate location of your current public IP without leaving Neovim. Whereami.nvim is useful for confirming that a VPN is connected to the expected country, city, and network.

Note

IP geolocation is approximate. It identifies the location associated with your public or VPN exit IP, not your exact physical location.

Features

  • Country code and flag notifications
  • City, public IP, and network organization details
  • Automatic fallback between location providers
  • In-memory response caching and manual refreshes
  • Optional display masking for private location fields
  • Support for Neovim's built-in notifications and nvim-notify

Table of Contents

Installation

Requirements

  • Neovim 0.9 or newer
  • plenary.nvim
  • Network access to the configured location providers
  • An emoji-capable terminal and font for country flags (optional)

lazy.nvim

{
  "ragnarok22/whereami.nvim",
  cmd = "Whereami",
  dependencies = { "nvim-lua/plenary.nvim" },
}

Whereami.nvim works with its defaults, so calling setup() is optional. To configure it with lazy.nvim, add an opts table:

{
  "ragnarok22/whereami.nvim",
  cmd = "Whereami",
  dependencies = { "nvim-lua/plenary.nvim" },
  opts = {
    default_command = "all",
  },
}

pckr.nvim

local cmd = require("pckr.loader.cmd")

require("pckr").add({
  {
    "ragnarok22/whereami.nvim",
    requires = { "nvim-lua/plenary.nvim" },
    cond = cmd("Whereami"),
  },
})

packer.nvim (legacy)

packer.nvim is deprecated, but existing configurations can install Whereami.nvim with:

use({
  "ragnarok22/whereami.nvim",
  cmd = "Whereami",
  requires = { "nvim-lua/plenary.nvim" },
})

Configuration

Call setup() to override any defaults. Each call resets unspecified options to their defaults and clears cached location data.

require("whereami").setup({
  default_command = "all",
  cache_ttl = 300000,
  privacy = {
    mask_ip = true,
    hide_city = false,
    hide_isp = false,
  },
})

Options

OptionDefaultDescription
provider_urlnilUse one provider URL instead of the default provider list.
providersnilA provider string, provider table, or ordered list of providers.
timeout5000Request timeout per provider, in milliseconds.
default_command"country"Notification shown by :Whereami and whereami.whereami(). Use country, city, ip, isp, or all.
cache_ttl300000Cache duration in milliseconds. Set to 0 to disable caching.
notification.title"Where am I?"Title passed to vim.notify.
notification.icons.country_fallback"๐ŸŒŽ"Icon used when a country code is unavailable or invalid.
notification.icons.default"โ”"Icon used for non-country notifications.
privacy.mask_ipfalseMask part of the IP address in notifications.
privacy.hide_cityfalseDisplay hidden instead of the city in notifications.
privacy.hide_ispfalseDisplay hidden instead of the network organization in notifications.
hooks.before_requestnilFunction called before a fresh provider request cycle.
hooks.after_requestnilFunction called after fresh location data is successfully accepted.

Notifications

Whereami.nvim uses vim.notify, so it works with Neovim's default notifications. To use nvim-notify with lazy.nvim:

{
  "ragnarok22/whereami.nvim",
  cmd = "Whereami",
  dependencies = {
    "nvim-lua/plenary.nvim",
    {
      "rcarriga/nvim-notify",
      config = function()
        require("notify").setup({})
        vim.notify = require("notify")
      end,
    },
  },
}

Providers

By default, Whereami.nvim tries ipinfo.io and falls back to ipapi.co if the first provider fails or returns no usable location fields.

Use provider_url for one endpoint that returns at least one supported field: ip, city, country, or org.

require("whereami").setup({
  provider_url = "https://ipinfo.io/json",
})

For custom response formats, define an ordered provider list and normalize each response:

require("whereami").setup({
  providers = {
    { url = "https://ipinfo.io/json" },
    {
      url = "https://ipapi.co/json/",
      normalize = function(data)
        return {
          ip = data.ip,
          city = data.city,
          country = data.country_code,
          org = data.org or data.asn,
        }
      end,
    },
  },
})

Each provider must define either:

  • url: The JSON endpoint requested with plenary.curl.
  • fetch(config): A custom request function used instead of url.

Providers can also define normalize(data) to map their response to ip, city, country, and org. provider_url takes precedence over providers. Accepted data must contain at least one supported location field; without a normalizer, additional provider-specific fields are preserved.

Request Hooks

Hooks run only for fresh requests, not cache hits:

require("whereami").setup({
  hooks = {
    before_request = function(config)
      vim.notify("Checking location with a " .. config.timeout .. " ms timeout")
    end,
    after_request = function(data, config)
      vim.notify("Location response received for " .. (data.ip or "unknown IP"))
    end,
  },
})

before_request runs once before the provider fallback cycle. after_request runs only after a provider returns accepted location data.

Usage

Run :Whereami to show the configured default notification. The default is the country associated with your current public IP.

For a complete VPN check:

:Whereami all

To bypass cached data after connecting to a different VPN server:

:Whereami refresh

Commands

CommandBehavior
:WhereamiRun the configured default_command.
:Whereami countryShow the country code and flag.
:Whereami cityShow the approximate city.
:Whereami ipShow the public IP address.
:Whereami ispShow the network organization or ISP.
:Whereami allShow country, city, IP address, and network organization.
:Whereami jsonPrint unmasked location data as JSON.
:Whereami refreshClear the cache, fetch fresh data, and show the country.

Lua API

local whereami = require("whereami")

whereami.whereami()   -- show the configured default notification
whereami.country()    -- show the country
whereami.city()       -- show the city
whereami.ip()         -- show the public IP
whereami.isp()        -- show the network organization
whereami.all()        -- show all location fields
whereami.clear_cache()

local data, err = whereami.get()
local fresh_data, refresh_err = whereami.refresh()

get() returns location data or nil, error. The default providers return the fields ip, city, country, and org; custom providers may preserve additional fields. refresh() clears the cache and returns freshly fetched data without displaying a success notification. Use :Whereami refresh when you want both a fresh request and visible output.

Keymaps

local whereami = require("whereami")

vim.keymap.set("n", "<leader>vc", whereami.country, {
  desc = "Check VPN country",
})

vim.keymap.set("n", "<leader>va", whereami.all, {
  desc = "Check VPN location details",
})

vim.keymap.set("n", "<leader>vr", "<cmd>Whereami refresh<cr>", {
  desc = "Refresh VPN location",
})

Privacy and Network Behavior

Whereami.nvim contacts a third-party IP geolocation provider when it needs fresh data.

  • The provider receives your current public IP address and normal HTTP request metadata.
  • Built-in requests do not send Neovim buffers, files, or editor configuration.
  • IP geolocation is approximate and may report the provider's nearest known network location.
  • Privacy options change notification output only. whereami.get() and :Whereami json return unmasked location data.
  • Successful responses are cached in memory for five minutes by default and are never persisted between Neovim sessions.
  • Provider requests are synchronous and use the configured timeout for each attempted provider.

Review the privacy policy and terms of every provider you configure. Use cache_ttl = 0 to disable caching, clear_cache() to remove cached data, or :Whereami refresh to force a new request.

Health Checks

Run the built-in health check when installation or requests are not working:

:checkhealth whereami

It verifies that plenary.curl is available, checks JSON decoding, reports information about vim.notify, and tests connectivity to https://ipinfo.io/json. The reachability check always uses ipinfo.io, even when a custom provider is configured.

If your plugin manager loads Whereami.nvim only for the :Whereami command, load the plugin first by running :Whereami or your manager's explicit load command.

Development

Setup

Clone plenary.nvim into the recommended repository-local dependency directory:

git clone --depth 1 https://github.com/nvim-lua/plenary.nvim .deps/plenary.nvim

Alternatively, set PLENARY_NVIM_PATH to an existing checkout. The test bootstrap also recognizes deps/plenary.nvim, tests/deps/plenary.nvim, and standard lazy.nvim or native package locations.

Checks

Run formatting and lint checks from the repository root:

stylua --check .
selene .

Run the Plenary test suite with the test bootstrap as Neovim's startup file:

nvim --headless -u tests/minimal_init.lua -c "PlenaryBustedDirectory lua/tests {minimal_init = 'tests/minimal_init.lua'}" +qa

Check README links with:

lychee --verbose --no-progress README.md

GitHub Actions runs these checks for pushes to main and for pull requests.

Contributing and Security

License

GNU GPLv3