Stephen

Hackett-Delaney

Full Stack Software Engineer

Articles

Keeping Google Places API Cost Near Zero

google-placesapicostsupabase

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-FieldMask to 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