When you are using the Google Maps API, you can add your own locations to the map to show as pins for your users. By default, the picture used for the pin (called a glyph) is a standard red map pin, like you are used to seeing on Google Maps. But what if you want to show different types of pictures for different types of locations? Or in my case, what if you want to show different types of pin glyphs based on the information about the location?
In this article I’m going to show:
- How you can use your location information (latitude and longitude and a name) to look up the details about a location in the Places API.
- How to use Places data to find a Google Map glyph/SVG so that your locations feel like native Google Map locations.
This article does not contain the full working code samples, but is focused on the key elements to help you understand what you need to do to work with the Places API to customize a Pin. For full walkthroughs of all pieces, please reference the Google documentation on the Google Maps JavaScript API for all sorts of tutorials and deep-dives. I also have some references listed at the end of the article!
Tech stack: Next.js, TypeScript, Google Maps JavaScript API, Places (New) API
Adding Your Markers/Pins to the Map
My suggestion for your application is to start with a datasource that you own with a list of locations. Your locations might be retail stores, corporate business locations, events, or something else. Whatever they are you should have these locations stored somewhere with GIS data or latitude/longitude data. In this example, I assume you have a collection of locations which is an array of objects that have: id, displayName, latitude, and longitude.
There are quite a few ways to add a Google Map to your application, so I won’t fully cover that in this article. I suggest using AdvancedMarker and you might want to create your own component to hold this AdvancedMarker, or perhaps you put it directly into your page logic. The example code below does the following:
- Specify an API Key which will be used to authenticate to the service
- Create a Map container for all the pins.
- Inside the <Map> iterate over the collection of locations.
- For each location, create a new AdvancedMarkerComponent (this is my custom advanced marker component)
<APIProvider apiKey={process.env.NEXT_PUBLIC_GOOGLE_MAPS_API_KEY as string}>
<Map>
{myLocations && myLocations.map((location, index) => {
let lat = location.latitude;
let lng = location.longitude;
if (!lat || !lng) return null;
return (
<AdvancedMarkerComponent
key={`${location.id}`}
index={index}
location={location}
/>
);
})}
{children}
</Map>
</APIProvider>
At this point, we haven’t yet created any pins, just the structure of how to start adding markers onto the Map. I’ve trimmed down this code sample to a very bare-bones block of React that can display advanced markers. You will likely add a lot more to your logic to handle zoom levels, initial positions, click events, etc. but I wanted to focus the example in on the necessary bits for getting a marker into the map.
ℹ️ Want to learn more about adding a map to your application? See the Google developer docs to Add a Google map to a React app.
The Pin Component
However you have implemented the Google Maps API onto your page, at some point you will have introduced a Pin to display a visual of the location on the map. In the example below, I’m customizing a few properties of the pin to give it my own look and feel.
<Pin
background="#3366FF"
scale={(isSelected) ? 1.2 : 1}
borderColor="white"
glyph={place ? new URL(getMapPinIconURI(location, place)) : `${index + 1}`}
glyphColor="white"
/>
Breaking down the code:
- background: This is the colour that will be used to fill in the pin. The default is the Google ‘red’ but you can set it to be any colour you want.
- scale: This sets how large the pin is, in relation to other pins. You might not need to set this, but in my use case I wanted users to click on it and have it become bigger while selected. The ‘isSelected’ property references that custom functionality, which won’t be shown in this article. You might have a different use case for how you might alter scale of the pin, perhaps when hovering or when interacting with something else on the page.
- borderColor: As you might suspect, this sets the colour that is on the edge of the pin. I’m using white in this example because I have a darker colour for the background, but if you were using a white pin I would suggest having a dark border colour so that the pin has a strong definition against the map behind it.
- glyph: This is the image that will be displayed in the middle of the pin. You can set this to text (like a number) or give it a URI to load an image. In the example code here, I’m checking to see if I have any place information from the Google Maps API and if I do I use that to determine an icon, but otherwise I default to showing a number based on the index in the array of locations.
- glyphColor: This sets the colour to be used for the number or icon set on the glyph property. I’m using white since I have a dark background and the default seems to be black. I have found when resizing my browser window that the Google Maps component will often default back to the default glyphColor, so be aware of that.
Retrieving Data from the Places API
In order to set that glyph icon based on what Google’s data has, we need to have a Place information about our location, from Google. The Places API allows us to get a variety of information that is stored in Google’s data service such as the icon for the location, its display name, location information, photos, the types associated to the location, and a bunch of other fields. You can see a full list of what you can extract in the Google Maps Nearby Search API documentation.
Here is an example method of how you could extract some information from the Places API for your location (assuming you have a latitude and longitude). Note that I’m also using a stored name of my location because some coordinate points in busy locations have multiple Place results nearby so the name is helpful at finding the correct one.
useEffect(() => {
if (!map || !location.address) return;
const initializePlaceService = async () => {
try {
const { Place } = await google.maps.importLibrary("places") as google.maps.PlacesLibrary;
if (!location || !location.address) return;
//Convert our address latitude and longitude into the LatLng object expected by the Places API
const latlng= new google.maps.LatLng(
location.address.latitude || 0,
location.address.longitude || 0
);
//Build up the parameters of the search request. We will use a name of the location and the lat/long coordinates.
const request = {
textQuery: location.displayName || '',
locationBias: new google.maps.Circle({
center: latlng,
radius: 50
}),
fields: ['iconBackgroundColor', 'svgIconMaskURI', 'displayName']
};
const nearbyPlaces = await Place.searchByText(request);
//Grab the first place that is the closest match to the search and fetch the data
if (nearbyPlaces.places?.[0]) {
const place = nearbyPlaces.places[0];
await place.fetchFields({
fields: ['iconBackgroundColor', 'svgIconMaskURI', 'displayName', 'location', 'types']
});
setPlace(place);
}
} catch (error) {
console.error("Error initializing Place Service:", error);
}
};
initializePlaceService();
}, [map, location]);
Breaking down the code
- PlacesLibrary: The initialization of the PlacesLibrary allows us to load the object that is going to be used to send the queries. This uses the new Places service.
- google.maps.LatLng: When building up the search request, we can specify a coordinate to center the search on. This requires a latitude and longitude in a google.maps.LatLong object type. Before building the request, you’ll want to convert your location’s latitude and longitude into this object type.
- The ‘request’ for the search: Before invoking the search, we can build up all the parameters we want to pass into the API request. In this example, I’m passing a few parameters in:
- textQuery: This allows the search to better find the location. There may be multiple businesses or places at a particular coordinate. Passing a text query allows you to restrict the results that come back to a better match.
- locationBias: This parameter specifies the latitude/longitude point where we want to run the search. The LatLng object we built up gets used here, and then we can specify a radius (in meters) around that point for how far we want to search from the latitude. I would recommend specifying the radius because sometimes your latitude/longitude data might not match exactly to Google’s latitude/longitude in their Places database, so this allows you to handle any slight offsets.
- fields: This specifies the data we want to return on the search. Depending on which field you specify, this also triggers different tiers of the request. Check the API docs to see which fields are at which tier: https://developers.google.com/maps/documentation/places/web-service/text-search#text-search-requests
- Place.searchByText: As you might suspect, this executes the text search on the Place API, using the request we just built up. The results will be a list of places that match our request parameters.
- fetchFields: Some information cannot be returned back by the search, so this follow-up request allows us to take the match we got back from the text search and request the information we need. In this case, I particularly want to know the icon information and the types associated to the location. This will help us with determining what icon to show on the map.
Using Google Icons for Glyphs
Earlier in the article, you may have noticed that the Pin was setting the glyph property based on a function called getMapPinIconURI. This is a custom function I built out, you can name your function whatever works for you, but I’ve provided the concept below of what I’m doing for my application.
The general idea is that we can take the information from the Place and use it to display a Google-styled icon. The Place itself may have an SVG icon we can use, and that’s the simplest approach. If the Place itself has no icon or a generic icon, we can try to use the types associated to a Place to find an appropriate icon. Failing that, we can always return the icon that we want to have as a default.
export const getMapPinIconURI = (location: Location, place: any): string => {
// Handle the case where the location or the place is not found
if(!place || !location) return '';
// If we have a custom icon that's not generic, use it
if(place.svgIconMaskURI && !place.svgIconMaskURI.includes("generic")) {
return place.svgIconMaskURI;
}
// Define our prioritized type mappings
const typeToIconMap: Record<string, string> = {
// Food & Drink
'restaurant': 'https://maps.gstatic.com/mapfiles/place_api/icons/v2/restaurant_pinlet.svg',
'bakery': 'https://maps.gstatic.com/mapfiles/place_api/icons/v2/restaurant_pinlet.svg',
'cafe': 'https://maps.gstatic.com/mapfiles/place_api/icons/v2/restaurant_pinlet.svg',
'bar': 'https://maps.gstatic.com/mapfiles/place_api/icons/v2/restaurant_pinlet.svg',
'candy_store': 'https://maps.gstatic.com/mapfiles/place_api/icons/v2/restaurant_pinlet.svg',
'dessert_shop': 'https://maps.gstatic.com/mapfiles/place_api/icons/v2/restaurant_pinlet.svg',
'chocolate_shop': 'https://maps.gstatic.com/mapfiles/place_api/icons/v2/restaurant_pinlet.svg',
'confectionery': 'https://maps.gstatic.com/mapfiles/place_api/icons/v2/restaurant_pinlet.svg',
// Grocery & Supermarket
'food': 'https://maps.gstatic.com/mapfiles/place_api/icons/v2/shoppingcart_pinlet.svg',
'grocery_or_supermarket': 'https://maps.gstatic.com/mapfiles/place_api/icons/v2/shoppingcart_pinlet.svg',
'grocery_store': 'https://maps.gstatic.com/mapfiles/place_api/icons/v2/shoppingcart_pinlet.svg',
'supermarket': 'https://maps.gstatic.com/mapfiles/place_api/icons/v2/shoppingcart_pinlet.svg',
// Services
'hair_care': 'https://maps.gstatic.com/mapfiles/place_api/icons/v2/convenience_pinlet.svg',
'beauty_salon': 'https://maps.gstatic.com/mapfiles/place_api/icons/v2/convenience_pinlet.svg',
'spa': 'https://maps.gstatic.com/mapfiles/place_api/icons/v2/convenience_pinlet.svg',
// Entertainment
'movie_theater': 'https://maps.gstatic.com/mapfiles/place_api/icons/v2/entertainment_pinlet.svg',
'museum': 'https://maps.gstatic.com/mapfiles/place_api/icons/v2/entertainment_pinlet.svg',
'art_gallery': 'https://maps.gstatic.com/mapfiles/place_api/icons/v2/entertainment_pinlet.svg',
// Health
'pharmacy': 'https://maps.gstatic.com/mapfiles/place_api/icons/v2/medical_pinlet.svg',
'hospital': 'https://maps.gstatic.com/mapfiles/place_api/icons/v2/medical_pinlet.svg',
'doctor': 'https://maps.gstatic.com/mapfiles/place_api/icons/v2/medical_pinlet.svg',
//Sporting Goods
'sporting_goods_store': 'https://maps.gstatic.com/mapfiles/place_api/icons/v2/golf_pinlet.svg',
'sports_equipment_store': 'https://maps.gstatic.com/mapfiles/place_api/icons/v2/golf_pinlet.svg',
'sports_store': 'https://maps.gstatic.com/mapfiles/place_api/icons/v2/golf_pinlet.svg',
'sports_goods_store': 'https://maps.gstatic.com/mapfiles/place_api/icons/v2/golf_pinlet.svg',
'sports_equipment': 'https://maps.gstatic.com/mapfiles/place_api/icons/v2/golf_pinlet.svg',
'sports_goods': 'https://maps.gstatic.com/mapfiles/place_api/icons/v2/golf_pinlet.svg',
};
// Check if we have types and find the first matching type
if (place.types && Array.isArray(place.types)) {
for (const type of place.types) {
if (typeToIconMap[type]) {
return typeToIconMap[type];
}
}
}
// Default to shopping icon if no specific type matches
return 'https://maps.gstatic.com/mapfiles/place_api/icons/v2/shopping_pinlet.svg';
}
Breaking Down the Code
- svgIconMaskURI: One of the fields we can pull is the icon URI that is stored for the Place. Sometimes this is blank nor has a generic icon, but if we have a non-generic value that is probably the most applicable icon we can use.
- typeToIconMap: This mapping takes a bunch of the types that can come back from the Place service and maps them to icons. You can define all the types that are applicable to your locations and then associate the appropriate SVG for that type.
- place.types loop: This iteration searches through the types associated to a Place record and sees if any of them have a matching type. The first one that is found is returned.
- Default icon: At the end, if no match is found in the typeToIconMap, I have a pinlet that I return as my default. Based on the types of locations your application displays, your default might be a different Google pinlet, or you might have a custom SVG you are using.
In my example here, I’ve codified in my own preferences and priorities for which icons to use for which type, so you will want to check your data and determine what icons work best for your locations. If you want to know what pinlets are available, check the Google Docs for Place Icons.
If none of the types match to any of the icons you’d like to use, then you can always return a generic SVG to use.
In this case, I’m using Google Icons for my glyphs, but you might have your own SVG icons that you want to return. This would be a great place to put the URLs to your icons so that you can use your own icons.
Pulling it Together
Now that we’ve got some coverage of the main pieces, here is a review of what you’ll want to do (at a high level):
- Setup your Google Map, including your API key, and add the map component to your page.
- Add logic to make sure that for each one of your locations, you add a Marker and Pin tied to that location.
- For each location, make sure we augment our data with information from the Places API to help us know more about the location category and icons.
- Make sure to add properties to the Pin to customize it to your brand.
- For each Pin, call out to the custom function to retrieve the glyph icon.

Hopefully this article was able to help you with getting some custom icons onto your Google Map pins!
Credits and References
Here are some docs that I found helpful while learning and will provide you with more in-depth details if you want to read more.
- Google Maps JavaScript API documentation (developers.google.com)
- Add a Google map to a React app (developers.google.com)
- Nearby Search (New) | Places API (developers.google.com)
- Place Details | Places API (developers.google.com)
- Place Icons | Places API (developers.google.com)

Leave a comment