Google Places Text Search
If you are building a mapping application, you need to know about the Google Maps Text Search API, which allows you to search for places using flexible, free-form text queries. This could be an exact address like “555 West Hastings Street, Vancouver” or even something more general like “best dim sum in Richmond”. The API provides detailed information on places that match your query, making it easy to access specific location data tailored to your app's needs.
In this final blog post in my Google Places API tutorial series, I'll show you how to use the Text Search API. We’ll explore worked examples, cover pricing, and build an LLM inspired query engine powered by Google Text Search (demo / source code - coming soon!) to help you easily find detailed information about any location in the Google Maps database.
Part 1: Finding the right place with the Google Places API
Part 2: Google address autocomplete with the Places API
Part 3: Google Place Details and Place Photos API
Part 4: Google Nearby Search API
Part 5: Google Places Text Search (this article)
What is the Google Places Text Search API?
The Google Places Text Search API, often called Google Text Search, retrieves information about a place based on its name or address without requiring a place_id
. This differs from the Google Place Details API, which specifically needs a place_id
to work.
Google Places API Text Search example
Similar to other Google Maps Platform APIs, Google Text Search works directly on https://maps.google.com/. To try it out, type in an address like "555 West Hastings Street, Vancouver" without selecting any of the Places Autocomplete suggestions, and hit the return key.
You'll immediately see details about the place associated with that address displayed on the screen. Making the same query using the Text Search API is just as easy.
Method: GET
https://maps.googleapis.com/maps/api/place/textsearch/json
?query={query}&
type={type}&
key={YOUR_API_KEY}
{query} is the text string you want to search for e.g. "555 West Hastings Street, Vancouver".
{type} is the place type that you can use to restrict search results by. For instance, if you only want to search for restaurants, you can set {type} to type=restaurant
. Only one type may be specified. If more than one type is provided, all types following the first entry are ignored.
{YOUR_API_KEY} is a valid Google Maps API key with the Places API enabled.
The Google Text Search API (see documentation) also includes radius
and region
parameters to help narrow down search results. However, these are often less necessary, as you can simply include location context within the search text itself. For instance, a query like "ramen restaurants in Tokyo" will automatically focus results on ramen spots in Tokyo, Japan, without needing additional parameters.
Here's what an API call to Text Search for “555 West Hastings Street, Vancouver” looks like:
Method: GET
https://maps.googleapis.com/maps/api/place/textsearch/json?query=555%20west%20hastings%20street%20vancouver%20bc&key=YOUR_API_KEY
Response
{
"html_attributions": [],
"results": [
{
"formatted_address": "555 W Hastings St, Vancouver, BC V6B 4N4, Canada",
"geometry": {
"location": {
"lat": 49.2846966,
"lng": -123.1119349
},
"viewport": {
"northeast": {
"lat": 49.28592632989272,
"lng": -123.1108964701073
},
"southwest": {
"lat": 49.28322667010728,
"lng": -123.1135961298927
}
}
},
"icon": "https://maps.gstatic.com/mapfiles/place_api/icons/v1/png_71/geocode-71.png",
"icon_background_color": "#7B9EB0",
"icon_mask_base_uri": "https://maps.gstatic.com/mapfiles/place_api/icons/v2/generic_pinlet",
"name": "555 W Hastings St",
"photos": [
{
"height": 4624,
"html_attributions": [
"<a href=\"https://maps.google.com/maps/contrib/112068941878962933054\">Pushpinder</a>"
],
"photo_reference": "AdCG2DM2GU-DiP2gcwzIQG7Qord6xnWFqrsP_4n0DmoLeoyU29Ni4bQppCQYrp1P1NZ_Jh3vXxv2EVO6YbG361l92pLhwKXRtG-v3PLGI1JPfbuHvw3rNJeL6njBCDVQcvVuZR_LkW0741GspTIX3srCWcu225YCXWMKJ3yudPI9vE7v8cZl",
"width": 3472
}
],
"place_id": "ChIJUcKFZ3hxhlQREJGVU1foPaE",
"reference": "ChIJUcKFZ3hxhlQREJGVU1foPaE",
"types": [
"premise"
]
}
],
"status": "OK"
}
In the results
array of the response, each place
object has a:
name
, the human-readable name for the returned place
.
formatted_address
, a human-readable, properly formatted address of the place
.
geometry.location
, which gives us its lat
(latitude) and lng
(longitude).
photos.photo_reference
, a unique identifier that represents a specific photo associated with a place. In a Google Text Search API response, this identifier always corresponds to the top-ranked photo for that place
on Google Maps.
place_id
, which is its unique place ID. It can be used to retrieve the place's name, address, photos, reviews etc using the Place Details API.
Google Places API Text Search "place at" trick
To retrieve business listing information in the same Text Search API call, prepend "place at" to the address, like this: "place at 555 West Hastings Street, Vancouver".
Method: GET
https://maps.googleapis.com/maps/api/place/textsearch/json?query=place%20at%20555%20west%20hastings%20street%20vancouver%20bc&
key=YOUR_API_KEY
Response
{
"html_attributions": [],
"results": [
{
"business_status": "OPERATIONAL",
"formatted_address": "555 W Hastings St, Vancouver, BC V6B 4N6, Canada",
"geometry": {
"location": {
"lat": 49.2845838,
"lng": -123.1121818
},
"viewport": {
"northeast": {
"lat": 49.28586082989273,
"lng": -123.1110061201073
},
"southwest": {
"lat": 49.28316117010728,
"lng": -123.1137057798927
}
}
},
"icon": "https://maps.gstatic.com/mapfiles/place_api/icons/v1/png_71/restaurant-71.png",
"icon_background_color": "#FF9E67",
"icon_mask_base_uri": "https://maps.gstatic.com/mapfiles/place_api/icons/v2/restaurant_pinlet",
"name": "Top Of Vancouver Revolving Restaurant",
"opening_hours": {
"open_now": true
},
"photos": [
{
"height": 2268,
"html_attributions": [
"<a href=\"https://maps.google.com/maps/contrib/112618640134446180937\">A Google User</a>"
],
"photo_reference": "AdCG2DPmrhrvMSTAH_uFvmXj-vKEyRD30G-wIEiZEbS7IJ9P2OHW0gngpDWY4GoA8X9J_ANtF6k0m4xb3vjEgWMCzmCJvsaCMTcLTMPjTBqjYsg1x-3OD-vMTWrHMtOUrEkMNBh8YDxcYSi3W4Dn5P9srFyPVvmrwAClOQvNAO6iEuoxY8E",
"width": 4032
}
],
"place_id": "ChIJfe2sYHhxhlQRYY79aUl69vw",
"plus_code": {
"compound_code": "7VMQ+R4 Vancouver, British Columbia, Canada",
"global_code": "84XR7VMQ+R4"
},
"rating": 4.1,
"reference": "ChIJfe2sYHhxhlQRYY79aUl69vw",
"types": [
"restaurant",
"food",
"point_of_interest",
"establishment"
],
"user_ratings_total": 3046
},
{
"business_status": "OPERATIONAL",
"formatted_address": "HARBOUR CENTRE, 555 W Hastings St L1, Vancouver, BC V6B 4N6, Canada",
"geometry": {
"location": {
"lat": 49.2849831,
"lng": -123.1115944
},
"viewport": {
"northeast": {
"lat": 49.28639587989272,
"lng": -123.1102140701073
},
"southwest": {
"lat": 49.28369622010727,
"lng": -123.1129137298927
}
}
},
"icon": "https://maps.gstatic.com/mapfiles/place_api/icons/v1/png_71/shopping-71.png",
"icon_background_color": "#4B96F3",
"icon_mask_base_uri": "https://maps.gstatic.com/mapfiles/place_api/icons/v2/shopping_pinlet",
"name": "BCLIQUOR Harbour Centre",
"opening_hours": {
"open_now": true
},
"photos": [
{
"height": 3024,
"html_attributions": [
"<a href=\"https://maps.google.com/maps/contrib/114288042566495812069\">Kevin T</a>"
],
"photo_reference": "AdCG2DMqgNZXaqSTV_U-rusLEwZyASb_e5uS8e0-YSziEbuilJC3HsAgIC81Dh1lkTmTdym1g8gsDpbCaExfCbCwZ62Zq0-nKbYwElNkRvAS0J5hehLLDjQ0NNH9KgsomEHs4vKMt2UoXoYvCS3z7QqiDzFzq5xyZ0a69lGpSl3A_bDC_fjy",
"width": 4032
}
],
"place_id": "ChIJfe2sYHhxhlQRqFq5z44TMvM",
"plus_code": {
"compound_code": "7VMQ+X9 Vancouver, British Columbia, Canada",
"global_code": "84XR7VMQ+X9"
},
"rating": 4.1,
"reference": "ChIJfe2sYHhxhlQRqFq5z44TMvM",
"types": [
"liquor_store",
"store",
"point_of_interest",
"establishment"
],
"user_ratings_total": 620
},
// 18 more entries
],
"status": "OK"
}
Since the Google Text Search API results
now include business listings from a restaurant
, liquor_store
and cafe
, we get additional metadata we can display in our app:
rating
is the average rating based on aggregated user reviews.
user_ratings_total
represents the total number of reviews for the place
.
photo_reference
is a unique identifier for the primary photo linked to a place. You can use it with the Place Photos API to retrieve the image.
Google Text Search free-form text example
The Text Search API also allows you to use free-form text in your query. You can now ask any kind of complex question about places to go to, or things to do, and expect Google Maps to help you with that. For example, the search string, "Best ramen in Vancouver" searches for listings that have "ramen" mentioned in the place name, description or reviews, and sets a location bias to Vancouver, BC in Canada.
Method: GET
https://maps.googleapis.com/maps/api/place/textsearch/json?
query=best%20ramen%20in%20vancouver&
key=YOUR_API_KEY
Response
- Jinya Ramen Bar (4.4 ⭐️)
- Ramen Danbo Robson (4.6 ⭐️)
- The Ramen Butcher (4.4 ⭐️)
- Maruhachi Ra-men Westend (4.6 ⭐️)
- Tonkotsu Ramen Tsukiya (4.8 ⭐️)
And 15 more ... As you can see, Text Search results are not based just on average rating or distance from the specified location. Instead, they are based on prominence, which is a measure of a business’s offline popularity. Google does not disclose the exact formula for calculating prominence, but the number and quality of reviews matters, as does the presence of relevant keywords in the listing description.
Google Places API Text Search pricing
Pricing for the Google Text Search API is straightforward. It costs $32 CPM (per thousand requests). This might seem high, but considering that you get valuable fields such as place_id
, rating
, user_rating_total
and photo_reference
included in the response without having to make a separate Place Details API call, it's actually quite reasonable. As always, you can contact a Google Maps Partner for bulk pricing and discounts.
Building a query engine powered by Google Text Search
In this last section, we'll use the Google Text Search API to build a Large Language Model (LLM) inspired query engine. Simply type what you're looking for into the search field (e.g., "Best ramen in Vancouver"), and the results will display on a map, complete with photos and user ratings (live demo: https://places-api-text-search.afi.dev/).
How our code is organized
The places_api_text_search app (coming soon!) is a single-page React application. It features a /frontend
with a search box, map, and results listing, while the /backend
handles calls to the Google Text Search API. As with our other tutorials, we'll be using @vis.gl/react-google-maps to quickly scaffold our app. This library helps integrate Google Maps components into a React app by providing prebuilt components that make it easy to manage map interactions and UI in a way that is consistent with React. You can find full source code and installation instructions to run the app locally in our Github repository (coming soon!).
App.jsx
App.jsx
in the /frontend
folder serves as the main entry point for the application's frontend (above) and logic. It provides a clean, map-centered UI structure with a few key interactive components for our query engine.
/* frontend/App.jsx */
import React, { useState } from "react";
import { APIProvider } from "@vis.gl/react-google-maps";
import Map from "./components/Map";
import LocationSearchPanel from "./components/LocationSearchPanel";
import PlaceList from "./components/PlaceList";
import "./App.scss";
const App = () => {
const [places, setPlaces] = useState([]);
const [activePlace, setActivePlace] = useState({});
const [searchValue, setSearchValue] = useState();
const handlePlaceSelection = async (places) => {
setPlaces(places);
};
const handlePlaceClick = (place) => {
setActivePlace(place || {});
};
return (
<div className="app">
<APIProvider apiKey={process.env.REACT_APP_GOOGLE_API_KEY}>
<div className="location-panel">
<LocationSearchPanel
searchValue={searchValue}
onSearch={setSearchValue}
onPlaceSelect={handlePlaceSelection}
/>
<PlaceList
placeList={places}
activePlace={activePlace}
onPlaceClick={handlePlaceClick}
/>
</div>
<Map
places={places}
activePlace={activePlace}
onPlaceClick={handlePlaceClick}
/>
</APIProvider>
</div>
);
};
export default App;
At the top of App.jsx
, the <APIProvider/>
wrapper component provides Google Maps API context to its children, which enables components within it (such as <Map/>
) to access the Google Maps API using the REACT_APP_GOOGLE_API_KEY
.
<App/> (div.app)
|
|-- <APIProvider/> (Google Maps API context)
| |
| |-- Location Panel (div.location-panel)
| | |
| | |-- <LocationSearchPanel/>
| | | (TextSearch input field)
| |
| |-- <PlaceList/>
| | (Displays list of places)
|
|-- <Map/>
(Displays Google Map with markers for places)
LocationSearchPanel.jsx
LocationSearchPanel.jsx
is a container and presentational component that holds the <PlaceAutocompleteInput/>
Text Search input field and an LLM-inspired chat bubble.
When the user submits a query, it’s saved to the onSearch
prop, which bubbles up to App.jsx
and is assigned to searchValue
. This searchValue
is passed back to <LocationSearchPanel />
and then into useTypingEffect()
to simulate a ChatGPT-style typing effect.
/* frontend/components/LocationSearchPanel.jsx */
import React from "react";
import PlaceAutocompleteInput from "./PlaceAutocompleteInput";
import "./LocationSearchPanel.scss";
import { ReactComponent as TriangleBubble } from "../assets/images/triangle-bubble.svg";
import useTypingEffect from "../hooks/useTypingEffect";
const LocationSearchPanel = ({ onSearch, onPlaceSelect, searchValue }) => {
const typedText = useTypingEffect(searchValue, 10);
return (
<div className="location-search-panel">
<h3>Ask Google Maps Anything</h3>
<PlaceAutocompleteInput
onSearch={onSearch}
onPlaceSelect={onPlaceSelect}
/>
{!!typedText && (
<>
<div className="break-line"></div>
<div className="search-value">
<TriangleBubble />
{typedText}
</div>
</>
)}
</div>
);
};
export default LocationSearchPanel;
useTypingEffect.js
useTypingEffect.js
defines a custom React hook, useTypingEffect
, which creates a typewriter effect for a given text.
By convention, useTypingEffect
is treated as a custom hook simply because of the use
prefix. Because of this, whenever the value of the displayedText
state variable (the currently displayed portion of the full text string being typed out) is updated via setDisplayedText
, React re-renders the component using this hook, providing the latest displayedText
value to that component.
Here's how it works:
/* frontend/hooks/useTypingEffect.js */
import { useState, useEffect } from "react";
const useTypingEffect = (text, baseSpeed = 100) => {
const [displayedText, setDisplayedText] = useState("");
useEffect(() => {
if (!text) {
setDisplayedText("");
return;
}
let index = 0;
const typeCharacter = () => {
setDisplayedText(text.slice(0, index + 1));
index++;
if (index < text.length) {
const nextChar = text[index];
// Set a random speed, slower for spaces and punctuation
const randomSpeed =
baseSpeed +
(nextChar === " " || [".", ",", "!", "?"].includes(nextChar)
? Math.floor(Math.random() * 120) + 100 // Longer pause for spaces/punctuation
: Math.floor(Math.random() * 70)); // Smaller variation for other characters
setTimeout(typeCharacter, randomSpeed);
}
};
typeCharacter(); // Start typing effect
return () => setDisplayedText(""); // Clean up on text change or unmount
}, [text, baseSpeed]);
return displayedText;
};
export default useTypingEffect;
When useTypingEffect
is called, displayedText
is initialized as an empty string (""
) using useState
. Inside the typeCharacter()
function, displayedText
is updated by slicing the text
up to the current character index and setting this slice as the new value of displayedText
via setDisplayedText
. With each iteration, displayedText
grows one character at a time until it matches text
.
To make the typing effect more realistic, we introduce a random delay by recursively calling the setTimeout()
method with typeCharacter()
paired with a randomSpeed
value. Each time setTimeout()
is called, the value of index
is incremented by 1 until its value reaches the length of the text
string, signaling that the entire text has been displayed.
PlaceAutocompleteInput.jsx
PlaceAutocompleteInput.jsx
contains the text input field and submit button that allows users to search Google Maps using free-form text. Instead of using Place Autocomplete, this component is configured to accept any input, which is then sent directly to the Google Maps Text Search API.
/* frontend/components/PlaceAutocompleteInput.jsx */
import React, { useState } from "react";
import { ReactComponent as SendIcon } from "../assets/images/send.svg";
import "./PlaceAutocompleteInput.scss";
const PlaceAutocompleteInput = ({ onSearch, onPlaceSelect }) => {
const [query, setQuery] = useState("");
const handleInputChange = (e) => {
setQuery(e.target.value);
};
const hasSufficientQueryLength = () => query.length > 2;
const handleSearch = async () => {
if (hasSufficientQueryLength()) {
onSearch(query);
setQuery("");
try {
const response = await fetch(
`${process.env.REACT_APP_BACKEND_URI}/places?query=${query}`
);
const suggestions = await response.json();
onPlaceSelect(suggestions);
} catch (error) {
console.error("Error fetching data:", error);
}
}
};
const handleKeyDown = async (e) => {
if (e.key === "Enter") {
e.preventDefault();
await handleSearch();
}
};
return (
<div className="autocomplete-container">
<input
type="text"
value={query}
onChange={handleInputChange}
onKeyDown={handleKeyDown}
placeholder="What are you looking for?"
/>
<SendIcon
onClick={handleSearch}
style={{ fill: hasSufficientQueryLength() ? "#2594ee" : "#888888" }}
/>
</div>
);
};
export default PlaceAutocompleteInput;
The handleSearch()
function is triggered when the user clicks the <SendIcon/>
or presses [Enter]. If query
is more than 2 chars long, it's sent to the /places
endpoint in our backend for a pass-through call to the Google Text Search API.
- Routing: The server matches the incoming request to predefined routes based on the URL path and HTTP method. Each route is associated with a specific handler function.
- Controller Handling: The route's handler function, often referred to as a controller, contains the business logic to process the request. It may interact with databases or other services as needed.
- PlaceAutoCompleteInput.jsx
- backend/PlaceController.js
- PlaceList.jsx
- PlaceItem.jsx
- Map.jsx
google text search
google places api text search example
google places api text search example
google places api text search
google maps text search api
google text search api
google places text search
text search google
text from google maps