Back to Developer Home

Storepoint Locator Widget JavaScript API

Customize and extend your embedded store locator with our Locator Widget JavaScript API. Listen for events, trigger actions, and create seamless integrations between your application and the Storepoint locator widget.

Get Started →

Installation

Get Your Embed Code

Find your unique embed code (and public widget ID) on the 'Embed Locator' page in your dashboard.

Get Embed Code

Copy the embed code from your dashboard and paste it into your website:

HTML
<!-- Storepoint Widget Embed Start -->
<div id="storepoint-widget"></div>
<script>
window.Storepoint=window.Storepoint||{_q:[],on:function(e,c){this._q.push([e,c])}};
window.loadStorepoint=function(){new StorepointWidget('YOUR_WIDGET_ID','#storepoint-widget',{});};
!function(){var t=document.createElement("script");t.type="text/javascript";t.async=!0;t.src="https://widget.storepoint.co/embed.js";t.onload=function(){"function"==typeof window.loadStorepoint&&window.loadStorepoint()};document.head.appendChild(t);}();
</script>
<!-- Storepoint Widget Embed End -->

Replace YOUR_WIDGET_ID with your public widget ID, or use the embed code directly from your dashboard embed page.

Timing-Safe: The first line creates a global Storepoint object. You can call Storepoint.on() anywhere on your page - before or after the widget loads - and it will work correctly.

Configuration Options

Pass configuration options as the third parameter when initializing the widget to customize behavior and override dashboard settings.

initialState

Set the initial search state of the widget. Useful for programmatically pre-loading a search, pre-filling the search bar, or automatically detecting the user's location.

JavaScript
var storepoint = new StorepointWidget('YOUR_PUBLIC_WIDGET_ID', '#storepoint-widget', {
    initialState: {
        search_text: 'New York, NY',    // Pre-fill search bar
        lat: 40.7128,                   // Latitude to search from
        lng: -74.0060,                  // Longitude to search from
        radius: 50,                     // Search radius (uses widget's distance unit)
        auto_detect_location: true      // Automatically detect user's location via IP
    }
});

Properties

Property Type Description
search_text string Text to display in the search bar. Does not trigger a search by itself unless combined with lat/lng.
lat number Latitude to search from. Must be between -90 and 90. When combined with lng, triggers a search.
lng number Longitude to search from. Must be between -180 and 180. When combined with lat, triggers a search.
radius number Search radius. Must be a positive number. Uses the distance unit configured in your dashboard (miles or kilometers).
auto_detect_location boolean When true, automatically detects the user's location using IP geolocation and performs a search. Overrides lat/lng if both are provided.

Examples

Pre-fill search with coordinates

JavaScript
var storepoint = new StorepointWidget('YOUR_PUBLIC_WIDGET_ID', '#storepoint-widget', {
    initialState: {
        search_text: 'Los Angeles',
        lat: 34.0522,
        lng: -118.2437,
        radius: 25
    }
});

Auto-detect user location

JavaScript
var storepoint = new StorepointWidget('YOUR_PUBLIC_WIDGET_ID', '#storepoint-widget', {
    initialState: {
        auto_detect_location: true,
        radius: 10
    }
});

URL parameters (e.g., ?search=Chicago) take precedence over initialState and will override these settings.

mapOptions

Advanced map configuration options. These settings allow you to override dashboard map settings or access deeper Mapbox GL JS configuration options not exposed in the dashboard.

mapOptions.initialFocus

Set the initial map viewport before any search is performed. Useful for centering the map on a specific region, city, or country.

JavaScript
var storepoint = new StorepointWidget('YOUR_PUBLIC_WIDGET_ID', '#storepoint-widget', {
    mapOptions: {
        initialFocus: {
            latitude: 51.5074,     // Center latitude
            longitude: -0.1278,    // Center longitude
            zoom: 10               // Zoom level (0-22)
        }
    }
});

Properties

Property Type Range Description
latitude number -90 to 90 Latitude coordinate for map center
longitude number -180 to 180 Longitude coordinate for map center
zoom number 0 to 22 Initial zoom level. Higher numbers = more zoomed in.

This only sets the initial map view. Once a search is performed, the map will automatically adjust to show search results.

mapOptions.mapboxConfig

Pass advanced Mapbox GL JS configuration options directly to the map instance. This allows fine-grained control over map behavior beyond what's available in the dashboard settings.

These options override dashboard settings, making them useful for one-off customizations or specific embedding contexts.

JavaScript
var storepoint = new StorepointWidget('YOUR_PUBLIC_WIDGET_ID', '#storepoint-widget', {
    mapOptions: {
        mapboxConfig: {
            style: 'mapbox://styles/your-username/custom-style-id',  // Custom map style
            minZoom: 5,                    // Minimum zoom level
            maxZoom: 18,                   // Maximum zoom level
            maxBounds: [                   // Restrict panning to specific region
                [-10.0, 45.0],             // Southwest coordinates [lng, lat]
                [10.0, 60.0]               // Northeast coordinates [lng, lat]
            ],
            center: [-0.1278, 51.5074],    // Initial center [lng, lat]
            zoom: 12,                      // Initial zoom
            pitch: 0,                      // Map tilt angle (0-85)
            bearing: 0,                    // Map rotation (0-360)
            maxPitch: 60,                  // Maximum tilt angle
            minPitch: 0                    // Minimum tilt angle
        }
    }
});
Common Use Cases

Restrict map to a specific region (e.g., Europe only)

JavaScript
mapOptions: {
    mapboxConfig: {
        maxBounds: [
            [-12.0, 35.0],  // Southwest: West of Portugal, South of Gibraltar
            [40.0, 72.0]    // Northeast: East of Urals, North of Scandinavia
        ],
        minZoom: 3
    }
}

Use a custom Mapbox style

JavaScript
mapOptions: {
    mapboxConfig: {
        style: 'mapbox://styles/your-username/ckl6m2k7s0t7w17nzqhgj5k8m'
    }
}

Limit zoom levels

JavaScript
mapOptions: {
    mapboxConfig: {
        minZoom: 6,   // Users can't zoom out beyond country-level
        maxZoom: 16   // Users can't zoom in beyond neighborhood-level
    }
}
Reserved Properties

The following properties cannot be overridden as they're managed internally by the widget:

  • container - Widget manages its own container
  • accessToken - Managed by your dashboard settings

For a complete list of available options, see the Mapbox GL JS Map documentation.

Note: These configuration options apply only to Mapbox-powered maps. If you're using Google Maps as your map provider, these options won't apply (except initialState, which works with all map providers).

Available Events

Search Events

search

Triggered when a search is performed.

JavaScript
Storepoint.on('search', function(data) {
    // data contains:
    // {
    //   lat: number|null,        // Latitude of search point
    //   lng: number|null,        // Longitude of search point
    //   query: string,           // Search text
    //   type: string,            // Search type (e.g., 'initial_search', 'filter_change')
    //   storepoint_location_id: number|null,  // ID of nearest location
    //   distance: number|null,   // Distance to nearest location
    //   tags: string[]|null      // Active filter tags
    // }
});

Location Events

location-result-item-rendered

Triggered when a location item is rendered in the results list and is available in the DOM.

When it fires:

  • After a location item appears in the visible portion of the results list
  • Only fires once per location per search session
  • Works with virtual scrolling - fires as items scroll into view
  • Fires after "Show More" is clicked on mobile
JavaScript
Storepoint.on('location-result-item-rendered', function(data) {
    // data contains:
    // {
    //   location: {
    //     id: "location_id",
    //     name: "Location Name",
    //     address: "123 Main St",
    //     // ... all other location properties
    //   },
    //   element: HTMLElement  // The DOM element containing the location card
    // }
    
    // Example: Inject custom content into the location card
    const infoDiv = data.element.querySelector('.storepoint-location-info');
    const customDiv = document.createElement('div');
    customDiv.innerHTML = '<strong>Custom info for ' + data.location.name + '</strong>';
    infoDiv.appendChild(customDiv);
});

This event is particularly useful for injecting dynamic content (like booking availability, real-time pricing, etc.) into location cards as they appear.

location-detail-panel-opened

Triggered when the location detail panel opens with a location (only fires when detail panel mode is enabled in your widget settings).

When it fires:

  • When a user clicks a location or map marker and the detail panel opens
  • Only when location_detail_panel_enabled setting is true in your dashboard
JavaScript
Storepoint.on('location-detail-panel-opened', function(data) {
    // data contains:
    // {
    //   location: {
    //     id: "location_id",
    //     name: "Location Name",
    //     address: "123 Main St",
    //     // ... all other location properties
    //   },
    //   element: HTMLElement  // The detail panel content container
    // }
    
    // Example: Inject additional details into the panel
    const infoDiv = data.element.querySelector('.storepoint-location-info');
    const detailDiv = document.createElement('div');
    detailDiv.className = 'custom-details';
    detailDiv.innerHTML = '<strong>Store Manager:</strong> ' + data.location.manager_name;
    infoDiv.appendChild(detailDiv);
});

location-result-item-click

Triggered when a location in the results list is clicked.

JavaScript
Storepoint.on('location-result-item-click', function(data) {
    // data contains location details
});

map-marker-click

Triggered when a location marker on the map is clicked.

JavaScript
Storepoint.on('map-marker-click', function(data) {
    // data contains location details
});

Online Store Events

online-store-click

Triggered when an online store link is clicked.

JavaScript
Storepoint.on('online-store-click', function(data) {
    // data contains:
    // {
    //   store_name: string,  // Name of the online store
    //   store_link: string   // URL of the online store
    // }
});

Widget Events

load

Triggered when the widget initially loads.

JavaScript
Storepoint.on('load', function(data) {
    // data contains:
    // {
    //   url: string,      // Current page URL
    //   referrer: string  // Referrer URL
    // }
});

no-results

Triggered when a search returns no results.

JavaScript
Storepoint.on('no-results', function(data) {
    // Useful for implementing fallback behavior
    window.location.href = '/store-not-found';
});

API Methods

Storepoint.on(event, callback)

Subscribes to widget events. Can be called anywhere on your page - the callback will fire when the event occurs.

JavaScript
// Listen for search events
Storepoint.on('search', function(data) {
    console.log('Search performed');
});

// Listen for location clicks
Storepoint.on('location-result-item-click', function(data) {
    console.log('Location clicked:', data.name);
});

Timing-Safe: Storepoint.on() can be called before the widget loads. Event listeners are automatically queued and registered when the widget is ready.

Storepoint.setLanguage(language)

Sets the widget's display language using a two-letter ISO language code (e.g. 'en', 'es', 'fr'). Must be called after widget loads.

JavaScript
// Call inside load callback or in response to user action
Storepoint.on('load', function() {
    Storepoint.setLanguage('fr');
});

// Or on a button click
document.getElementById('lang-btn').addEventListener('click', function() {
    Storepoint.setLanguage('es');
});

Storepoint.setFilters(filters)

Programmatically sets active tag filters on the widget. Must be called after widget loads.

JavaScript
// Filter to show only 'premium' locations on button click
document.getElementById('premium-btn').addEventListener('click', function() {
    Storepoint.setFilters(['premium']);
});

// Clear all filters
document.getElementById('clear-btn').addEventListener('click', function() {
    Storepoint.setFilters([]);
});

Storepoint.getCurrentLocations()

Returns an array of location objects currently visible in the widget. Must be called after widget loads.

JavaScript
// Get locations after widget loads
Storepoint.on('load', function() {
    var locations = Storepoint.getCurrentLocations();
    console.log('Loaded', locations.length, 'locations');
});

// Or get locations after a search
Storepoint.on('search', function() {
    var locations = Storepoint.getCurrentLocations();
    console.log('Found', locations.length, 'locations');
});

This method returns the current set of locations after any filtering or searching has been applied. The returned array contains the full location objects with all properties.

Storepoint.addCustomLayer(layerId, layerOptions)

Adds a custom GeoJSON layer to the map. Must be called after widget loads.

JavaScript
// Add a custom polygon layer after widget loads
Storepoint.on('load', function() {
    Storepoint.addCustomLayer('my-custom-layer', {
        type: 'fill',
        fillColor: '#FF5733',
        fillOpacity: 0.2,
        strokeWidth: 2,
        strokeColor: '#FF5733',
        strokeOpacity: 0.8,
        data: {
            type: 'FeatureCollection',
            features: [{
                type: 'Feature',
                geometry: {
                    type: 'Polygon',
                    coordinates: [[
                        [-74.006, 40.713],
                        [-74.006, 40.717],
                        [-73.998, 40.717],
                        [-73.998, 40.713],
                        [-74.006, 40.713]
                    ]]
                }
            }]
        }
    });
});

The layerOptions object supports various styling properties including type (fill, line, symbol), fillColor, fillOpacity, strokeWidth, strokeColor, and strokeOpacity. The data property should contain valid GeoJSON.

Storepoint.removeCustomLayer(layerId)

Removes a previously added custom layer from the map by its ID.

JavaScript
Storepoint.removeCustomLayer('my-custom-layer');

Storepoint.addCustomFilter(filterConfig)

Adds a custom filter UI component to the widget. Must be called after widget loads. The filterConfig object defines the filter's ID, filtering logic, UI template, and event handling.

JavaScript
// Add a custom range filter after widget loads
Storepoint.on('load', function() {
    Storepoint.addCustomFilter({
    // Unique identifier for this filter
    id: 'price_range',
    
    // Filter type (always 'custom' for custom filters)
    type: 'custom',
    
    /**
     * Filtering function that determines which locations to display
     * @param {Array} locations - Array of locations matching the current query before custom filters
     * @param {any} filterValue - Current value of the filter (from defaultValue or updateValue)
     * @return {Array} - Filtered array of locations that should be displayed
     */
    filter: function(locations, filterValue) {
        if (!filterValue) return locations;
        
        const min = filterValue[0];
        const max = filterValue[1];
        
        return locations.filter(location => {
            // Custom logic to filter locations based on a property
            const price = parseFloat(location.custom_fields.text.find(
                field => field.name === 'price'
            ).value);
            return !isNaN(price) && price >= min && price <= max;
        });
    },
    
    /**
     * HTML template for the filter UI
     * Can be provided as a string or as a getter function: get template() { return '...'; }
     * This defines the visual structure of your custom filter component
     */
    template: `
        <div class="price-filter">
            <div class="filter-header">Price Range</div>
            <div class="range-inputs">
                <input type="number" class="min-input" placeholder="Min">
                <span>to</span>
                <input type="number" class="max-input" placeholder="Max">
            </div>
        </div>
    `,
    
    /**
     * Setup function to initialize the filter UI and handle events
     * @param {HTMLElement} container - The DOM element where your filter HTML is mounted
     * @param {Function} updateValue - Callback function that must be called with the new filter value. This new value will be passed to the filter function to filter locations
     */
    setup: function(container, updateValue) {
        const minInput = container.querySelector('.min-input');
        const maxInput = container.querySelector('.max-input');
        
        // Handle input changes
        function handleChange() {
            const min = parseFloat(minInput.value) || 0;
            const max = parseFloat(maxInput.value) || 1000;
            updateValue([min, max]);
        }
        
        minInput.addEventListener('input', handleChange);
        maxInput.addEventListener('input', handleChange);
        
        // Initialize with default values
        minInput.value = 0;
        maxInput.value = 1000;
        updateValue([0, 1000]);
    },
    
    // Default filter value
    defaultValue: [0, 1000],
    
    // Optional CSS styles for the filter
    styles: `
        .price-filter {
            padding: 10px;
            background: #f5f5f5;
            border-radius: 4px;
        }
        .filter-header {
            font-weight: bold;
            margin-bottom: 8px;
        }
        .range-inputs {
            display: flex;
            align-items: center;
            gap: 8px;
        }
        .min-input, .max-input {
            width: 80px;
            padding: 6px;
            border: 1px solid #ddd;
            border-radius: 4px;
        }
    `
    });
});

Custom filters allow you to create advanced filtering UI with your own logic. The filter function receives locations and filter value, returning filtered results. The setup function initializes the UI and handles user interactions.

Advanced Examples

Adding Radius Circles Around Locations

This example demonstrates how to add radius circles around all locations currently in view. It combines getCurrentLocations(), addCustomLayer(), and event listening to create dynamic visualizations.

JavaScript
// Add radius circles around locations after each search
Storepoint.on('search', function() {
    var locations = Storepoint.getCurrentLocations();
    
    // Remove existing layer
    Storepoint.removeCustomLayer('radius-circles');
    
    // Create circle features for each location
    var features = locations.map(function(location) {
        return createCircleFeature(
            [location.loc_long, location.loc_lat],
            50 // radius in miles
        );
    });
    
    // Add the layer
    Storepoint.addCustomLayer('radius-circles', {
        type: 'fill',
        fillColor: '#000000',
        fillOpacity: 0.15,
        strokeWidth: 1,
        strokeColor: '#000000',
        strokeOpacity: 0.3,
        data: {
            type: 'FeatureCollection',
            features: features
        }
    });
});

// Helper: Create a circle as a GeoJSON polygon
function createCircleFeature(center, radiusInMiles) {
    var points = 64;
    var lng = center[0];
    var lat = center[1];
    var coordinates = [];
    
    var distanceX = radiusInMiles / (111.320 * Math.cos(lat * Math.PI / 180) * 0.621371);
    var distanceY = radiusInMiles / (110.574 * 0.621371);
    
    for (var i = 0; i < points; i++) {
        var angle = (i / points) * (2 * Math.PI);
        coordinates.push([
            lng + distanceX * Math.cos(angle),
            lat + distanceY * Math.sin(angle)
        ]);
    }
    coordinates.push(coordinates[0]); // Close the polygon
    
    return {
        type: 'Feature',
        geometry: { type: 'Polygon', coordinates: [coordinates] }
    };
}

This example creates radius circles around each location to visualize coverage areas. The circles are added as a custom layer using GeoJSON polygons. The radius calculation uses a simplified approximation that works well for most use cases.

Creating a Dual-Range Filter

This example shows how to create a sophisticated dual-range filter for filtering locations by a numeric range (such as square footage or price).

This is a more advanced example that demonstrates the flexibility of custom filters.

JavaScript
// Add a custom dual range filter after widget loads
Storepoint.on('load', function() {
    Storepoint.addCustomFilter({
        type: 'custom',
        id: 'sq_ft_range',
    
    // Filtering function
    filter: function(locations, filterValue) {
        if (!filterValue) return locations;
        
        const min = filterValue[0];
        const max = filterValue[1];
        
        return locations.filter(location => {
            // Find square footage values in custom fields
            let sqFt = null;
            
            // Check if custom_fields exist
            if (location.custom_fields && location.custom_fields.text) {
                // Find the square footage field by its token
                const sqFtField = location.custom_fields.text.find(
                    field => field.token === "square_footage_token"
                );
                
                if (sqFtField) {
                    sqFt = parseFloat(sqFtField.value);
                }
            }
            
            // If square footage exists and is in range, include the location
            if (!isNaN(sqFt)) {
                return sqFt >= min && sqFt <= max;
            }
            
            // If no square footage data, don't filter out
            return true;
        });
    },
    
    // Configuration for the filter
    rangeConfig: {
        minValue: 0,
        maxValue: 10000,
        defaultMin: 0,
        defaultMax: 10000,
        step: 100
    },
    
    // HTML template for the filter UI
    template: `
        <div class="range-filter">
            <div class="filter-header">Square Footage</div>
            <div class="range-inputs">
                <input type="number" class="min-input" placeholder="Min">
                <span>to</span>
                <input type="number" class="max-input" placeholder="Max">
                <span>sq ft</span>
            </div>
            <div class="range-slider">
                <input type="range" class="min-slider" min="0" max="10000" step="100" value="0">
                <input type="range" class="max-slider" min="0" max="10000" step="100" value="10000">
            </div>
        </div>
    `,
    
    // Setup function (simplified version)
    setup: function(container, updateValue) {
        const minInput = container.querySelector('.min-input');
        const maxInput = container.querySelector('.max-input');
        const minSlider = container.querySelector('.min-slider');
        const maxSlider = container.querySelector('.max-slider');
        
        // Handle input changes and update the filter value
        function handleChange() {
            const min = parseInt(minInput.value) || 0;
            const max = parseInt(maxInput.value) || 10000;
            updateValue([min, max]);
        }
        
        // Set up event listeners
        minInput.addEventListener('input', handleChange);
        maxInput.addEventListener('input', handleChange);
        minSlider.addEventListener('input', function() {
            minInput.value = this.value;
            handleChange();
        });
        maxSlider.addEventListener('input', function() {
            maxInput.value = this.value;
            handleChange();
        });
        
        // Initialize with default values
        minInput.value = 0;
        maxInput.value = 10000;
        updateValue([0, 10000]);
    },
    
    // Default filter value
    defaultValue: [0, 10000],
    
    // CSS styles for the filter (simplified)
    styles: `
        .range-filter {
            padding: 10px;
            background: #f5f5f5;
            border-radius: 4px;
        }
        .filter-header {
            font-weight: bold;
            margin-bottom: 8px;
        }
        .range-inputs {
            display: flex;
            align-items: center;
            gap: 8px;
            margin-bottom: 8px;
        }
        .min-input, .max-input {
            width: 80px;
            padding: 6px;
            border: 1px solid #ddd;
            border-radius: 4px;
        }
        .range-slider {
            position: relative;
            height: 30px;
        }
        .min-slider, .max-slider {
            position: absolute;
            width: 100%;
            pointer-events: none;
        }
        .min-slider::-webkit-slider-thumb, .max-slider::-webkit-slider-thumb {
            pointer-events: auto;
        }
    `
    });
});

Integrations

Google Analytics / Google Tag Manager

Track widget interactions in Google Analytics. This example sends events to gtag.

JavaScript
// Google Analytics GTM Events
// Add this anywhere on your page

Storepoint.on('load', function() {
    gtag('event', 'storepoint_widget_load');
});

Storepoint.on('search', function() {
    gtag('event', 'storepoint_search');
});

Storepoint.on('location-result-item-click', function() {
    gtag('event', 'storepoint_location_click', {
        click_source: 'list'
    });
});

Storepoint.on('map-marker-click', function() {
    gtag('event', 'storepoint_location_click', {
        click_source: 'map'
    });
});

Google Tag Manager (dataLayer)

If you're using Google Tag Manager, push events to the dataLayer instead:

JavaScript
Storepoint.on('load', function() {
    dataLayer.push({ event: 'storepoint_widget_load' });
});

Storepoint.on('search', function() {
    dataLayer.push({ event: 'storepoint_search' });
});

Storepoint.on('location-result-item-click', function() {
    dataLayer.push({ event: 'storepoint_location_click' });
});

Storepoint.on('map-marker-click', function() {
    dataLayer.push({ event: 'storepoint_location_click' });
});

To use these events in GTM, create a Custom Event trigger for each event name (e.g., storepoint_search) and connect it to your GA4 Event tag. See the GTM Custom Event documentation.

Interested in other events or functions?

We're constantly expanding our API capabilities. Contact us to discuss your specific needs or request new features.

Contact us at [email protected]