Developer guide · Places API (New)
Google Places API: get ratings and reviews with Node.js
Working Node.js code to find a business, read its Google rating and newest reviews, check many locations on a schedule, and alert on bad reviews, with the cost tips I use in production.
1. Set up a key
In Google Cloud Console, create a project, enable Places API (New), add billing, and create an API key. Restrict the key to the Places API. For server code, also restrict it by IP if you can; for a browser key, restrict it to your domain. Keep the key in an environment variable:
export PLACES_KEY="your-key"
The examples use Node.js 18 or newer, which has fetch built in. No packages needed.
2. Find a Place ID by name
Every business on Google Maps has a Place ID. Text Search turns a name and city into one. The X-Goog-FieldMask header says which fields you want, and Google bills by the fields you ask for, so ask only for what you need.
const KEY = process.env.PLACES_KEY;
async function findPlace(query) {
const res = await fetch('https://places.googleapis.com/v1/places:searchText', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Goog-Api-Key': KEY,
'X-Goog-FieldMask': 'places.id,places.displayName,places.formattedAddress',
},
body: JSON.stringify({ textQuery: query, pageSize: 5 }),
});
if (!res.ok) throw new Error(`Places search ${res.status}: ${await res.text()}`);
const { places = [] } = await res.json();
return places.map(p => ({ id: p.id, name: p.displayName?.text, address: p.formattedAddress }));
}
console.log(await findPlace('Blue Bottle Coffee, Oakland'));
Save the ID with each location in your database. You only need to search once per location. Don't want to write code for this step? The review link generator finds the Place ID by name too.
3. Get the rating, review count and newest reviews
async function getReviews(placeId) {
const res = await fetch(`https://places.googleapis.com/v1/places/${encodeURIComponent(placeId)}?languageCode=en`, {
headers: {
'X-Goog-Api-Key': KEY,
'X-Goog-FieldMask': 'id,displayName,rating,userRatingCount,reviews,googleMapsUri',
},
});
if (!res.ok) throw new Error(`Place details ${res.status}: ${await res.text()}`);
const p = await res.json();
return {
id: p.id,
name: p.displayName?.text,
rating: p.rating ?? null, // e.g. 4.7
total: p.userRatingCount ?? 0, // number of ratings
maps: p.googleMapsUri,
reviews: (p.reviews || []).map(r => ({
stars: r.rating,
text: r.text?.text || r.originalText?.text || '',
author: r.authorAttribution?.displayName,
when: r.publishTime, // ISO date
link: r.googleMapsUri,
})),
};
}
A result looks like this:
{ "name": "Downtown", "rating": 4.7, "total": 1284,
"reviews": [ { "stars": 2, "text": "Long queue, only one cashier open.", "when": "2026-10-14T09:12:00Z" }, ... ] }
4. Many locations at once
Run the requests a few at a time, not all together, and keep going when one location fails:
async function checkAll(placeIds, parallel = 4) {
const out = [], queue = [...placeIds];
async function worker() {
while (queue.length) {
const id = queue.shift();
try { out.push(await getReviews(id)); }
catch (e) { out.push({ id, error: e.message }); }
}
}
await Promise.all(Array.from({ length: parallel }, worker));
return out.sort((a, b) => (a.rating ?? 9) - (b.rating ?? 9)); // weakest branch first
}
5. Keep the cost low
- Ask only for the fields you show. Fields like
reviewsandratingare in a higher pricing tier thanidor the name. If you only need the rating, leavereviewsout. - Check on a schedule, not on every page view. A daily or weekly job (cron, GitHub Actions, a serverless scheduler) writes results to your database; your dashboard reads the database.
- Cache server-side. If a page must call Google live, cache each place for some minutes so many visitors share one request.
- Set a budget alert and a quota in Google Cloud, so a bug can't turn into a big bill.
SemuaCabang uses about five Google calls per branch per month this way. Check Google's current pricing page for the numbers for your volume.
6. Send new bad reviews to Telegram
Create a bot with @BotFather, get its token, and send yourself a message to find your chat ID. Then remember which reviews you've already sent:
async function alertBadReviews(place, seen, { token, chatId }) {
for (const r of place.reviews) {
const key = `${place.id}:${r.when}:${r.author}`;
if (r.stars > 3 || seen.has(key)) continue;
seen.add(key);
await fetch(`https://api.telegram.org/bot${token}/sendMessage`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ chat_id: chatId, text: `${'★'.repeat(r.stars)} ${place.name}\n"${r.text}"\n${r.link || place.maps}` }),
});
}
}
Store seen in a file or database between runs, so each review is sent once.
Limits to know
- At most 5 reviews per place per request, chosen by Google. Checking often catches new ones, but you won't get the full history.
- For every review of locations you own or manage, the Google Business Profile API is the right tool. It needs Google's approval for API access.
- Show the Google attribution and the review author's name when you display reviews, as Google's policies require.
Questions
Can the Places API return all reviews of a place?
No. Place Details returns at most 5 reviews. For all reviews of locations you own or manage, use the Google Business Profile API after Google approves your access.
Do I need the old or the new Places API?
Use Places API (New) for new projects: it uses field masks, so you only pay for the fields you ask for. Place IDs are the same in both.
Can I call the Places API from the browser?
You can with a restricted key, but a server or serverless function is safer: the key stays secret and you can cache results so visitors don't trigger paid requests.
Can I store Google reviews in my database?
Google's terms limit how long you may cache most Places content. Place IDs can be stored indefinitely. Check the current Google Maps Platform terms for your use case before storing review text long term.