Best WordPress Map Plugins for Multiple Locations

How to Create a WordPress Map Plugin for Multiple Locations: A Developer’s Guide

Displaying multiple locations on your WordPress site is essential for businesses with multiple stores, service areas, or event venues. While you can build a custom plugin, it requires significant development expertise. This guide will show you how to create a functional multiple location map plugin from scratch.

This method requires advanced PHP/JavaScript knowledge and a Google Cloud account with billing enabled.

Method 1: Build a Custom Multiple Locations Map Plugin

This approach creates a professional-grade solution but involves complex development work.

Step 1: Google Maps Platform Setup

  • 1.  Create a Google Cloud Project at [console.cloud.google.com]
  • 2.  Enable Billing (utilizing the $200 monthly credit)
  • 3.  Enable Maps JavaScript API and Geocoding API
  • 4.  Create and restrict an API Key to your domain

Enable the necessary APIs for your custom plugin.

Step 2: Create the Custom WordPress Plugin

Create a new directory `/wp-content/plugins/multi-location-maps/` with these files:

A. Main plugin file: `multi-location-maps.php`

php

<?php
/**
 * Plugin Name: Multi Location Maps
 * Description: Advanced multiple location mapping with custom post types and shortcodes
 * Version: 2.0
 * Author: Your Name
 */

defined('ABSPATH') or die('No direct access allowed!');

class WP_Multi_Location_Maps {
    
    private $api_key;
    
    public function __construct() {
        $this->api_key = get_option('wmlm_api_key', '');
        
        add_action('init', array($this, 'register_location_post_type'));
        add_action('add_meta_boxes', array($this, 'add_location_meta_boxes'));
        add_action('save_post', array($this, 'save_location_metadata'));
        add_action('admin_menu', array($this, 'add_admin_menu'));
        add_action('admin_init', array($this, 'register_settings'));
        add_shortcode('wmlm_map', array($this, 'render_map_shortcode'));
        add_action('wp_enqueue_scripts', array($this, 'enqueue_scripts'));
        register_activation_hook(__FILE__, array($this, 'activate_plugin'));
    }
    
    public function activate_plugin() {
        flush_rewrite_rules();
    }
    
    public function register_location_post_type() {
        $args = array(
            'public' => true,
            'label'  => 'Locations',
            'supports' => array('title', 'editor', 'thumbnail'),
            'menu_icon' => 'dashicons-location',
            'show_in_rest' => true,
            'has_archive' => true
        );
        register_post_type('location', $args);
    }
    
    public function add_location_meta_boxes() {
        add_meta_box('location_coordinates', 'Location Details', array($this, 'render_coordinates_meta_box'), 'location', 'normal', 'high');
    }
    
    public function render_coordinates_meta_box($post) {
        wp_nonce_field('wmlm_save_location', 'wmlm_nonce');
        
        $address = get_post_meta($post->ID, '_wmlm_address', true);
        $lat = get_post_meta($post->ID, '_wmlm_lat', true);
        $lng = get_post_meta($post->ID, '_wmlm_lng', true);
        $phone = get_post_meta($post->ID, '_wmlm_phone', true);
        ?>
        <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 15px;">
            <div>
                <label><strong>Full Address:</strong></label>
                <input type="text" name="wmlm_address" value="<?php echo esc_attr($address); ?>" style="width: 100%;" placeholder="Enter full address for geocoding">
            </div>
            <div>
                <label><strong>Phone Number:</strong></label>
                <input type="text" name="wmlm_phone" value="<?php echo esc_attr($phone); ?>" style="width: 100%;">
            </div>
            <div>
                <label><strong>Latitude:</strong></label>
                <input type="text" name="wmlm_lat" value="<?php echo esc_attr($lat); ?>" style="width: 100%;">
            </div>
            <div>
                <label><strong>Longitude:</strong></label>
                <input type="text" name="wmlm_lng" value="<?php echo esc_attr($lng); ?>" style="width: 100%;">
            </div>
        </div>
        <p><small><em>Leave latitude/longitude empty and save to attempt automatic geocoding from address.</em></small></p>
        <?php
    }
    
    public function save_location_metadata($post_id) {
        if (!isset($_POST['wmlm_nonce']) || !wp_verify_nonce($_POST['wmlm_nonce'], 'wmlm_save_location')) {
            return;
        }
        
        if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) return;
        
        $fields = array('address', 'lat', 'lng', 'phone');
        foreach ($fields as $field) {
            if (isset($_POST['wmlm_' . $field])) {
                update_post_meta($post_id, '_wmlm_' . $field, sanitize_text_field($_POST['wmlm_' . $field]));
            }
        }
        
        // Auto-geocode if coordinates are empty but address exists
        $address = get_post_meta($post_id, '_wmlm_address', true);
        $lat = get_post_meta($post_id, '_wmlm_lat', true);
        $lng = get_post_meta($post_id, '_wmlm_lng', true);
        
        if ($address && (!$lat || !$lng) && $this->api_key) {
            $this->geocode_address($post_id, $address);
        }
    }
    
    private function geocode_address($post_id, $address) {
        $url = 'https://maps.googleapis.com/maps/api/geocode/json?address=' . urlencode($address) . '&key=' . $this->api_key;
        
        $response = wp_remote_get($url);
        if (is_wp_error($response)) return false;
        
        $data = json_decode(wp_remote_retrieve_body($response), true);
        
        if ($data['status'] === 'OK' && !empty($data['results'][0]['geometry']['location'])) {
            $location = $data['results'][0]['geometry']['location'];
            update_post_meta($post_id, '_wmlm_lat', $location['lat']);
            update_post_meta($post_id, '_wmlm_lng', $location['lng']);
            return true;
        }
        
        return false;
    }
    
    public function add_admin_menu() {
        add_options_page('Multi Location Maps Settings', 'Map Settings', 'manage_options', 'wmlm-settings', array($this, 'render_settings_page'));
    }
    
    public function register_settings() {
        register_setting('wmlm_settings', 'wmlm_api_key');
        register_setting('wmlm_settings', 'wmlm_default_zoom');
        register_setting('wmlm_settings', 'wmlm_map_style');
    }
    
    public function render_settings_page() {
        ?>
        <div class="wrap">
            <h1>Multi Location Maps Settings</h1>
            <form method="post" action="options.php">
                <?php settings_fields('wmlm_settings'); ?>
                <table class="form-table">
                    <tr>
                        <th scope="row">Google Maps API Key</th>
                        <td><input type="text" name="wmlm_api_key" value="<?php echo esc_attr(get_option('wmlm_api_key')); ?>" class="regular-text"></td>
                    </tr>
                    <tr>
                        <th scope="row">Default Zoom Level</th>
                        <td><input type="number" name="wmlm_default_zoom" value="<?php echo esc_attr(get_option('wmlm_default_zoom', '10')); ?>" min="1" max="20"></td>
                    </tr>
                </table>
                <?php submit_button(); ?>
            </form>
        </div>
        <?php
    }
    
    public function enqueue_scripts() {
        global $post;
        if (is_a($post, 'WP_Post') && has_shortcode($post->post_content, 'wmlm_map')) {
            wp_enqueue_script('google-maps', 'https://maps.googleapis.com/maps/api/js?key=' . $this->api_key . '&loading=async', array(), null, true);
            wp_enqueue_script('wmlm-frontend', plugin_dir_url(__FILE__) . 'assets/frontend.js', array('jquery'), '1.0', true);
            
            // Pass data to JavaScript
            $locations = $this->get_all_locations();
            wp_localize_script('wmlm-frontend', 'wmlmData', array(
                'locations' => $locations,
                'defaultZoom' => get_option('wmlm_default_zoom', 10),
                'mapStyle' => get_option('wmlm_map_style', '[]')
            ));
            
            wp_enqueue_style('wmlm-styles', plugin_dir_url(__FILE__) . 'assets/styles.css', array(), '1.0');
        }
    }
    
    private function get_all_locations() {
        $locations = array();
        $posts = get_posts(array(
            'post_type' => 'location',
            'numberposts' => -1,
            'post_status' => 'publish'
        ));
        
        foreach ($posts as $post) {
            $locations[] = array(
                'id' => $post->ID,
                'title' => $post->post_title,
                'description' => wp_trim_words($post->post_content, 25),
                'address' => get_post_meta($post->ID, '_wmlm_address', true),
                'phone' => get_post_meta($post->ID, '_wmlm_phone', true),
                'lat' => floatval(get_post_meta($post->ID, '_wmlm_lat', true)),
                'lng' => floatval(get_post_meta($post->ID, '_wmlm_lng', true)),
                'thumbnail' => get_the_post_thumbnail_url($post->ID, 'thumbnail')
            );
        }
        
        return $locations;
    }
    
    public function render_map_shortcode($atts) {
        $atts = shortcode_atts(array(
            'height' => '500px',
            'width' => '100%',
            'zoom' => 'auto',
            'cluster' => 'true'
        ), $atts);
        
        ob_start();
        ?>
        <div class="wmlm-map-container">
            <div class="wmlm-map" 
                 data-height="<?php echo esc_attr($atts['height']); ?>"
                 data-width="<?php echo esc_attr($atts['width']); ?>"
                 data-zoom="<?php echo esc_attr($atts['zoom']); ?>"
                 data-cluster="<?php echo esc_attr($atts['cluster']); ?>"
                 style="height: <?php echo esc_attr($atts['height']); ?>; width: <?php echo esc_attr($atts['width']); ?>;">
            </div>
            <div class="wmlm-locations-list"></div>
        </div>
        <?php
        return ob_get_clean();
    }
}

new WP_Multi_Location_Maps();
?>
Code language: HTML, XML (xml)

B. Create frontend JavaScript: `/assets/frontend.js

javascript

(function($) {
    'use strict';
    
    class MultiLocationMap {
        constructor(container) {
            this.container = container;
            this.map = null;
            this.markers = [];
            this.infoWindow = null;
            this.settings = {
                height: container.data('height') || '500px',
                width: container.data('width') || '100%',
                zoom: container.data('zoom') || 'auto',
                cluster: container.data('cluster') === 'true'
            };
            
            this.init();
        }
        
        init() {
            if (typeof google === 'undefined') {
                console.error('Google Maps API not loaded');
                return;
            }
            
            this.createMap();
            this.addMarkers();
            this.addListings();
        }
        
        createMap() {
            const center = this.calculateCenter();
            
            this.map = new google.maps.Map(this.container[0], {
                zoom: this.settings.zoom === 'auto' ? 10 : parseInt(this.settings.zoom),
                center: center,
                styles: JSON.parse(wmlmData.mapStyle || '[]'),
                mapTypeControl: false,
                streetViewControl: true,
                fullscreenControl: true
            });
            
            this.infoWindow = new google.maps.InfoWindow();
        }
        
        calculateCenter() {
            if (wmlmData.locations.length === 0) {
                return { lat: 40.7128, lng: -74.0060 }; // Default to NYC
            }
            
            const bounds = new google.maps.LatLngBounds();
            wmlmData.locations.forEach(location => {
                bounds.extend(new google.maps.LatLng(location.lat, location.lng));
            });
            
            return bounds.getCenter();
        }
        
        addMarkers() {
            const bounds = new google.maps.LatLngBounds();
            
            wmlmData.locations.forEach(location => {
                const marker = new google.maps.Marker({
                    position: { lat: location.lat, lng: location.lng },
                    map: this.map,
                    title: location.title,
                    icon: this.getCustomIcon()
                });
                
                this.markers.push(marker);
                bounds.extend(marker.getPosition());
                
                // Add click listener
                marker.addListener('click', () => {
                    this.showInfoWindow(marker, location);
                });
            });
            
            // Fit map to show all markers
            if (wmlmData.locations.length > 1 && this.settings.zoom === 'auto') {
                this.map.fitBounds(bounds);
            }
        }
        
        getCustomIcon() {
            // Return custom marker icon or null for default
            return null;
        }
        
        showInfoWindow(marker, location) {
            const content = `
                <div class="wmlm-info-window">
                    ${location.thumbnail ? `<img src="${location.thumbnail}" alt="${location.title}" style="max-width: 100px; float: left; margin-right: 10px;">` : ''}
                    <h3 style="margin: 0 0 8px 0;">${location.title}</h3>
                    <p style="margin: 0 0 5px 0; color: #666;">${location.address}</p>
                    ${location.phone ? `<p style="margin: 0 0 5px 0;">📞 ${location.phone}</p>` : ''}
                    <p style="margin: 0; font-size: 14px;">${location.description}</p>
                    <div style="clear: both;"></div>
                </div>
            `;
            
            this.infoWindow.setContent(content);
            this.infoWindow.open(this.map, marker);
        }
        
        addListings() {
            const $list = $('.wmlm-locations-list');
            if ($list.length === 0) return;
            
            let html = '<div class="wmlm-locations">';
            wmlmData.locations.forEach(location => {
                html += `
                    <div class="wmlm-location-item" data-lat="${location.lat}" data-lng="${location.lng}">
                        <h4>${location.title}</h4>
                        <p class="address">${location.address}</p>
                        ${location.phone ? `<p class="phone">${location.phone}</p>` : ''}
                    </div>
                `;
            });
            html += '</div>';
            
            $list.html(html);
            
            // Add click handlers for listings
            $('.wmlm-location-item').on('click', (e) => {
                const $item = $(e.currentTarget);
                const lat = parseFloat($item.data('lat'));
                const lng = parseFloat($item.data('lng'));
                
                this.map.panTo({ lat, lng });
                this.map.setZoom(15);
            });
        }
    }
    
    // Initialize maps when document is ready
    $(document).ready(function() {
        $('.wmlm-map').each(function() {
            new MultiLocationMap($(this));
        });
    });
    
})(jQuery);
Code language: JavaScript (javascript)

C. Create CSS file: `/assets/styles.css

css

.wmlm-map-container {
    margin: 20px 0;
    border-radius: 8px;
    overflow: hidden;
    box-shadow: 0 4px 12px rgba(0,0,0,0.1);
}

.wmlm-map {
    border-radius: 8px;
}

.wmlm-locations {
    padding: 20px;
    background: #f9f9f9;
}

.wmlm-location-item {
    padding: 15px;
    margin-bottom: 10px;
    background: white;
    border-radius: 6px;
    cursor: pointer;
    transition: all 0.3s ease;
    border-left: 4px solid #4CAF50;
}

.wmlm-location-item:hover {
    transform: translateX(5px);
    box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}

.wmlm-location-item h4 {
    margin: 0 0 8px 0;
    color: #2c3e50;
}

.wmlm-location-item .address {
    margin: 0 0 5px 0;
    color: #666;
    font-size: 14px;
}

.wmlm-location-item .phone {
    margin: 0;
    color: #4CAF50;
    font-weight: bold;
}

.wmlm-info-window {
    max-width: 300px;
    font-family: Arial, sans-serif;
}

.wmlm-info-window img {
    border-radius: 4px;
}
Code language: CSS (css)

Step 3: Install and Use the Plugin

1.  Zip the plugin folder and upload via WordPress Admin

2.  Activate the plugin and configure your API key in Settings > Map Settings

3.  Add locations through the new Locations menu

4.  Use shortcode `[wmlm_map]` on any page or post

Manage locations through a custom post type with detailed metadata

The Challenges of This Custom Plugin Development

While this solution offers complete control, it comes with significant challenges:

  1. Complex Development: Requires advanced PHP, JavaScript, and WordPress API knowledge
  2. Maintenance Burden: You’re responsible for security updates, WordPress compatibility, and bug fixes
  3. Limited Geocoding: Basic geocoding implementation without error handling or batch processing
  4. No Advanced Features: Missing marker clustering, category filters, live search, or import/export functionality
  5. API Management Complexity: Requires ongoing monitoring of Google Cloud usage, quotas, and costs
  6. Time Investment: Development, testing, and debugging can take weeks of dedicated work
  7. No Support: You’re on your own for troubleshooting and feature requests

Get a Professional Multi-Location Map in Minutes with MapsFun.com

Why spend weeks developing and maintaining a complex plugin when you can have a superior solution instantly?

MapsFun.com offers the ultimate WordPress multiple location mapping solution:

  • 1.  Zero Coding Required: Create stunning, feature-rich maps with our visual editor—no development needed
  • 2.  Automatic Geocoding: Simply enter addresses—we handle all coordinate processing automatically
  • 3.  Advanced Features: Marker clustering, category filters, live search, custom styling, and import/export
  • 4.  Easy WordPress Integration: Simple plugin installation with seamless WordPress integration
  • 5.  Fully Managed: No API key management, no security concerns, automatic updates and support
  • 6.  Professional Support: Get help when you need it from our dedicated support team

Stop wasting time on complex development and maintenance. Create a professional, feature-rich multi-location map in just a few clicks at MapsFun.com