Lightning Map Marker in Salesforce: Visualize Lead Data with LWC

🗺️ Lightning Map Marker in Salesforce: Visualize Lead Data with LWC 

In today’s data-driven CRM world, seeing your customer data on a map isn’t just a feature—it’s a strategic advantage. 

With Lightning Web Components (LWC) and the powerful lightning-map base component, Salesforce developers can now create interactive, location-based visualizations right inside their apps. In this blog, we’ll walk through building a full-featured Lead Map Viewer using: 

  • LWC 
  • Apex 
  • Google Maps (via lightning-map) 

 

🚀 Why Integrate Maps in Salesforce? 

Integrating maps into your Lightning components enables: 

  • 📍 Geospatial visualization of Leads, Accounts, and more 
  • 🎯 Real-time address-based filtering 
  • 🧠 Smarter insights for sales and field reps 
  • 📈 Improved user experience with visual interactivity 

 

🔧 Prerequisites 

Before you begin, make sure you have: 

  • A Salesforce org with Lead records 
  • Visual Studio Code + Salesforce CLI 
  • Familiarity with LWC and Apex basics 

🛠️ Step-by-Step Implementation 

 

Step 1: Create the Search & Map View (HTML) 

In mapLeadAddress.html, layout the map, input filters, and buttons: 

html 

<template> 

  <lightning-card> 

    <div class=”slds-grid slds-gutters slds-m-vertical_medium” style=”padding-left: 20px;”> 

      <!– Map Section –> 

      <div class=”slds-col slds-size_8-of-12″> 

        <lightning-map 

          map-markers={mapMarkers} 

          alternative-text=”Map of Leads” 

          list-view=”hidden” 

          onmarkerselect={handleMarkerSelect} 

          selected-marker-value={selectedMarkerValue} 

        ></lightning-map> 

      </div> 

 

      <!– Spinner While Loading –> 

      <template if:true={showSpinner}> 

        <lightning-spinner alternative-text=”Loading” size=”medium”></lightning-spinner> 

      </template> 

 

      <!– Search Form –> 

      <template if:false={showSpinner}> 

        <template if:true={showSearchForm}> 

          <div class=”slds-col slds-size_4-of-12 filterStyle”> 

            <div class=”slds-text-heading_large slds-align_absolute-center”>Map Search</div> 

            <lightning-input label=”Street Address” data-id=”Street”></lightning-input> 

            <lightning-input label=”City” data-id=”City” pattern=”^([^0-9]*)$” message-when-pattern-mismatch=”Numbers are not allowed”></lightning-input> 

            <lightning-input label=”Postal Code” data-id=”PostalCode”></lightning-input> 

            <lightning-input label=”Country” data-id=”country” pattern=”^([^0-9]*)$” message-when-pattern-mismatch=”Numbers are not allowed”></lightning-input> 

 

            <div class=”slds-align_absolute-center slds-p-vertical_medium”> 

              <lightning-button variant=”brand” label=”Search” onclick={handleSearchClick}></lightning-button> 

              <lightning-button variant=”neutral” label=”Clear” class=”slds-m-left_medium” onclick={handleClear}></lightning-button> 

            </div> 

          </div> 

        </template> 

      </template> 

    </div> 

  </lightning-card> 

</template> 

 

Step 2: Handle Map Search in JavaScript 

In mapLeadAddress.js: 

js 

import { LightningElement, api, track } from ‘lwc’; 

import getLeadAddresses from ‘@salesforce/apex/mapLeadAddressController.getLeadAddresses’; 

import { ShowToastEvent } from ‘lightning/platformShowToastEvent’; 

 

export default class MapLeadAddress extends LightningElement { 

  @track mapMarkers = []; 

  tempMapMarkers = []; 

  showSearchForm = true; 

  showMulitpleMapCards = false; 

  showSingleMapCard = false; 

  searchAccountList = []; 

  showSpinner = false; 

  leadRecordId = ”; 

  fieldVsValue = new Map(); 

 

  escapeQuotes(value) { 

    return value.replace(/’/g, “\\'”); 

  } 

 

  handleSearchClick() { 

    this.fieldVsValue.clear(); 

    this.showSpinner = true; 

 

    const street = this.template.querySelector(‘[data-id=”Street”]’).value?.trim(); 

    const city = this.template.querySelector(‘[data-id=”City”]’).value?.trim(); 

    const zipCode = this.template.querySelector(‘[data-id=”PostalCode”]’).value?.trim(); 

    const country = this.template.querySelector(‘[data-id=”country”]’).value?.trim(); 

 

    if (street) this.fieldVsValue.set(‘Street’, `Street = ‘${this.escapeQuotes(street)}’`); 

    if (city) this.fieldVsValue.set(‘City’, `City = ‘${this.escapeQuotes(city)}’`); 

    if (zipCode) this.fieldVsValue.set(‘PostalCode’, `PostalCode = ‘${this.escapeQuotes(zipCode)}’`); 

    if (country) this.fieldVsValue.set(‘Country’, `Country = ‘${this.escapeQuotes(country)}’`); 

 

    if (this.fieldVsValue.size === 0) { 

      this.showToast(‘Error’, ‘Please enter some value’, ‘error’); 

      this.showSpinner = false; 

      return; 

    } 

 

    let query = ”; 

    let index = 0; 

    for (let key of this.fieldVsValue.keys()) { 

      query += this.fieldVsValue.get(key); 

      if (++index < this.fieldVsValue.size) query += ‘ AND ‘; 

    } 

 

    getLeadAddresses({ query }) 

      .then(response => { 

        this.showSpinner = false; 

        if (response && response.length > 0) { 

          this.mapMarkers = response.map(lead => ({ 

            location: { 

              Street: lead.Street, 

              City: lead.City, 

              State: lead.State, 

              PostalCode: lead.PostalCode, 

              Country: lead.Country 

            }, 

            title: lead.Name, 

            value: lead.Id 

          })); 

 

          this.searchAccountList = response.map(lead => ({ 

            …lead, 

            Address: `${lead.Street ?? ”} ${lead.City ?? ”} ${lead.State ?? ”} ${lead.Country ?? ”} ${lead.PostalCode ?? ”}` 

          })); 

 

          this.tempMapMarkers = this.mapMarkers; 

          this.showSearchForm = false; 

          this.showMulitpleMapCards = true; 

          this.showSingleMapCard = false; 

        } else { 

          this.showToast(‘Error’, ‘No records found’, ‘error’); 

          this.resetViews(); 

        } 

      }) 

      .catch(error => { 

        this.showToast(‘Error’, ‘Invalid Entry: ‘ + error.body?.message || error.message, ‘error’); 

        console.error(‘getLeadAddresses error’, error); 

        this.resetViews(); 

      }); 

  } 

 

  handleClear() { 

    [‘Street’, ‘City’, ‘PostalCode’, ‘country’].forEach(id => { 

      this.template.querySelector(`[data-id=”${id}”]`).value = ”; 

    }); 

    this.fieldVsValue.clear(); 

  } 

 

  showToast(title, message, variant) { 

    this.dispatchEvent(new ShowToastEvent({ title, message, variant })); 

  } 

 

  resetViews() { 

    this.showSpinner = false; 

    this.showSearchForm = true; 

    this.showMulitpleMapCards = false; 

    this.showSingleMapCard = false; 

    this.mapMarkers = []; 

  } 

} 

 

Step 3: Apex Class to Fetch Leads 

Create the Apex class mapLeadAddressController: 

apex 

public with sharing class mapLeadAddressController { 

  @AuraEnabled 

  public static List<Lead> getLeadAddresses(String query) { 

    try { 

      String baseQuery = ‘SELECT Id, Name, Address, Longitude, Latitude, PostalCode, City, Street, State, Country FROM Lead’; 

      if (!String.isBlank(query)) { 

        baseQuery += ‘ WHERE ‘ + query; 

      } 

      System.debug(‘Final Query: ‘ + baseQuery); 

      return Database.query(baseQuery); 

    } catch (Exception e) { 

      throw new AuraHandledException(‘Invalid Entry: ‘ + e.getMessage()); 

    } 

  } 

} 

 

Step 4: Display Lead Results with Pagination 

Create a child component (mapAddressDetailCard) to display leads and handle pagination: 

html 

<template> 

  <div class=”slds-text-heading_medium slds-align_absolute-center” style=”font-weight: bold;”>Searched Results</div> 

 

  <div class=”pagination-controls slds-m-vertical_medium” style=”text-align: right;”> 

    <lightning-icon icon-name=”utility:chevronleft” onclick={handlePrevious} disabled={isFirstPage}></lightning-icon> 

    <span class=”slds-m-horizontal_x-small”>Page {currentPage} of {totalPages}</span> 

    <lightning-icon icon-name=”utility:chevronright” onclick={handleNext} disabled={isLastPage}></lightning-icon> 

  </div> 

 

  <div class=”slds-p-around_medium cardViewer”> 

    <template for:each={paginatedRecords} for:item=”lead”> 

      <article key={lead.Id} class=”slds-card”> 

        <div class=”slds-card__header”>{lead.Name}</div> 

        <div class=”slds-card__body”>{lead.Address}</div> 

        <div class=”slds-card__footer”> 

          <span onclick={handleAccountClick} data-id={lead.Id} style=”cursor: pointer;”>View Marker Details</span> 

        </div> 

      </article> 

    </template> 

  </div> 

</template> 

 

And in its JS: 

js 

import { LightningElement, api, track } from ‘lwc’; 

 

export default class MapAddressDetailCard extends LightningElement { 

  _leadList = []; 

 

  @track paginatedRecords = []; 

  @track currentPage = 1; 

  @track pageSize = 7; 

 

  @api 

  get leadList() { 

    return this._leadList; 

  } 

 

  set leadList(value) { 

    this._leadList = value || []; 

    this.currentPage = 1; 

    this.updatePaginatedRecords(); 

  } 

 

  get totalPages() { 

    return Math.ceil(this._leadList.length / this.pageSize); 

  } 

 

  get isFirstPage() { 

    return this.currentPage === 1; 

  } 

 

  get isLastPage() { 

    return this.currentPage === this.totalPages; 

  } 

 

  updatePaginatedRecords() { 

    const start = (this.currentPage – 1) * this.pageSize; 

    const end = start + this.pageSize; 

    this.paginatedRecords = this._leadList.slice(start, end); 

  } 

 

  handlePrevious() { 

    if (this.currentPage > 1) { 

      this.currentPage–; 

      this.updatePaginatedRecords(); 

    } 

  } 

 

  handleNext() { 

    if (this.currentPage < this.totalPages) { 

      this.currentPage++; 

      this.updatePaginatedRecords(); 

    } 

  } 

 

  handleAccountClick(event) { 

    const recordId = event.currentTarget.dataset.id; 

    this.dispatchEvent(new CustomEvent(‘mycustomevent’, { 

      detail: recordId 

    })); 

  } 

} 

 

💡 Bonus Tips 

Want to make your map even more powerful? 

  • Show route directions between markers 
  • Add real-time geolocation using device GPS 
  • Use clustering for dense marker groups 

 

🏁 Conclusion 

You now have a working map-based lead search app in Salesforce, built using: 

  • lightning-map 
  • dynamic SOQL via Apex 
  • reusable LWC components 
  • elegant, paginated lead display 

📍 Whether you’re enabling field reps, supporting local campaigns, or tracking regional performance—this component gives users a whole new way to engage with CRM data visually. 

 

Leave a Comment

Your email address will not be published. Required fields are marked *