Stephen
Hackett-Delaney
Full Stack Software Engineer
Keeping Google Places API Cost Near Zero
February 19, 2026
Google Places API has a reputation for surprise bills. The reputation is earned — if you call it naively, costs add up fast. But for a small app, you can keep it near zero with three decisions made upfront.
I'm building a restaurant tracker PWA. Users search for restaurants via Places autocomplete, pick one, and it gets saved to the database. Here's how I kept the API cost negligible.
Decision 1: Session Tokens
Without session tokens, every autocomplete request is billed separately. A user types "maenam vancou" — that's potentially seven billable requests.
Session tokens bundle all the autocomplete requests in a single search session into one unit. While the user is typing, the requests are free. You only pay when they pick a result and you fetch the Place Details.
Generate a UUID when the sheet opens, include it in every autocomplete request, then pass it to the Place Details call. After that call, generate a new token for the next search.
const sessionTokenRef = useRef<string>(crypto.randomUUID())
// In autocomplete fetch:
body: JSON.stringify({
input,
sessionToken: sessionTokenRef.current,
})
// In place details fetch:
`https://places.googleapis.com/v1/places/${placeId}?sessionToken=${sessionTokenRef.current}`
// After details call returns:
sessionTokenRef.current = crypto.randomUUID()Cost without session tokens: billed per keystroke. Cost with: one Place Details charge per restaurant selected, regardless of how much the user typed.
Decision 2: Basic Fields Only
Place Details (New) has three pricing tiers based on which fields you request. The expensive ones — reviews, photos, opening hours, ratings — push you into Advanced or Preferred tier.
We only need three fields: location (lat/lng), display name, and formatted address. All three are Basic tier, currently ~$0.017 per call.
Enforce this with the X-Goog-FieldMask header:
headers: {
'X-Goog-Api-Key': apiKey,
'X-Goog-FieldMask': 'location,displayName,formattedAddress',
}If you don't set a field mask, Google returns everything and bills you for the highest tier in the response. Always set it explicitly.
Decision 3: Cache in Your Database
Once a restaurant is fetched and saved, you never need to call Places again for that restaurant. Store everything you need — place ID, name, address, lat/lng — in your own table and upsert on place_id.
await supabase
.from('restaurants')
.upsert(
{ place_id, name, address, lat, lng },
{ onConflict: 'place_id' },
)The second user who adds the same restaurant hits your database, not the API. At small scale, this means most additions are free — you're only calling Places for restaurants that haven't been added before.
What This Costs in Practice
With the $200/month Google credit and Basic tier pricing, you get roughly 11,700 unique restaurant lookups per month before paying anything. For a private app used by a handful of people, that's effectively unlimited.
The bill only becomes real if you're adding thousands of new restaurants per month — at which point you have a different kind of problem.
The Checklist
Before your first Places API call:
- Generate a session token on search open, pass it in autocomplete and place details requests, rotate after each selection
- Set
X-Goog-FieldMaskto only the fields you actually use - Store everything returned in your database, upsert on
place_id - Restrict your API key to your domain and to Places API (New) only
© 2026. All rights reserved