Skip to content
go-arcgis

go · zero deps · arcgis rest

A Go client for ArcGIS Feature Services.

Query ArcGIS Feature Services over their REST API — with automatic pagination, counts, ID-only queries, and spatial filters. Two interchangeable styles: a plain struct API and a fluent builder.

MIT licensed · no third-party dependencies · Go 1.26+

query.go
client := arcgis.NewClient(baseURL)

features, _ := client.Layer(7).Query().
    Where("STAGE = 4").
    Fields("BLOCK_NAME", "STAGE").
    All(ctx)  // paginates automatically

what you get

Feature Services, without the boilerplate.

One small engine behind two call styles — it handles the paging, the counting, and ArcGIS's quirks so you don't have to.

Two styles, one engine

A fluent builder and an explicit struct API produce the same QueryParams and hit the same code path — pick whichever reads better at the call site.

Automatic pagination

QueryAll / .All keeps advancing resultOffset while the service reports exceededTransferLimit — you get every feature, not just the first page.

Counts & IDs, cheaply

.Count returns an int with no feature data transferred; .IDs returns just the object IDs — perfect for pre-flighting a big query.

Spatial filters

Filter by a bounding box with WithinEnvelope, or by a point with IntersectsPoint and an explicit SpatialRel.

GeoJSON or Esri JSON

GeoJSON by default; switch to Esri JSON for richer metadata. Feature.Attrs() returns attributes uniformly regardless of the response format.

Zero deps, typed errors

Pure Go, no third-party imports. ArcGIS often reports failures with HTTP 200 and an error envelope — these surface as a typed *APIError you can match with errors.As.

usage

Struct or fluent — same result.

Both compile to identical QueryParams. Use the struct form when you want something explicit and serializable; the builder when you want it to read like a sentence.

// Struct style — explicit, serializable
fs, err := client.Query(ctx, arcgis.QueryParams{
    LayerID:  7,
    Where:    "STAGE = 4",
    Fields:   []string{"BLOCK_NAME", "STAGE"},
    PageSize: 100,
})
// Fluent style — readable, chainable
features, err := client.Layer(7).Query().
    Where("STAGE = 4").
    Fields("BLOCK_NAME", "STAGE").
    WithinEnvelope(18.4, -34.0, 18.6, -33.8).
    All(ctx)
// Cheap pre-flight: count, then IDs
n, _  := client.Layer(7).Query().Where("1=1").Count(ctx)
ids, _ := client.Layer(7).Query().IDs(ctx)
// ArcGIS errors arrive as HTTP 200 + envelope
var apiErr *arcgis.APIError
if errors.As(err, &apiErr) {
    fmt.Println(apiErr.Code, apiErr.Message)
}
  • Query / .First
  • QueryAll / .All
  • QueryCount / .Count
  • QueryIDs / .IDs
  • WithToken
  • WithTimeout
  • WithHTTPClient

install

Add it to your module.

One import, no transitive dependencies. Requires Go 1.26+.

go get go get github.com/richardwooding/go-arcgis
import import "github.com/richardwooding/go-arcgis"

Need verified City of Cape Town layer IDs? The companion capetown-opendata package ships ready-made constructors. A PHP port tracks this design too.