Way back in the day I used to drop Google Maps embeds into client sites all the time but… nothing in life is free any more it seems! Actually, in most cases Google Maps is still “free” within their fair use policy – Most small businesses will never hit their monthly usage limits. But I started to wonder if there was a risk that some kind of hack/attack might cause a huge spike in apparent map users. And if they did, there is potential for getting a hefty bill from Google! Also, they require a billing account now, which some small businesses may be reluctant to hand out.
So I had a look for another actually free option to see what I could build with it, and I found Leaflet (JavaScript library for making maps) using the OpenStreetMap tiles (the bits that actually looks like a map). Then I set about building a plugin.
Leaflet’s pretty easy to install like any other JS library, and there’s plenty of documentation to go along with it. Then I started building a WP plugin…
Name your own markers
This plugin started out life as code in a site theme that needed to show a location of three buildings run by one organisation. I decided to spin it out into its own plugin so I could drop it in future projects. Which brought me to the first major decision: I would not hardcode what a ‘location’ is called.
The first instinct for a plugin like this is to register a “Location” post type. Rather than do this, I created a map settings screen where you can invent your own marker types: “Shop” / “Shops”, “Point of interest” / “Points of interest”, “Theme park” / “Theme parks”, whatever the site needs. Each can have their own user defined colour and icon. Each type becomes it’s own WordPress post type (much like I did in the SC Events Manager plugin) which are grouped under a “Map Locations” admin menu.

Also just like my SC Events Manager plugin, there are some guards in place to stop locations from being deleted too easily. When you click delete on a post type you need to type in the name of the type in order to properly remove it.
Drawing markers without Google
The map itself is Leaflet (tiles from OpenStreetMap), a marker per post, a popup with the name/excerpt/photo/link on click. Leaflet’s divIcon has no “give this marker a background colour” option, because the icon is just arbitrary HTML dropped into a wrapping <div>. So each marker type’s colour gets painted directly onto the pin’s DOM element after Leaflet’s placed it on the page, not passed in as configuration. Coloured markers exist because the plugin paints them.
The more interesting bit is what a theme can do with a rendered map afterwards. Once a map finishes initialising, it fires a scm:ready custom event and leaves every Leaflet marker parked on the container element itself, keyed by post ID. That means a theme’s own JS can grab a specific marker and call a JS script on it whenever it wants, without ever touching Leaflet internals directly. I use it with .scmSetHover(true) so users can interact with a list of places next to a map: where hovering a list item lights up the matching pin.
Timing problems
Two small issues about when WordPress lets you act:
- The map’s CSS is enqueued unconditionally on every front-end page load, whether or not a map ever ends up rendering. That looks wasteful until you notice
wp_head()— and therefore the point styles actually get printed — fires before the page’s own content renders. If you wait until you know for certain a map is coming before enqueuing its stylesheet, and you’ve already missed the one moment WordPress was willing to print it. The JS half doesn’t have this problem at all — it’s registered to print in the footer, which runs after all content, so by then the plugin knows whether a map showed up, and only pays the ~150KB Leaflet cost on pages that actually need it.
- Creating or deleting a marker type can’t flush WordPress’s rewrite rules in the same request that saves it, because the request handling the form submission runs before
initre-registers the post types on the next page load — the very hook that would make the new type’s URLs exist at all. So instead of flushing directly, the handler just drops a transient; the next `init` notices it, flushes, and deletes it. One request creates the type, the next one makes its URLs real.
Where it’s used
This site runs SC Maps in a component that has a list of places on the left, and an interactive map on the right. Hovering over a list item lights up its corresponding pin (on_hover="tooltip" plus that scm:ready/scmSetHover hook mentioned above doing the actual work), clicking one swaps in that place’s full write-up below the map. None of this behaviour lives in the plugin — it’s built entirely in the theme, on top of two small hooks the plugin exposes for this purpose. Plugin draws the map; theme decides what “selecting a place” means on this particular page.

Next developments?
The next things to tackle is probably marker clustering for a site with more pins than screen space — The Hookable interface feature already implemented means it’ll slot in as one more class.
At the moment, the plugin could be used by somebody with no technical knowledge (beyond using shortcodes) to drop simple maps into a site. However, I built it to allow developers to use the site’s theme code to extend what’s possible – for example if you wanted to make rich interactive markers. To bridge this gap, I could build a “marker bubble builder” perhaps with drag and drop interface that would allow basic users to create rich markers easily.