Geo Service
← All Guides & Resources • JavaScript Tutorial

Instant Canadian Address Autocomplete in Leaflet JS

Learn how to implement a blazing-fast, typo-tolerant address search bar on a Leaflet map using Geo Service's sovereign Canadian Photon endpoint.

⚡ What You Will Build

A pure Vanilla JavaScript search input that triggers instant address suggestions across all Canadian provinces and territories as the user types, flies the map camera to the selected coordinate, and drops a pin—without external bloated dependencies.

Step 1: HTML Setup & Leaflet Container

Include Leaflet's assets and create a simple container with an overlay search input:

index.html HTML
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title>Canadian Address Autocomplete</title>
    <link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
    <style>
        #map { height: 500px; width: 100%; border-radius: 12px; }
        .search-box { position: absolute; top: 15px; left: 60px; z-index: 1000; width: 320px; }
        .search-input { width: 100%; padding: 10px 14px; font-size: 14px; border-radius: 8px; border: 1px solid #ccc; box-shadow: 0 2px 6px rgba(0,0,0,0.15); }
        .results-list { background: white; list-style: none; padding: 0; margin: 4px 0 0; border-radius: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.15); max-height: 220px; overflow-y: auto; }
        .results-list li { padding: 8px 12px; cursor: pointer; border-bottom: 1px solid #eee; font-size: 13px; }
        .results-list li:hover { background: #f0f4ff; }
    </style>
</head>
<body>
    <div style="position: relative;">
        <div class="search-box">
            <input type="text" id="address-input" class="search-input" placeholder="Search Canadian address..." autocomplete="off">
            <ul id="results" class="results-list" style="display: none;"></ul>
        </div>
        <div id="map"></div>
    </div>

    <script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
    <script src="app.js"></script>
</body>
</html>

Step 2: Connect Autocomplete to the Photon API

In your app.js, initialize the map and fetch address suggestions with debounce. Notice how we pass your domain-scoped Browser Key in the X-Api-Key header:

app.js JavaScript
// 1. Initialize map centered on Toronto / Southern Ontario
const map = L.map('map').setView([43.6532, -79.3832], 12);
L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
    maxZoom: 19,
    attribution: '© OpenStreetMap contributors'
}).addTo(map);

let activeMarker = null;
const input = document.getElementById('address-input');
const resultsList = document.getElementById('results');

// 2. Debounced search listener
let debounceTimeout = null;
input.addEventListener('input', (e) => {
    clearTimeout(debounceTimeout);
    const query = e.target.value.trim();
    if (query.length < 3) {
        resultsList.style.display = 'none';
        return;
    }

    debounceTimeout = setTimeout(async () => {
        try {
            // Photon Canadian geocoding endpoint with your browser key
            const url = `https://geo.jerielbenavides.com/api?q=${encodeURIComponent(query)}&limit=5&bbox=-141.0,41.6,-52.6,83.1`;
            const res = await fetch(url, {
                headers: { 'X-Api-Key': 'YOUR_BROWSER_KEY_HERE' }
            });
            const data = await res.json();
            renderResults(data.features || []);
        } catch (err) {
            console.error('Geocoding error:', err);
        }
    }, 250);
});

// 3. Render suggestions and handle pin placement
function renderResults(features) {
    resultsList.innerHTML = '';
    if (!features.length) {
        resultsList.style.display = 'none';
        return;
    }

    features.forEach(feature => {
        const p = feature.properties;
        const [lon, lat] = feature.geometry.coordinates;
        const label = [p.name, p.street, p.city, p.state, p.postcode].filter(Boolean).join(', ');

        const li = document.createElement('li');
        li.textContent = label;
        li.addEventListener('click', () => {
            input.value = label;
            resultsList.style.display = 'none';
            map.flyTo([lat, lon], 16);

            if (activeMarker) map.removeLayer(activeMarker);
            activeMarker = L.marker([lat, lon]).addTo(map).bindPopup(label).openPopup();
        });
        resultsList.appendChild(li);
    });
    resultsList.style.display = 'block';
}

🔒 Security Best Practice: Use Browser Keys

Never expose your secret Server Key in client-side JavaScript. Instead, navigate to your Dashboard → API Keys and create a Browser Key. Browser keys are restricted strictly to your allowed web domains (e.g. https://myapp.ca) and can only perform address lookups.

Ready to build your Canadian map experience?

Get your free API key in seconds and start searching Canadian addresses with zero query logging.