This library reads MaxMind GeoLite2 and GeoIP2 databases.
This library is built using
the Go maxminddb reader. All
data for the database record is decoded using this library. Version 2.0
provides significant performance improvements with 56% fewer allocations and
34% less memory usage compared to v1. Version 2.0 also adds Network and
IPAddress fields to all result structs, and includes a HasData() method to
easily check if data was found. If you only need several fields, you may get
superior performance by using maxminddb's Lookup directly with a result
struct that only contains the required fields. (See
example_test.go
in the maxminddb repository for an example of this.)
go get github.com/oschwald/geoip2-golang/v2
Version 2.0 includes several major improvements:
netip.Addr instead of net.IP for better performanceNetwork and
IPAddress fieldsHasData() method to easily check if data was foundmap[string]string with typed Names struct
for better performanceSee MIGRATION.md for step-by-step guidance on upgrading from v1.
See GoDoc for documentation and examples.
package main
import (
"fmt"
"log"
"net/netip"
"github.com/oschwald/geoip2-golang/v2"
)
func main() {
db, err := geoip2.Open("GeoIP2-City.mmdb")
if err != nil {
log.Fatal(err)
}
defer db.Close()
// If you are using strings that may be invalid, use netip.ParseAddr and check for errors
ip, err := netip.ParseAddr("81.2.69.142")
if err != nil {
log.Fatal(err)
}
record, err := db.City(ip)
if err != nil {
log.Fatal(err)
}
if !record.HasData() {
fmt.Println("No data found for this IP")
return
}
fmt.Printf("Portuguese (BR) city name: %v\n", record.City.Names.BrazilianPortuguese)
if len(record.Subdivisions) > 0 {
fmt.Printf("English subdivision name: %v\n", record.Subdivisions[0].Names.English)
}
fmt.Printf("Russian country name: %v\n", record.Country.Names.Russian)
fmt.Printf("ISO country code: %v\n", record.Country.ISOCode)
fmt.Printf("Time zone: %v\n", record.Location.TimeZone)
if record.Location.HasCoordinates() {
fmt.Printf("Coordinates: %v, %v\n", *record.Location.Latitude, *record.Location.Longitude)
}
// Output:
// Portuguese (BR) city name: Londres
// English subdivision name: England
// Russian country name: Великобритания
// ISO country code: GB
// Time zone: Europe/London
// Coordinates: 51.5142, -0.0931
}
Database model structs use decoders generated by the maxminddb-gen tool from
maxminddb-golang. This is transparent to library users and improves lookup
performance while preserving the decoded results.
When changing models.go, run go generate ./... and commit the updated
models_maxminddb.go. The generator version is pinned in go.mod, and CI
checks that generated files are current.
Download free GeoLite2 databases from MaxMind's website. Registration required.
Purchase GeoIP2 databases from MaxMind for enhanced accuracy and additional features.
This library supports all MaxMind GeoIP2 and GeoLite2 database types. Below are examples for each database type:
The City database provides the most comprehensive geolocation data, including city, subdivision, country, and precise location information.
package main
import (
"fmt"
"log"
"net/netip"
"github.com/oschwald/geoip2-golang/v2"
)
func main() {
db, err := geoip2.Open("GeoIP2-City.mmdb")
if err != nil {
log.Fatal(err)
}
defer db.Close()
ip, err := netip.ParseAddr("128.101.101.101")
if err != nil {
log.Fatal(err)
}
record, err := db.City(ip)
if err != nil {
log.Fatal(err)
}
if !record.HasData() {
fmt.Println("No data found for this IP")
return
}
fmt.Printf("City: %v\n", record.City.Names.English)
fmt.Printf("Subdivision: %v\n", record.Subdivisions[0].Names.English)
fmt.Printf("Country: %v (%v)\n", record.Country.Names.English, record.Country.ISOCode)
fmt.Printf("Continent: %v (%v)\n", record.Continent.Names.English, record.Continent.Code)
fmt.Printf("Postal Code: %v\n", record.Postal.Code)
if record.Location.HasCoordinates() {
fmt.Printf("Location: %v, %v\n", *record.Location.Latitude, *record.Location.Longitude)
}
fmt.Printf("Time Zone: %v\n", record.Location.TimeZone)
fmt.Printf("Network: %v\n", record.Traits.Network)
fmt.Printf("IP Address: %v\n", record.Traits.IPAddress)
}
The Country database provides country-level geolocation data.
package main
import (
"fmt"
"log"
"net/netip"
"github.com/oschwald/geoip2-golang/v2"
)
func main() {
db, err := geoip2.Open("GeoIP2-Country.mmdb")
if err != nil {
log.Fatal(err)
}
defer db.Close()
ip, err := netip.ParseAddr("81.2.69.142")
if err != nil {
log.Fatal(err)
}
record, err := db.Country(ip)
if err != nil {
log.Fatal(err)
}
if !record.HasData() {
fmt.Println("No data found for this IP")
return
}
fmt.Printf("Country: %v (%v)\n", record.Country.Names.English, record.Country.ISOCode)
fmt.Printf("Continent: %v (%v)\n", record.Continent.Names.English, record.Continent.Code)
fmt.Printf("Is in EU: %v\n", record.Country.IsInEuropeanUnion)
fmt.Printf("Network: %v\n", record.Traits.Network)
fmt.Printf("IP Address: %v\n", record.Traits.IPAddress)
if record.RegisteredCountry.Names.English != "" {
fmt.Printf("Registered Country: %v (%v)\n",
record.RegisteredCountry.Names.English, record.RegisteredCountry.ISOCode)
}
}
The ASN database provides Autonomous System Number and organization information.
package main
import (
"fmt"
"log"
"net/netip"
"github.com/oschwald/geoip2-golang/v2"
)
func main() {
db, err := geoip2.Open("GeoLite2-ASN.mmdb")
if err != nil {
log.Fatal(err)
}
defer db.Close()
ip, err := netip.ParseAddr("1.128.0.0")
if err != nil {
log.Fatal(err)
}
record, err := db.ASN(ip)
if err != nil {
log.Fatal(err)
}
if !record.HasData() {
fmt.Println("No data found for this IP")
return
}
fmt.Printf("ASN: %v\n", record.AutonomousSystemNumber)
fmt.Printf("Organization: %v\n", record.AutonomousSystemOrganization)
fmt.Printf("Network: %v\n", record.Network)
fmt.Printf("IP Address: %v\n", record.IPAddress)
}
The Anonymous IP database identifies various types of anonymous and proxy networks.
package main
import (
"fmt"
"log"
"net/netip"
"github.com/oschwald/geoip2-golang/v2"
)
func main() {
db, err := geoip2.Open("GeoIP2-Anonymous-IP.mmdb")
if err != nil {
log.Fatal(err)
}
defer db.Close()
ip, err := netip.ParseAddr("81.2.69.142")
if err != nil {
log.Fatal(err)
}
record, err := db.AnonymousIP(ip)
if err != nil {
log.Fatal(err)
}
if !record.HasData() {
fmt.Println("No data found for this IP")
return
}
fmt.Printf("Is Anonymous: %v\n", record.IsAnonymous)
fmt.Printf("Is Anonymous VPN: %v\n", record.IsAnonymousVPN)
fmt.Printf("Is Hosting Provider: %v\n", record.IsHostingProvider)
fmt.Printf("Is Public Proxy: %v\n", record.IsPublicProxy)
fmt.Printf("Is Residential Proxy: %v\n", record.IsResidentialProxy)
fmt.Printf("Is Tor Exit Node: %v\n", record.IsTorExitNode)
fmt.Printf("Network: %v\n", record.Network)
fmt.Printf("IP Address: %v\n", record.IPAddress)
}
The Anonymous Plus database extends the Anonymous IP database with additional fields for confidence scoring, provider identification, and temporal tracking.
package main
import (
"fmt"
"log"
"net/netip"
"github.com/oschwald/geoip2-golang/v2"
)
func main() {
db, err := geoip2.Open("GeoIP-Anonymous-Plus.mmdb")
if err != nil {
log.Fatal(err)
}
defer db.Close()
ip, err := netip.ParseAddr("1.2.0.1")
if err != nil {
log.Fatal(err)
}
record, err := db.AnonymousPlus(ip)
if err != nil {
log.Fatal(err)
}
if !record.HasData() {
fmt.Println("No data found for this IP")
return
}
// Standard anonymous IP flags
fmt.Printf("Is Anonymous: %v\n", record.IsAnonymous)
fmt.Printf("Is Anonymous VPN: %v\n", record.IsAnonymousVPN)
fmt.Printf("Is Hosting Provider: %v\n", record.IsHostingProvider)
fmt.Printf("Is Public Proxy: %v\n", record.IsPublicProxy)
fmt.Printf("Is Residential Proxy: %v\n", record.IsResidentialProxy)
fmt.Printf("Is Tor Exit Node: %v\n", record.IsTorExitNode)
// Anonymous Plus specific fields
fmt.Printf("Anonymizer Confidence: %v\n", record.AnonymizerConfidence)
fmt.Printf("Provider Name: %v\n", record.ProviderName)
if !record.NetworkLastSeen.IsZero() {
fmt.Printf("Network Last Seen: %v\n", record.NetworkLastSeen.Format("2006-01-02"))
}
fmt.Printf("Network: %v\n", record.Network)
fmt.Printf("IP Address: %v\n", record.IPAddress)
}
The Enterprise database provides the most comprehensive data, including all City database fields plus additional enterprise features.
package main
import (
"fmt"
"log"
"net/netip"
"github.com/oschwald/geoip2-golang/v2"
)
func main() {
db, err := geoip2.Open("GeoIP2-Enterprise.mmdb")
if err != nil {
log.Fatal(err)
}
defer db.Close()
ip, err := netip.ParseAddr("128.101.101.101")
if err != nil {
log.Fatal(err)
}
record, err := db.Enterprise(ip)
if err != nil {
log.Fatal(err)
}
if !record.HasData() {
fmt.Println("No data found for this IP")
return
}
// Basic location information
fmt.Printf("City: %v\n", record.City.Names.English)
fmt.Printf("Country: %v (%v)\n", record.Country.Names.English, record.Country.ISOCode)
if record.Location.HasCoordinates() {
fmt.Printf("Location: %v, %v\n", *record.Location.Latitude, *record.Location.Longitude)
}
// Enterprise-specific fields
fmt.Printf("ISP: %v\n", record.Traits.ISP)
fmt.Printf("Organization: %v\n", record.Traits.Organization)
fmt.Printf("ASN: %v (%v)\n", record.Traits.AutonomousSystemNumber,
record.Traits.AutonomousSystemOrganization)
fmt.Printf("Connection Type: %v\n", record.Traits.ConnectionType)
fmt.Printf("Domain: %v\n", record.Traits.Domain)
fmt.Printf("User Type: %v\n", record.Traits.UserType)
fmt.Printf("Is Anycast: %v\n", record.Traits.IsAnycast)
// Mobile carrier information (if available)
if record.Traits.MobileCountryCode != "" {
fmt.Printf("Mobile Country Code: %v\n", record.Traits.MobileCountryCode)
fmt.Printf("Mobile Network Code: %v\n", record.Traits.MobileNetworkCode)
}
fmt.Printf("Network: %v\n", record.Traits.Network)
fmt.Printf("IP Address: %v\n", record.Traits.IPAddress)
}
The ISP database provides ISP, organization, and ASN information.
package main
import (
"fmt"
"log"
"net/netip"
"github.com/oschwald/geoip2-golang/v2"
)
func main() {
db, err := geoip2.Open("GeoIP2-ISP.mmdb")
if err != nil {
log.Fatal(err)
}
defer db.Close()
ip, err := netip.ParseAddr("1.128.0.0")
if err != nil {
log.Fatal(err)
}
record, err := db.ISP(ip)
if err != nil {
log.Fatal(err)
}
if !record.HasData() {
fmt.Println("No data found for this IP")
return
}
fmt.Printf("ISP: %v\n", record.ISP)
fmt.Printf("Organization: %v\n", record.Organization)
fmt.Printf("ASN: %v (%v)\n", record.AutonomousSystemNumber,
record.AutonomousSystemOrganization)
// Mobile carrier information (if available)
if record.MobileCountryCode != "" {
fmt.Printf("Mobile Country Code: %v\n", record.MobileCountryCode)
fmt.Printf("Mobile Network Code: %v\n", record.MobileNetworkCode)
}
fmt.Printf("Network: %v\n", record.Network)
fmt.Printf("IP Address: %v\n", record.IPAddress)
}
The Domain database provides the second-level domain associated with an IP address.
package main
import (
"fmt"
"log"
"net/netip"
"github.com/oschwald/geoip2-golang/v2"
)
func main() {
db, err := geoip2.Open("GeoIP2-Domain.mmdb")
if err != nil {
log.Fatal(err)
}
defer db.Close()
ip, err := netip.ParseAddr("1.2.0.0")
if err != nil {
log.Fatal(err)
}
record, err := db.Domain(ip)
if err != nil {
log.Fatal(err)
}
if !record.HasData() {
fmt.Println("No data found for this IP")
return
}
fmt.Printf("Domain: %v\n", record.Domain)
fmt.Printf("Network: %v\n", record.Network)
fmt.Printf("IP Address: %v\n", record.IPAddress)
}
The Connection Type database identifies the connection type of an IP address.
package main
import (
"fmt"
"log"
"net/netip"
"github.com/oschwald/geoip2-golang/v2"
)
func main() {
db, err := geoip2.Open("GeoIP2-Connection-Type.mmdb")
if err != nil {
log.Fatal(err)
}
defer db.Close()
ip, err := netip.ParseAddr("1.0.128.0")
if err != nil {
log.Fatal(err)
}
record, err := db.ConnectionType(ip)
if err != nil {
log.Fatal(err)
}
if !record.HasData() {
fmt.Println("No data found for this IP")
return
}
fmt.Printf("Connection Type: %v\n", record.ConnectionType)
fmt.Printf("Network: %v\n", record.Network)
fmt.Printf("IP Address: %v\n", record.IPAddress)
}
All database lookups can return errors and should be handled appropriately:
package main
import (
"fmt"
"log"
"net/netip"
"github.com/oschwald/geoip2-golang/v2"
)
func main() {
db, err := geoip2.Open("GeoIP2-City.mmdb")
if err != nil {
log.Fatal(err)
}
defer db.Close()
ip, err := netip.ParseAddr("10.0.0.1") // Private IP
if err != nil {
log.Fatal(err)
}
record, err := db.City(ip)
if err != nil {
log.Fatal(err)
}
// Always check if data was found
if !record.HasData() {
fmt.Println("No data found for this IP address")
return
}
// Check individual fields before using them
if record.City.Names.English != "" {
fmt.Printf("City: %v\n", record.City.Names.English)
} else {
fmt.Println("City name not available")
}
// Check array bounds for subdivisions
if len(record.Subdivisions) > 0 {
fmt.Printf("Subdivision: %v\n", record.Subdivisions[0].Names.English)
} else {
fmt.Println("No subdivision data available")
}
fmt.Printf("Country: %v\n", record.Country.Names.English)
}
Always reuse database instances across requests rather than opening/closing repeatedly:
// Good: Create once, use many times
db, err := geoip2.Open("GeoIP2-City.mmdb")
if err != nil {
log.Fatal(err)
}
defer db.Close()
// Use db for multiple lookups...
For applications needing only specific fields, consider using the lower-level maxminddb library with custom result structs to reduce memory allocation.
The Reader is safe for concurrent use by multiple goroutines.
All result structs include JSON tags and support marshaling to JSON:
record, err := db.City(ip)
if err != nil {
log.Fatal(err)
}
jsonData, err := json.Marshal(record)
if err != nil {
log.Fatal(err)
}
fmt.Println(string(jsonData))
github.com/oschwald/geoip2-golang to
github.com/oschwald/geoip2-golang/v2netip.Addr instead of net.IPIsoCode → ISOCodeHasData() method to check for data availability// v1
import "github.com/oschwald/geoip2-golang"
ip := net.ParseIP("81.2.69.142")
record, err := db.City(ip)
cityName := record.City.Names["en"]
// v2
import "github.com/oschwald/geoip2-golang/v2"
ip, err := netip.ParseAddr("81.2.69.142")
if err != nil {
// handle error
}
record, err := db.City(ip)
if !record.HasData() {
// handle no data found
}
cityName := record.City.Names.English
Database not found: Ensure the .mmdb file path is correct and readable.
No data returned: Check if HasData() returns false - the IP may not be in
the database or may be a private/reserved IP.
Performance issues: Ensure you're reusing the database instance rather than opening it for each lookup.
Make sure you checked out test data submodule:
git submodule init
git submodule update
Execute test suite:
go test
Contributions welcome! Please fork the repository and open a pull request with your changes.
This is free software, licensed under the ISC license.
7,524 followers · starred Jun 2026
147 followers · starred Oct 2023
840 followers · starred Nov 2018
173 followers · starred Sep 2024
This library reads MaxMind GeoLite2 and GeoIP2 databases.
This library is built using
the Go maxminddb reader. All
data for the database record is decoded using this library. Version 2.0
provides significant performance improvements with 56% fewer allocations and
34% less memory usage compared to v1. Version 2.0 also adds Network and
IPAddress fields to all result structs, and includes a HasData() method to
easily check if data was found. If you only need several fields, you may get
superior performance by using maxminddb's Lookup directly with a result
struct that only contains the required fields. (See
example_test.go
in the maxminddb repository for an example of this.)
go get github.com/oschwald/geoip2-golang/v2
Version 2.0 includes several major improvements:
netip.Addr instead of net.IP for better performanceNetwork and
IPAddress fieldsHasData() method to easily check if data was foundmap[string]string with typed Names struct
for better performanceSee MIGRATION.md for step-by-step guidance on upgrading from v1.
See GoDoc for documentation and examples.
package main
import (
"fmt"
"log"
"net/netip"
"github.com/oschwald/geoip2-golang/v2"
)
func main() {
db, err := geoip2.Open("GeoIP2-City.mmdb")
if err != nil {
log.Fatal(err)
}
defer db.Close()
// If you are using strings that may be invalid, use netip.ParseAddr and check for errors
ip, err := netip.ParseAddr("81.2.69.142")
if err != nil {
log.Fatal(err)
}
record, err := db.City(ip)
if err != nil {
log.Fatal(err)
}
if !record.HasData() {
fmt.Println("No data found for this IP")
return
}
fmt.Printf("Portuguese (BR) city name: %v\n", record.City.Names.BrazilianPortuguese)
if len(record.Subdivisions) > 0 {
fmt.Printf("English subdivision name: %v\n", record.Subdivisions[0].Names.English)
}
fmt.Printf("Russian country name: %v\n", record.Country.Names.Russian)
fmt.Printf("ISO country code: %v\n", record.Country.ISOCode)
fmt.Printf("Time zone: %v\n", record.Location.TimeZone)
if record.Location.HasCoordinates() {
fmt.Printf("Coordinates: %v, %v\n", *record.Location.Latitude, *record.Location.Longitude)
}
// Output:
// Portuguese (BR) city name: Londres
// English subdivision name: England
// Russian country name: Великобритания
// ISO country code: GB
// Time zone: Europe/London
// Coordinates: 51.5142, -0.0931
}
Database model structs use decoders generated by the maxminddb-gen tool from
maxminddb-golang. This is transparent to library users and improves lookup
performance while preserving the decoded results.
When changing models.go, run go generate ./... and commit the updated
models_maxminddb.go. The generator version is pinned in go.mod, and CI
checks that generated files are current.
Download free GeoLite2 databases from MaxMind's website. Registration required.
Purchase GeoIP2 databases from MaxMind for enhanced accuracy and additional features.
This library supports all MaxMind GeoIP2 and GeoLite2 database types. Below are examples for each database type:
The City database provides the most comprehensive geolocation data, including city, subdivision, country, and precise location information.
package main
import (
"fmt"
"log"
"net/netip"
"github.com/oschwald/geoip2-golang/v2"
)
func main() {
db, err := geoip2.Open("GeoIP2-City.mmdb")
if err != nil {
log.Fatal(err)
}
defer db.Close()
ip, err := netip.ParseAddr("128.101.101.101")
if err != nil {
log.Fatal(err)
}
record, err := db.City(ip)
if err != nil {
log.Fatal(err)
}
if !record.HasData() {
fmt.Println("No data found for this IP")
return
}
fmt.Printf("City: %v\n", record.City.Names.English)
fmt.Printf("Subdivision: %v\n", record.Subdivisions[0].Names.English)
fmt.Printf("Country: %v (%v)\n", record.Country.Names.English, record.Country.ISOCode)
fmt.Printf("Continent: %v (%v)\n", record.Continent.Names.English, record.Continent.Code)
fmt.Printf("Postal Code: %v\n", record.Postal.Code)
if record.Location.HasCoordinates() {
fmt.Printf("Location: %v, %v\n", *record.Location.Latitude, *record.Location.Longitude)
}
fmt.Printf("Time Zone: %v\n", record.Location.TimeZone)
fmt.Printf("Network: %v\n", record.Traits.Network)
fmt.Printf("IP Address: %v\n", record.Traits.IPAddress)
}
The Country database provides country-level geolocation data.
package main
import (
"fmt"
"log"
"net/netip"
"github.com/oschwald/geoip2-golang/v2"
)
func main() {
db, err := geoip2.Open("GeoIP2-Country.mmdb")
if err != nil {
log.Fatal(err)
}
defer db.Close()
ip, err := netip.ParseAddr("81.2.69.142")
if err != nil {
log.Fatal(err)
}
record, err := db.Country(ip)
if err != nil {
log.Fatal(err)
}
if !record.HasData() {
fmt.Println("No data found for this IP")
return
}
fmt.Printf("Country: %v (%v)\n", record.Country.Names.English, record.Country.ISOCode)
fmt.Printf("Continent: %v (%v)\n", record.Continent.Names.English, record.Continent.Code)
fmt.Printf("Is in EU: %v\n", record.Country.IsInEuropeanUnion)
fmt.Printf("Network: %v\n", record.Traits.Network)
fmt.Printf("IP Address: %v\n", record.Traits.IPAddress)
if record.RegisteredCountry.Names.English != "" {
fmt.Printf("Registered Country: %v (%v)\n",
record.RegisteredCountry.Names.English, record.RegisteredCountry.ISOCode)
}
}
The ASN database provides Autonomous System Number and organization information.
package main
import (
"fmt"
"log"
"net/netip"
"github.com/oschwald/geoip2-golang/v2"
)
func main() {
db, err := geoip2.Open("GeoLite2-ASN.mmdb")
if err != nil {
log.Fatal(err)
}
defer db.Close()
ip, err := netip.ParseAddr("1.128.0.0")
if err != nil {
log.Fatal(err)
}
record, err := db.ASN(ip)
if err != nil {
log.Fatal(err)
}
if !record.HasData() {
fmt.Println("No data found for this IP")
return
}
fmt.Printf("ASN: %v\n", record.AutonomousSystemNumber)
fmt.Printf("Organization: %v\n", record.AutonomousSystemOrganization)
fmt.Printf("Network: %v\n", record.Network)
fmt.Printf("IP Address: %v\n", record.IPAddress)
}
The Anonymous IP database identifies various types of anonymous and proxy networks.
package main
import (
"fmt"
"log"
"net/netip"
"github.com/oschwald/geoip2-golang/v2"
)
func main() {
db, err := geoip2.Open("GeoIP2-Anonymous-IP.mmdb")
if err != nil {
log.Fatal(err)
}
defer db.Close()
ip, err := netip.ParseAddr("81.2.69.142")
if err != nil {
log.Fatal(err)
}
record, err := db.AnonymousIP(ip)
if err != nil {
log.Fatal(err)
}
if !record.HasData() {
fmt.Println("No data found for this IP")
return
}
fmt.Printf("Is Anonymous: %v\n", record.IsAnonymous)
fmt.Printf("Is Anonymous VPN: %v\n", record.IsAnonymousVPN)
fmt.Printf("Is Hosting Provider: %v\n", record.IsHostingProvider)
fmt.Printf("Is Public Proxy: %v\n", record.IsPublicProxy)
fmt.Printf("Is Residential Proxy: %v\n", record.IsResidentialProxy)
fmt.Printf("Is Tor Exit Node: %v\n", record.IsTorExitNode)
fmt.Printf("Network: %v\n", record.Network)
fmt.Printf("IP Address: %v\n", record.IPAddress)
}
The Anonymous Plus database extends the Anonymous IP database with additional fields for confidence scoring, provider identification, and temporal tracking.
package main
import (
"fmt"
"log"
"net/netip"
"github.com/oschwald/geoip2-golang/v2"
)
func main() {
db, err := geoip2.Open("GeoIP-Anonymous-Plus.mmdb")
if err != nil {
log.Fatal(err)
}
defer db.Close()
ip, err := netip.ParseAddr("1.2.0.1")
if err != nil {
log.Fatal(err)
}
record, err := db.AnonymousPlus(ip)
if err != nil {
log.Fatal(err)
}
if !record.HasData() {
fmt.Println("No data found for this IP")
return
}
// Standard anonymous IP flags
fmt.Printf("Is Anonymous: %v\n", record.IsAnonymous)
fmt.Printf("Is Anonymous VPN: %v\n", record.IsAnonymousVPN)
fmt.Printf("Is Hosting Provider: %v\n", record.IsHostingProvider)
fmt.Printf("Is Public Proxy: %v\n", record.IsPublicProxy)
fmt.Printf("Is Residential Proxy: %v\n", record.IsResidentialProxy)
fmt.Printf("Is Tor Exit Node: %v\n", record.IsTorExitNode)
// Anonymous Plus specific fields
fmt.Printf("Anonymizer Confidence: %v\n", record.AnonymizerConfidence)
fmt.Printf("Provider Name: %v\n", record.ProviderName)
if !record.NetworkLastSeen.IsZero() {
fmt.Printf("Network Last Seen: %v\n", record.NetworkLastSeen.Format("2006-01-02"))
}
fmt.Printf("Network: %v\n", record.Network)
fmt.Printf("IP Address: %v\n", record.IPAddress)
}
The Enterprise database provides the most comprehensive data, including all City database fields plus additional enterprise features.
package main
import (
"fmt"
"log"
"net/netip"
"github.com/oschwald/geoip2-golang/v2"
)
func main() {
db, err := geoip2.Open("GeoIP2-Enterprise.mmdb")
if err != nil {
log.Fatal(err)
}
defer db.Close()
ip, err := netip.ParseAddr("128.101.101.101")
if err != nil {
log.Fatal(err)
}
record, err := db.Enterprise(ip)
if err != nil {
log.Fatal(err)
}
if !record.HasData() {
fmt.Println("No data found for this IP")
return
}
// Basic location information
fmt.Printf("City: %v\n", record.City.Names.English)
fmt.Printf("Country: %v (%v)\n", record.Country.Names.English, record.Country.ISOCode)
if record.Location.HasCoordinates() {
fmt.Printf("Location: %v, %v\n", *record.Location.Latitude, *record.Location.Longitude)
}
// Enterprise-specific fields
fmt.Printf("ISP: %v\n", record.Traits.ISP)
fmt.Printf("Organization: %v\n", record.Traits.Organization)
fmt.Printf("ASN: %v (%v)\n", record.Traits.AutonomousSystemNumber,
record.Traits.AutonomousSystemOrganization)
fmt.Printf("Connection Type: %v\n", record.Traits.ConnectionType)
fmt.Printf("Domain: %v\n", record.Traits.Domain)
fmt.Printf("User Type: %v\n", record.Traits.UserType)
fmt.Printf("Is Anycast: %v\n", record.Traits.IsAnycast)
// Mobile carrier information (if available)
if record.Traits.MobileCountryCode != "" {
fmt.Printf("Mobile Country Code: %v\n", record.Traits.MobileCountryCode)
fmt.Printf("Mobile Network Code: %v\n", record.Traits.MobileNetworkCode)
}
fmt.Printf("Network: %v\n", record.Traits.Network)
fmt.Printf("IP Address: %v\n", record.Traits.IPAddress)
}
The ISP database provides ISP, organization, and ASN information.
package main
import (
"fmt"
"log"
"net/netip"
"github.com/oschwald/geoip2-golang/v2"
)
func main() {
db, err := geoip2.Open("GeoIP2-ISP.mmdb")
if err != nil {
log.Fatal(err)
}
defer db.Close()
ip, err := netip.ParseAddr("1.128.0.0")
if err != nil {
log.Fatal(err)
}
record, err := db.ISP(ip)
if err != nil {
log.Fatal(err)
}
if !record.HasData() {
fmt.Println("No data found for this IP")
return
}
fmt.Printf("ISP: %v\n", record.ISP)
fmt.Printf("Organization: %v\n", record.Organization)
fmt.Printf("ASN: %v (%v)\n", record.AutonomousSystemNumber,
record.AutonomousSystemOrganization)
// Mobile carrier information (if available)
if record.MobileCountryCode != "" {
fmt.Printf("Mobile Country Code: %v\n", record.MobileCountryCode)
fmt.Printf("Mobile Network Code: %v\n", record.MobileNetworkCode)
}
fmt.Printf("Network: %v\n", record.Network)
fmt.Printf("IP Address: %v\n", record.IPAddress)
}
The Domain database provides the second-level domain associated with an IP address.
package main
import (
"fmt"
"log"
"net/netip"
"github.com/oschwald/geoip2-golang/v2"
)
func main() {
db, err := geoip2.Open("GeoIP2-Domain.mmdb")
if err != nil {
log.Fatal(err)
}
defer db.Close()
ip, err := netip.ParseAddr("1.2.0.0")
if err != nil {
log.Fatal(err)
}
record, err := db.Domain(ip)
if err != nil {
log.Fatal(err)
}
if !record.HasData() {
fmt.Println("No data found for this IP")
return
}
fmt.Printf("Domain: %v\n", record.Domain)
fmt.Printf("Network: %v\n", record.Network)
fmt.Printf("IP Address: %v\n", record.IPAddress)
}
The Connection Type database identifies the connection type of an IP address.
package main
import (
"fmt"
"log"
"net/netip"
"github.com/oschwald/geoip2-golang/v2"
)
func main() {
db, err := geoip2.Open("GeoIP2-Connection-Type.mmdb")
if err != nil {
log.Fatal(err)
}
defer db.Close()
ip, err := netip.ParseAddr("1.0.128.0")
if err != nil {
log.Fatal(err)
}
record, err := db.ConnectionType(ip)
if err != nil {
log.Fatal(err)
}
if !record.HasData() {
fmt.Println("No data found for this IP")
return
}
fmt.Printf("Connection Type: %v\n", record.ConnectionType)
fmt.Printf("Network: %v\n", record.Network)
fmt.Printf("IP Address: %v\n", record.IPAddress)
}
All database lookups can return errors and should be handled appropriately:
package main
import (
"fmt"
"log"
"net/netip"
"github.com/oschwald/geoip2-golang/v2"
)
func main() {
db, err := geoip2.Open("GeoIP2-City.mmdb")
if err != nil {
log.Fatal(err)
}
defer db.Close()
ip, err := netip.ParseAddr("10.0.0.1") // Private IP
if err != nil {
log.Fatal(err)
}
record, err := db.City(ip)
if err != nil {
log.Fatal(err)
}
// Always check if data was found
if !record.HasData() {
fmt.Println("No data found for this IP address")
return
}
// Check individual fields before using them
if record.City.Names.English != "" {
fmt.Printf("City: %v\n", record.City.Names.English)
} else {
fmt.Println("City name not available")
}
// Check array bounds for subdivisions
if len(record.Subdivisions) > 0 {
fmt.Printf("Subdivision: %v\n", record.Subdivisions[0].Names.English)
} else {
fmt.Println("No subdivision data available")
}
fmt.Printf("Country: %v\n", record.Country.Names.English)
}
Always reuse database instances across requests rather than opening/closing repeatedly:
// Good: Create once, use many times
db, err := geoip2.Open("GeoIP2-City.mmdb")
if err != nil {
log.Fatal(err)
}
defer db.Close()
// Use db for multiple lookups...
For applications needing only specific fields, consider using the lower-level maxminddb library with custom result structs to reduce memory allocation.
The Reader is safe for concurrent use by multiple goroutines.
All result structs include JSON tags and support marshaling to JSON:
record, err := db.City(ip)
if err != nil {
log.Fatal(err)
}
jsonData, err := json.Marshal(record)
if err != nil {
log.Fatal(err)
}
fmt.Println(string(jsonData))
github.com/oschwald/geoip2-golang to
github.com/oschwald/geoip2-golang/v2netip.Addr instead of net.IPIsoCode → ISOCodeHasData() method to check for data availability// v1
import "github.com/oschwald/geoip2-golang"
ip := net.ParseIP("81.2.69.142")
record, err := db.City(ip)
cityName := record.City.Names["en"]
// v2
import "github.com/oschwald/geoip2-golang/v2"
ip, err := netip.ParseAddr("81.2.69.142")
if err != nil {
// handle error
}
record, err := db.City(ip)
if !record.HasData() {
// handle no data found
}
cityName := record.City.Names.English
Database not found: Ensure the .mmdb file path is correct and readable.
No data returned: Check if HasData() returns false - the IP may not be in
the database or may be a private/reserved IP.
Performance issues: Ensure you're reusing the database instance rather than opening it for each lookup.
Make sure you checked out test data submodule:
git submodule init
git submodule update
Execute test suite:
go test
Contributions welcome! Please fork the repository and open a pull request with your changes.
This is free software, licensed under the ISC license.
7,524 followers · starred Jun 2026
147 followers · starred Oct 2023
840 followers · starred Nov 2018
173 followers · starred Sep 2024