This post is also available in: Español (Spanish)
If you store geolocation when creating new leads or use services like GeoIP to detect where your users are, you can use Leaflet in your LWC components to show your users where the customers are.
This is useful, as your users do not have to search Google for specific locations, and they can use the map no navigate the zone.
In this article, we will use the Metadata API to set up everything so… let’s code our new component
Adding the LeafletJS library
As first step, we will need to add the LeafletJS library as a static dependency in Salesforce, so go to the Downloads page of the LeafletJS site, and download the latest version.
Add the downloaded ZIP file to the staticresource
directory and create its manifest (If your file is called leaflet.js
, your manifest file would be leaflet.resource-meta.xml
).
The manifest file should look like this, we set the Cache-Control header to public, so it can be cached in the Salesforce CDN to speed its download and set the Content-Type to application/zip, to allow Salesforce to unzip the file and be able to use its contents.
<?xml version="1.0" encoding="UTF-8"?>
<StaticResource xmlns="<http://soap.sforce.com/2006/04/metadata>">
<cacheControl>Public</cacheControl>
<contentType>application/zip</contentType>
<description>Leaflet 1.7.1</description>
</StaticResource>
Preparing our new component
Salesforce provides us two methods to load external libraries, called loadStyle
and loadScript
from the Platform Resource Loader, explained on the Use Third-Party JavaScript Libraries article. These methods return promises, so we can wait them to load before start using the requested files.
We will create a new component called OpportunityMap
to show our map, create the files opportunityMap.js
, opportunityMap.html
, opportunityMap.css
, and its manifest file, which should be like:
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="<http://soap.sforce.com/2006/04/metadata>" fqn="sonneilDevelopmentAvailability">
<apiVersion>49.0</apiVersion>
<isExposed>true</isExposed>
<masterLabel>Opportunity Map</masterLabel>
<targets>
<target>lightning__RecordPage</target>
</targets>
<targetConfigs>
<targetConfig targets="lightning__RecordPage">
<objects>
<object>Opportunity</object>
</objects>
<property name="height" type="Integer" min="50" required="true" default="200" label="Height (in pixels)"/>
</targetConfig>
</targetConfigs>
</LightningComponentBundle>
In this manifest file, we configure our component to be available on the details page of the Opportunity object, as we will need the record ID to get the location of our customer.
Also, we add a configurable property to allow setting the height of our component, so depending on where we add it, we can easily set its height.
In the opportunityMap.html we will only need a div where we will paint the map, so type the following:
<template>
<div class="slds-box slds-theme_default"></div>
</template>
We add the slds-box
and the slds-theme_default
classes to be aligned with the Salesforce Design System. The first one will apply spacing, border, and rounded corners, meanwhile the second one will add a white background while loading our map.
Coding our component
If we test our component, we will only have a 50px height rectangle, so let’s add code.
First, we import all the required dependencies:
import { api, LightningElement } from 'lwc';
import { loadScript, loadStyle } from 'lightning/platformResourceLoader';
import LEAFLET from '@salesforce/resourceUrl/leaflet';
Remember when loading LeafletJS, you should use the same name you uploaded it with.
Begin with the OpportunityMap
class, we should add recordId
and height
properties, the first one will be filled with by Salesforce with the current Opportunity ID, and the height one will hold the value used when added to a page.
export default class InquiryMap extends LightningElement {
@api recordId;
@api height;
}
And now, we will be using the connectedCallback
and renderedCallback
methods to load the LeafletJS library. These methods are part of the Lifecycle hooks. The first one will be called when the component is inserted into a document, and renderedCallback
is fired after every render of the component.
import { api, LightningElement } from 'lwc';
import { loadScript, loadStyle } from 'lightning/platformResourceLoader';
import LEAFLET from '@salesforce/resourceUrl/leaflet';
export default class InquiryMap extends LightningElement {
@api recordId;
@api height;
renderedCallback() {
this.template.querySelector('div').style.height = `${this.height}px`;
}
connectedCallback() {
Promise.all([
loadStyle(this, LEAFLET + '/leaflet.css'),
loadScript(this, LEAFLET + '/leaflet.js'),
]).then(() => {
// Leaflet should be ready, create a new draw method
// this.draw();
});
}
}
Now, after the promises are fulfilled, Leaflet is ready to be instantiated, so we will create a new draw method, where we will create the map and add the point. We will be using the Open Street Map tiles and create a test marker located near our offices in Alicante, Spain.
draw() {
let container = this.template.querySelector('div');
let position = [38.341891109801594, -0.48035610027642234];
let map = L.map(container, { scrollWheelZoom: false }).setView(position, 13);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© <a href="<https://www.openstreetmap.org/copyright>">OpenStreetMap</a> contributors',
}).addTo(map);
let marker = L.marker(position).addTo(map);
let featureGroup = L.featureGroup([marker]).addTo(map);
map.fitBounds(featureGroup.getBounds());
}
Now, if you insert this component in the Opportunity Page, you should see something like this:

Adding real data from our Opportunity
In the previous section we added an example point, but we want to show the real location of our customer. You should be already storing in your records the latitude/longitude, for example, in our case, we have a Geolocation custom field.
You can use the wire service and getRecord
to get the value of this field, but in our case, we created a controller to return this, and other required values, as we also store more useful information to show in this map, like our customer time zone and local time, and we can use this method as another promise when loading LeafleftJS:
connectedCallback() {
Promise.all([
// ...
getOppportunityMapData({ recordId: this.recordId }),
]).then(([st, sc, opportunity]) => {
this.opportunity = opportunity;
this.draw();
});
}
And now, we can update our draw method:
draw() {
// ...
let position = [
this.opportunity.location__latitude__s,
this.opportunity.location__longitude__s
];
// ...
}
My map is over the header bar
Due to the default z-index for the LeafletJS map (400), it is possible your map floats over the Salesforce navbar. This is easily fixable by setting the container z-index in opportunityMap.css
.
div {
z-index: 0
}
Hope this article allows you to help your Salesforce users =) If you have any questions, feel free to ask them in the comments!