[BREAKGLASS] Modern Google Places CLI in Go
https://goplaces.sh/
* feat: add userRatingCount to search, nearby, and details Add the userRatingCount field from the Google Places API (New) to PlaceSummary and PlaceDetails types. This allows consumers to see the total number of user ratings for each place. Changes: - Add userRatingCount to search, nearby, and details field masks - Add UserRatingCount field to placeItem payload struct - Add UserRatingCount to PlaceSummary and PlaceDetails types - Map the field in mapPlaceSummary and mapPlaceDetails - Display rating count in CLI output as 'Rating: 4.5 (532)' - Include as 'user_rating_count' in JSON output * fix: handle rating-count rendering fallback and add regressions --------- Co-authored-by: Peter Steinberger <steipete@gmail.com> |
||
|---|---|---|
| .github/workflows | ||
| cmd/goplaces | ||
| docs | ||
| internal/cli | ||
| scripts | ||
| .gitignore | ||
| .golangci.yml | ||
| .goreleaser.yml | ||
| autocomplete.go | ||
| CHANGELOG.md | ||
| client_test.go | ||
| client.go | ||
| details.go | ||
| e2e_test.go | ||
| errors_test.go | ||
| errors.go | ||
| go.mod | ||
| go.sum | ||
| LICENSE | ||
| limits.go | ||
| Makefile | ||
| mapping.go | ||
| nearby.go | ||
| payloads.go | ||
| photo.go | ||
| price_levels.go | ||
| README.md | ||
| request_helpers.go | ||
| resolve.go | ||
| route_test.go | ||
| route.go | ||
| search.go | ||
| types.go | ||
| validation.go | ||
📍 goplaces — Modern Places for Go
Modern Go client + CLI for the Google Places API (New). Fast for humans, tidy for scripts.
Highlights
- 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).
Install / Run
Latest release: v0.2.1 (2026-01-23).
- Homebrew:
brew install steipete/tap/goplaces - Go:
go install github.com/steipete/goplaces/cmd/goplaces@latest - Source:
make goplaces
Config
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)
Getting a Google Places API Key
-
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!
CLI
Long flags accept --flag value or --flag=value (examples use space).
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 US
Pagination:
goplaces search "pizza" --page-token "NEXT_PAGE_TOKEN"
Autocomplete:
goplaces autocomplete "cof" --session-token "goplaces-demo" --limit 5 --language en --region US
Nearby search:
goplaces nearby --lat 47.6062 --lng -122.3321 --radius-m 1500 --type cafe --limit 5
Route search:
goplaces route "coffee" --from "Seattle, WA" --to "Portland, OR" --max-waypoints 5
Details (with reviews):
goplaces details ChIJN1t_tDeuEmsRUsoyG83frY4 --reviews
Details (with photos):
goplaces details ChIJN1t_tDeuEmsRUsoyG83frY4 --photos
Photo URL:
goplaces photo "places/PLACE_ID/photos/PHOTO_ID" --max-width 1200
Resolve:
goplaces resolve "Riverside Park, New York" --limit 5
JSON output:
goplaces search "sushi" --json
Library
boolPtr := 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,
})
Notes
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.
Testing
make lint test coverage
E2E tests (optional)
export GOOGLE_PLACES_API_KEY="..."
make e2e
Optional 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