Modern Go client + CLI for the Google Places API (New). Fast for humans, tidy for scripts.
- Text search with filters: keyword, type, open now, min rating, price levels.
- Autocomplete suggestions for places + queries (session tokens supported).
- Nearby search around a location restriction.
- Place photos in details + photo media URLs.
- Route search along a driving path (Routes API).
- Location bias (lat/lng/radius) and pagination tokens.
- Place details: hours, phone, website, rating, price, types.
- Optional reviews in details (
--reviews/IncludeReviews). - Resolve free-form location strings to candidate places.
- Locale hints (language + region) across search/resolve/details.
- Typed models, validation errors, and API error surfacing.
- CLI with color human output +
--json(respectsNO_COLOR).
- Homebrew:
brew install steipete/tap/goplaces - Go:
go install github.com/steipete/goplaces/cmd/goplaces@latest - Source:
make goplaces
export GOOGLE_PLACES_API_KEY="..."Optional overrides:
GOOGLE_PLACES_BASE_URL(testing, proxying, or mock servers)GOOGLE_ROUTES_BASE_URL(testing Routes API or proxying)
-
Create a Google Cloud Project
- Go to Google Cloud Console
- Click "Select a project" → "New Project"
- Name it (e.g., "goplaces") and click "Create"
-
Enable the Places API (New)
- Go to APIs & Services → Library
- Search for "Places API (New)" — make sure it says (New)!
- Click "Enable"
-
Enable the Routes API (for
route)- Search for "Routes API"
- Click "Enable"
-
Create an API Key
- Go to APIs & Services → Credentials
- Click "Create Credentials" → "API Key"
- Copy the key
-
Set the Environment Variable
export GOOGLE_PLACES_API_KEY="your-api-key-here"
Add to your
~/.zshrcor~/.bashrcto persist. -
(Recommended) Restrict the Key
- Click on the key in Credentials
- Under "API restrictions", select "Restrict key" → "Places API (New)"
- Set quota limits in Quotas
Note: The Places API has usage costs. Check pricing and set budget alerts!
goplaces [--api-key=KEY] [--base-url=URL] [--routes-base-url=URL] [--timeout=10s] [--json] [--no-color] [--verbose]
<command>
Commands:
autocomplete Autocomplete places and queries.
nearby Search nearby places by location.
search Search places by text query.
route Search places along a route.
details Fetch place details by place ID.
photo Fetch a photo URL by photo name.
resolve Resolve a location string to candidate places.
Search with filters + location bias:
goplaces search "coffee" --min-rating 4 --open-now --limit 5 \
--lat 40.8065 --lng -73.9719 --radius-m 3000 --language en --region USPagination:
goplaces search "pizza" --page-token "NEXT_PAGE_TOKEN"Autocomplete:
goplaces autocomplete "cof" --session-token "goplaces-demo" --limit 5 --language en --region USNearby search:
goplaces nearby --lat 47.6062 --lng -122.3321 --radius-m 1500 --type cafe --limit 5Route search:
goplaces route "coffee" --from "Seattle, WA" --to "Portland, OR" --max-waypoints 5Details (with reviews):
goplaces details ChIJN1t_tDeuEmsRUsoyG83frY4 --reviewsDetails (with photos):
goplaces details ChIJN1t_tDeuEmsRUsoyG83frY4 --photosPhoto URL:
goplaces photo "places/PLACE_ID/photos/PHOTO_ID" --max-width 1200Resolve:
goplaces resolve "Riverside Park, New York" --limit 5JSON output:
goplaces search "sushi" --jsonboolPtr := func(v bool) *bool { return &v }
floatPtr := func(v float64) *float64 { return &v }
client := goplaces.NewClient(goplaces.Options{
APIKey: os.Getenv("GOOGLE_PLACES_API_KEY"),
Timeout: 8 * time.Second,
})
search, err := client.Search(ctx, goplaces.SearchRequest{
Query: "italian restaurant",
Filters: &goplaces.Filters{
OpenNow: boolPtr(true),
MinRating: floatPtr(4.0),
Types: []string{"restaurant"},
},
LocationBias: &goplaces.LocationBias{Lat: 40.8065, Lng: -73.9719, RadiusM: 3000},
Language: "en",
Region: "US",
Limit: 10,
})
details, err := client.DetailsWithOptions(ctx, goplaces.DetailsRequest{
PlaceID: "ChIJN1t_tDeuEmsRUsoyG83frY4",
Language: "en",
Region: "US",
IncludeReviews: true,
})
autocomplete, err := client.Autocomplete(ctx, goplaces.AutocompleteRequest{
Input: "cof",
SessionToken: "goplaces-demo",
Limit: 5,
Language: "en",
Region: "US",
})
nearby, err := client.NearbySearch(ctx, goplaces.NearbySearchRequest{
LocationRestriction: &goplaces.LocationBias{Lat: 47.6062, Lng: -122.3321, RadiusM: 1500},
IncludedTypes: []string{"cafe"},
Limit: 5,
})
photo, err := client.PhotoMedia(ctx, goplaces.PhotoMediaRequest{
Name: "places/PLACE_ID/photos/PHOTO_ID",
MaxWidthPx: 1200,
})
route, err := client.Route(ctx, goplaces.RouteRequest{
Query: "coffee",
From: "Seattle, WA",
To: "Portland, OR",
MaxWaypoints: 5,
})Filters.Typesmaps toincludedType(Google accepts a single value). Only the first type is sent.- Price levels map to Google enums:
0(free) →4(very expensive). - Reviews are returned only when
IncludeReviews/--reviewsis set. - Photos are returned only when
IncludePhotos/--photosis set. - Route search requires the Google Routes API to be enabled.
- Field masks are defined alongside each request (e.g.
search.go,details.go,autocomplete.go). - The Places API is billed and quota-limited; keep an eye on your Cloud Console quotas.
make lint test coverageexport GOOGLE_PLACES_API_KEY="..."
make e2eOptional env overrides:
- Use a custom endpoint (proxy/mock):
GOOGLE_PLACES_E2E_BASE_URL - Override the search text used in E2E:
GOOGLE_PLACES_E2E_QUERY - Override language code for E2E:
GOOGLE_PLACES_E2E_LANGUAGE - Override region code for E2E:
GOOGLE_PLACES_E2E_REGION