Places UI Kit: A ready-to-use library that provides room for customization and low-code development. Try it out, and share yourinput on your UI Kit experience.

AI-powered summaries

This sample lets you search for AI-powered summaries. Some suggested searches:

  • "Hotel" for neighborhood summaries.
  • "EV charging station" for EVCS amenity summaries.
  • Any restaurant or business for place and review summaries.

Read thedocumentation.

TypeScript

// Define DOM elements.constmapElement=document.querySelector('gmp-map')asgoogle.maps.MapElement;constplaceAutocomplete=document.querySelector('gmp-place-autocomplete')asgoogle.maps.places.PlaceAutocompleteElement;constsummaryPanel=document.getElementById('summary-panel')asHTMLDivElement;constplaceName=document.getElementById('place-name')asHTMLElement;constplaceAddress=document.getElementById('place-address')asHTMLElement;consttabContainer=document.getElementById('tab-container')asHTMLDivElement;constsummaryContent=document.getElementById('summary-content')asHTMLDivElement;constaiDisclosure=document.getElementById('ai-disclosure')asHTMLDivElement;constflagContentLink=document.getElementById('flag-content-link')asHTMLAnchorElement;letinnerMap;letmarker:google.maps.marker.AdvancedMarkerElement;asyncfunctioninitMap():Promise<void>{// Request needed libraries.const[]=awaitPromise.all([google.maps.importLibrary('marker'),google.maps.importLibrary('places'),]);innerMap=mapElement.innerMap;innerMap.setOptions({mapTypeControl:false,streetViewControl:false,fullscreenControl:false,});// Bind autocomplete bounds to map bounds.google.maps.event.addListener(innerMap,'bounds_changed',async()=>{placeAutocomplete.locationRestriction=innerMap.getBounds();});// Create the marker.marker=newgoogle.maps.marker.AdvancedMarkerElement({map:innerMap,});// Handle selection of an autocomplete result.// prettier-ignore// @ts-ignoreplaceAutocomplete.addEventListener('gmp-select',async({placePrediction})=>{constplace=placePrediction.toPlace();// Fetch all summary fields.awaitplace.fetchFields({fields:['displayName','formattedAddress','location','generativeSummary','neighborhoodSummary','reviewSummary','evChargeAmenitySummary',],});// Update the map viewport and position the marker.if(place.viewport){innerMap.fitBounds(place.viewport);}else{innerMap.setCenter(place.location);innerMap.setZoom(17);}marker.position=place.location;// Update the panel UI.updateSummaryPanel(place);});}functionupdateSummaryPanel(place:google.maps.places.Place){// Reset UIsummaryPanel.classList.remove('hidden');tabContainer.innerHTML='';// innerHTML is OK here since we're clearing known child elements.summaryContent.textContent='';aiDisclosure.textContent='';placeName.textContent=place.displayName||'';placeAddress.textContent=place.formattedAddress||'';letfirstTabActivated=false;/**     * Safe Helper: Accepts either a text string or a DOM Node (like a div or DocumentFragment).     */constcreateTab=(label:string,content:string|Node,disclosure:string,flagUrl:string)=>{constbtn=document.createElement('button');btn.className='tab-button';btn.textContent=label;btn.onclick=()=>{// Do nothing if the tab is already active.if(btn.classList.contains('active')){return;}// Manage the active class state.document.querySelectorAll('.tab-button').forEach((b)=>b.classList.remove('active'));btn.classList.add('active');if(typeofcontent==='string'){summaryContent.textContent=content;}else{summaryContent.replaceChildren(content.cloneNode(true));}// Set the disclosure text.aiDisclosure.textContent=disclosure||'AI-generated content.';// Add the content flag URI.if(flagUrl){flagContentLink.href=flagUrl;flagContentLink.textContent="Report an issue"}};tabContainer.appendChild(btn);// Auto-select the first available summary.if(!firstTabActivated){btn.click();firstTabActivated=true;}};// --- 1. Generative Summary (Place) ---//@ts-ignoreif(place.generativeSummary?.overview){createTab('Overview',//@ts-ignoreplace.generativeSummary.overview,//@ts-ignoreplace.generativeSummary.disclosureText,//@ts-ignoreplace.generativeSummary.flagContentURI);}// --- 2. Review Summary ---//@ts-ignoreif(place.reviewSummary?.text){createTab('Reviews',//@ts-ignoreplace.reviewSummary.text,//@ts-ignoreplace.reviewSummary.disclosureText,//@ts-ignoreplace.reviewSummary.flagContentURI);}// --- 3. Neighborhood Summary ---//@ts-ignoreif(place.neighborhoodSummary?.overview?.content){createTab('Neighborhood',//@ts-ignoreplace.neighborhoodSummary.overview.content,//@ts-ignoreplace.neighborhoodSummary.disclosureText,//@ts-ignoreplace.neighborhoodSummary.flagContentURI);}// --- 4. EV Amenity Summary (uses content blocks)) ---//@ts-ignoreif(place.evChargeAmenitySummary){//@ts-ignoreconstevSummary=place.evChargeAmenitySummary;constevContainer=document.createDocumentFragment();// Helper to build a safe DOM section for EV categories.constcreateSection=(title:string,text:string)=>{constwrapper=document.createElement('div');wrapper.style.marginBottom='15px';// Or use a CSS classconsttitleEl=document.createElement('strong');titleEl.textContent=title;consttextEl=document.createElement('div');textEl.textContent=text;wrapper.appendChild(titleEl);wrapper.appendChild(textEl);returnwrapper;};// Check and append each potential sectionif(evSummary.overview?.content){evContainer.appendChild(createSection('Overview',evSummary.overview.content));}if(evSummary.coffee?.content){evContainer.appendChild(createSection('Coffee',evSummary.coffee.content));}if(evSummary.restaurant?.content){evContainer.appendChild(createSection('Food',evSummary.restaurant.content));}if(evSummary.store?.content){evContainer.appendChild(createSection('Shopping',evSummary.store.content));}// Only add the tab if the container has childrenif(evContainer.hasChildNodes()){createTab('EV Amenities',evContainer,// Passing a Node instead of stringevSummary.disclosureText,evSummary.flagContentURI);}}// Safely handle the empty state.if(!firstTabActivated){constmsg=document.createElement('em');msg.textContent='No AI summaries are available for this specific location.';summaryContent.replaceChildren(msg);aiDisclosure.textContent='';}}initMap();
Note: Read theguide on using TypeScript and Google Maps.

JavaScript

// Define DOM elements.constmapElement=document.querySelector('gmp-map');constplaceAutocomplete=document.querySelector('gmp-place-autocomplete');constsummaryPanel=document.getElementById('summary-panel');constplaceName=document.getElementById('place-name');constplaceAddress=document.getElementById('place-address');consttabContainer=document.getElementById('tab-container');constsummaryContent=document.getElementById('summary-content');constaiDisclosure=document.getElementById('ai-disclosure');constflagContentLink=document.getElementById('flag-content-link');letinnerMap;letmarker;asyncfunctioninitMap(){// Request needed libraries.const[]=awaitPromise.all([google.maps.importLibrary('marker'),google.maps.importLibrary('places'),]);innerMap=mapElement.innerMap;innerMap.setOptions({mapTypeControl:false,streetViewControl:false,fullscreenControl:false,});// Bind autocomplete bounds to map bounds.google.maps.event.addListener(innerMap,'bounds_changed',async()=>{placeAutocomplete.locationRestriction=innerMap.getBounds();});// Create the marker.marker=newgoogle.maps.marker.AdvancedMarkerElement({map:innerMap,});// Handle selection of an autocomplete result.// prettier-ignore// @ts-ignoreplaceAutocomplete.addEventListener('gmp-select',async({placePrediction})=>{constplace=placePrediction.toPlace();// Fetch all summary fields.awaitplace.fetchFields({fields:['displayName','formattedAddress','location','generativeSummary','neighborhoodSummary','reviewSummary','evChargeAmenitySummary',],});// Update the map viewport and position the marker.if(place.viewport){innerMap.fitBounds(place.viewport);}else{innerMap.setCenter(place.location);innerMap.setZoom(17);}marker.position=place.location;// Update the panel UI.updateSummaryPanel(place);});}functionupdateSummaryPanel(place){// Reset UIsummaryPanel.classList.remove('hidden');tabContainer.innerHTML='';// innerHTML is OK here since we're clearing known child elements.summaryContent.textContent='';aiDisclosure.textContent='';placeName.textContent=place.displayName||'';placeAddress.textContent=place.formattedAddress||'';letfirstTabActivated=false;/**     * Safe Helper: Accepts either a text string or a DOM Node (like a div or DocumentFragment).     */constcreateTab=(label,content,disclosure,flagUrl)=>{constbtn=document.createElement('button');btn.className='tab-button';btn.textContent=label;btn.onclick=()=>{// Do nothing if the tab is already active.if(btn.classList.contains('active')){return;}// Manage the active class state.document.querySelectorAll('.tab-button').forEach((b)=>b.classList.remove('active'));btn.classList.add('active');if(typeofcontent==='string'){summaryContent.textContent=content;}else{summaryContent.replaceChildren(content.cloneNode(true));}// Set the disclosure text.aiDisclosure.textContent=disclosure||'AI-generated content.';// Add the content flag URI.if(flagUrl){flagContentLink.href=flagUrl;flagContentLink.textContent="Report an issue";}};tabContainer.appendChild(btn);// Auto-select the first available summary.if(!firstTabActivated){btn.click();firstTabActivated=true;}};// --- 1. Generative Summary (Place) ---//@ts-ignoreif(place.generativeSummary?.overview){createTab('Overview',//@ts-ignoreplace.generativeSummary.overview,//@ts-ignoreplace.generativeSummary.disclosureText,//@ts-ignoreplace.generativeSummary.flagContentURI);}// --- 2. Review Summary ---//@ts-ignoreif(place.reviewSummary?.text){createTab('Reviews',//@ts-ignoreplace.reviewSummary.text,//@ts-ignoreplace.reviewSummary.disclosureText,//@ts-ignoreplace.reviewSummary.flagContentURI);}// --- 3. Neighborhood Summary ---//@ts-ignoreif(place.neighborhoodSummary?.overview?.content){createTab('Neighborhood',//@ts-ignoreplace.neighborhoodSummary.overview.content,//@ts-ignoreplace.neighborhoodSummary.disclosureText,//@ts-ignoreplace.neighborhoodSummary.flagContentURI);}// --- 4. EV Amenity Summary (uses content blocks)) ---//@ts-ignoreif(place.evChargeAmenitySummary){//@ts-ignoreconstevSummary=place.evChargeAmenitySummary;constevContainer=document.createDocumentFragment();// Helper to build a safe DOM section for EV categories.constcreateSection=(title,text)=>{constwrapper=document.createElement('div');wrapper.style.marginBottom='15px';// Or use a CSS classconsttitleEl=document.createElement('strong');titleEl.textContent=title;consttextEl=document.createElement('div');textEl.textContent=text;wrapper.appendChild(titleEl);wrapper.appendChild(textEl);returnwrapper;};// Check and append each potential sectionif(evSummary.overview?.content){evContainer.appendChild(createSection('Overview',evSummary.overview.content));}if(evSummary.coffee?.content){evContainer.appendChild(createSection('Coffee',evSummary.coffee.content));}if(evSummary.restaurant?.content){evContainer.appendChild(createSection('Food',evSummary.restaurant.content));}if(evSummary.store?.content){evContainer.appendChild(createSection('Shopping',evSummary.store.content));}// Only add the tab if the container has childrenif(evContainer.hasChildNodes()){createTab('EV Amenities',evContainer,// Passing a Node instead of stringevSummary.disclosureText,evSummary.flagContentURI);}}// Safely handle the empty state.if(!firstTabActivated){constmsg=document.createElement('em');msg.textContent='No AI summaries are available for this specific location.';summaryContent.replaceChildren(msg);aiDisclosure.textContent='';}}initMap();
Note: The JavaScript is compiled from the TypeScript snippet.

CSS

/* Reuse existing map height */gmp-map{height:100%;}html,body{height:100%;margin:0;padding:0;}/* Existing Autocomplete Card Style */.place-autocomplete-card{background-color:#fff;border-radius:5px;box-shadow:rgba(0,0,0,0.35)0px5px15px;margin:10px;padding:15px;font-family:Roboto,sans-serif;font-size:1rem;}gmp-place-autocomplete{width:300px;}/* New: Summary Panel Styles */.summary-card{background-color:#fff;border-radius:5px;box-shadow:rgba(0,0,0,0.35)0px5px15px;margin:10px;padding:0;/* Padding handled by children */font-family:Roboto,sans-serif;width:350px;max-height:80vh;/* Prevent overflow on small screens */overflow-y:auto;display:flex;flex-direction:column;}.hidden{display:none;}#place-header{padding:15px;background-color:#f8f9fa;border-bottom:1pxsolid#ddd;}#place-headerh2{margin:005px0;font-size:1.2rem;}#place-address{margin:0;color:#555;font-size:0.9rem;}/* Tab Navigation */.tab-container{display:flex;border-bottom:1pxsolid#ddd;background-color:#fff;}.tab-button{flex:1;background:none;border:none;padding:10px;cursor:pointer;font-weight:500;color:#555;border-bottom:3pxsolidtransparent;}.tab-button:hover{background-color:#f1f1f1;}.tab-button.active{font-weight:bold;border-bottom:3pxsolid#000000;}.tab-button.active:hover{background-color:#ffffff;cursor:default;}/* Content Area */.content-area{padding:15px;line-height:1.5;font-size:0.95rem;color:#333;}.disclosure-footer{font-size:0.75rem;color:#666;padding:10px15px;border-top:1pxsolid#eee;font-style:italic;}.flag-content-link{font-size:0.75rem;color:#666;padding:10px15px;border-top:1pxsolid#eee;}/* Reuse existing map height */gmp-map{height:100%;}html,body{height:100%;margin:0;padding:0;}/* Existing Autocomplete Card Style */.place-autocomplete-card{background-color:#fff;border-radius:5px;box-shadow:rgba(0,0,0,0.35)0px5px15px;margin:10px;padding:15px;font-family:Roboto,sans-serif;font-size:1rem;}gmp-place-autocomplete{width:300px;}/* New: Summary Panel Styles */.summary-card{background-color:#fff;border-radius:5px;box-shadow:rgba(0,0,0,0.35)0px5px15px;margin:10px;padding:0;/* Padding handled by children */font-family:Roboto,sans-serif;width:350px;max-height:80vh;/* Prevent overflow on small screens */overflow-y:auto;display:flex;flex-direction:column;}.hidden{display:none;}#place-header{padding:15px;background-color:#f8f9fa;border-bottom:1pxsolid#ddd;}#place-headerh2{margin:005px0;font-size:1.2rem;}#place-address{margin:0;color:#555;font-size:0.9rem;}/* Tab Navigation */.tab-container{display:flex;border-bottom:1pxsolid#ddd;background-color:#fff;}.tab-button{flex:1;background:none;border:none;padding:10px;cursor:pointer;font-weight:500;color:#555;border-bottom:3pxsolidtransparent;}.tab-button:hover{background-color:#f1f1f1;}.tab-button.active{font-weight:bold;border-bottom:3pxsolid#000000;}.tab-button.active:hover{background-color:#ffffff;cursor:default;}/* Content Area */.content-area{padding:15px;line-height:1.5;font-size:0.95rem;color:#333;}.disclosure-footer{font-size:0.75rem;color:#666;padding:10px15px;border-top:1pxsolid#eee;font-style:italic;}.flag-content-link{font-size:0.75rem;color:#666;padding:10px15px;}

HTML

<html>    <head>        <title>AI Place Summaries</title>        <link rel="stylesheet" type="text/css" href="./style.css" />        <script type="module" src="./index.js"></script>        <!-- prettier-ignore -->        <script>(g=>{var h,a,k,p="The Google Maps JavaScript API",c="google",l="importLibrary",q="__ib__",m=document,b=window;b=b[c]||(b[c]={});var d=b.maps||(b.maps={}),r=new Set,e=new URLSearchParams,u=()=>h||(h=new Promise(async(f,n)=>{await (a=m.createElement("script"));e.set("libraries",[...r]+"");for(k in g)e.set(k.replace(/[A-Z]/g,t=>"_"+t[0].toLowerCase()),g[k]);e.set("callback",c+".maps."+q);a.src=`https://maps.${c}apis.com/maps/api/js?`+e;d[q]=f;a.onerror=()=>h=n(Error(p+" could not load."));a.nonce=m.querySelector("script[nonce]")?.nonce||"";m.head.append(a)}));d[l]?console.warn(p+" only loads once. Ignoring:",g):d[l]=(f,...n)=>r.add(f)&&u().then(()=>d[l](f,...n))})        ({key: "AIzaSyA6myHzS10YXdcazAFalmXvDkrYCp5cLc8", v: "weekly"});</script>    </head>    <body>        <gmp-map center="37.805, -122.425" zoom="14" map-id="DEMO_MAP_ID">            <!-- Search Input Card -->            <div                               slot="control-inline-start-block-start">                <p>Search for a place with AI summaries:</p>                <gmp-place-autocomplete></gmp-place-autocomplete>            </div>            <!-- Summary text panel (initially hidden) -->            <div                                              slot="control-inline-end-block-start">                <div>                    <h2></h2>                    <p></p>                </div>                <!-- Tabs for toggling summary types -->                <div></div>                <!-- Content display area -->                <div></div>                <!-- Legal/AI Disclosure -->                <div></div>                <!-- Flag content link -->                <a></a>            </div>        </gmp-map>    </body></html>

Try Sample

Clone Sample

Git and Node.js are required to run this sample locally. Follow theseinstructionsto install Node.js and NPM. The following commands clone, install dependencies and start the sample application.

gitclonehttps://github.com/googlemaps-samples/js-api-samples.gitcdsamples/ai-powered-summariesnpminpmstart

Except as otherwise noted, the content of this page is licensed under theCreative Commons Attribution 4.0 License, and code samples are licensed under theApache 2.0 License. For details, see theGoogle Developers Site Policies. Java is a registered trademark of Oracle and/or its affiliates.

Last updated 2025-12-17 UTC.