Decoupling WordPress Maps: A Headless-Ready Architecture with SCF and Leaflet

Pinning markers on a map

4 minutes read time.

Interactive maps are a staple of modern web development, but integrating them into WordPress often leads down a messy path. Between the rising costs of proprietary APIs like Google Maps, the clutter of inline <script> tags, and themes tightly coupled to backend data, map implementations can quickly turn into a technical debt nightmare.

It was time for a clean slate. I recently went back into one of my older repositories to completely refactor how I approach mapping in WordPress. The goal? A robust, decoupled architecture using 100% open-source tools that is just as comfortable in a traditional theme as it is in a headless Jamstack environment.

Here is how I rebuilt my mapping architecture using Leaflet.js, OpenStreetMap, and Secure Custom Fields (SCF).

Ditching Paid APIs for Open Source

The first step was removing reliance on expensive, proprietary map providers. Leaflet.js is a brilliantly lightweight, open-source JavaScript library for interactive maps. By pairing it with tile layers from OpenStreetMap, you get a completely free, highly customizable mapping solution that doesn’t require API keys, credit cards, or strict rate limits.

The Shift to Secure Custom Fields (SCF)

With the recent shifts in the WordPress ecosystem, I transitioned the backend data structure from Advanced Custom Fields (ACF) to Secure Custom Fields (SCF).

Because SCF is a direct, community-maintained fork, it acts as a perfect drop-in replacement. In this architecture, we use it to create a dynamic Repeater field, allowing content editors to easily add multiple latitudes, longitudes, and popup descriptions to a single “Event” Custom Post Type (CPT).

True Separation of Concerns: Plugin vs. Theme

The biggest flaw in older map tutorials is mixing data creation with data presentation. If a user switches themes, they shouldn’t lose their map data!

To solve this, I split the architecture into two distinct, modular layers:

  1. The Core Plugin (Backend): A lightweight plugin handles the database layer. It registers the Event CPT and the SCF Repeater fields entirely via PHP. To make it a true plug-and-play resource, I added an automated script: the moment you activate the plugin, it generates dummy events across Europe (from Patras and Athens to Berlin and London) so you can test the map immediately.
  2. The Theme Presentation (Frontend): The frontend implementation lives safely in the child theme. It features a clean HTML wrapper (<div id="scf-leaflet-map"></div>) and intelligently enqueues the JavaScript logic only when an Event post or archive is being viewed.

Passing Data the Clean Way: wp_localize_script

Writing PHP variables directly into inline JavaScript is a security and performance anti-pattern. Instead, the backend securely passes the SCF location data to the frontend JavaScript engine using wp_localize_script.

The frontend JS simply listens for this structured object and plots the markers. I also upgraded the logic to include Auto-Framing. Whether you are looking at a single event with one marker, or a massive master archive map with dozens of locations, Leaflet’s featureGroup automatically calculates the boundaries and zooms to fit perfectly.

JavaScript

document.addEventListener("DOMContentLoaded", function () {
    if (!document.getElementById("scf-leaflet-map") || typeof scfMapData === "undefined") return;

    const map = L.map('scf-leaflet-map');
    L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png').addTo(map);

    const markers = [];
    scfMapData.locations.forEach(location => {
        const marker = L.marker([location.latitude, location.longitude]).addTo(map);
        if (location.popup_text) marker.bindPopup(location.popup_text);
        markers.push(marker);
    });

    // Auto-fit the map bounds to show all markers dynamically
    if (markers.length > 0) {
        const group = new L.featureGroup(markers);
        map.fitBounds(group.getBounds(), { padding: [30, 30] });
        if (markers.length === 1) map.setZoom(13);
    }
});

Ready for the Jamstack

Because I often build in headless environments using Gatsby and Netlify, I couldn’t just build a classic PHP theme implementation and call it a day.

The core plugin also registers a custom REST API endpoint (/wp-json/custom/v1/event-maps). This endpoint intentionally bypasses the heavy, standard WordPress REST responses. Instead, it delivers a clean, lightning-fast JSON payload containing only the map coordinates and popup HTML, ready to be consumed by any decoupled React, Vue, or Astro application.

Explore the Code

Building clean, decoupled architecture is an ongoing journey of refactoring and refining. If you want to see the full implementation, explore the code, or drop it into your own local environment, check out the freshly launched repository on GitHub: Explore the wordpress-scf-leaflet repository on GitHub

Share this article:


Comments

Leave a Reply

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

This site uses Akismet to reduce spam. Learn how your comment data is processed.