> For the complete documentation index, see [llms.txt](https://wplake.gitbook.io/advanced-views/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://wplake.gitbook.io/advanced-views/post-selections/guides/cpt-items-map.md).

# How to display all CPT items on a single map

In Advanced Views, [*Post Selections*](/advanced-views/getting-started/first-post-selection.md) make it easy to query and display Custom Post Type (CPT) items. With full control over the output template, you can present the results in virtually any format—including an interactive map, where each CPT item is displayed as a marker.

This approach is especially useful when you want to display multiple locations automatically. For example, if you have a **Company Offices** custom post type, you can show all office locations on a single map without manually adding each office to a map field.

## 1. Create a marker Layout

Create a new **Layout** that defines how each marker and its popup will appear.

Include the following fields:

* **Map field** – stores the coordinates used to position the marker.
* **Post Title** – displayed as the popup title.
* **Featured Image** – provides visual context.
* **Excerpt** – displays a brief description.

This Layout will be rendered inside each marker popup when a user clicks a marker on the map.

## 2. Create a marker Selection

Next, create a **Post Selection** that queries your custom post type.

Configure it as follows:

* Select the **Layout** created in the previous step.
* Set **Post Type** to your custom post type (for example, **Events**).
* Set **Limit** to **-1** to return all matching posts.

Save the Post Selection and copy its shortcode.

#### Verify the query

Insert the shortcode into the page where you want the map to appear.

Save the page and verify that your posts are displayed.

At this stage, the output will simply be a list of Layouts. That's expected—the purpose of this step is only to confirm that the *Post Selection* returns the correct posts.

If no items appear, review your *Post Selection* settings before continuing.

## 3. Build the Map

Download the latest **Leaflet** JavaScript and CSS files from the official website and place them somewhere inside your theme, for example:`/wp-content/themes/YOUR_THEME/assets/leaflet/`

### 3.1) Adjust Selection template

Open the **Post Selection** template and replace its contents with the following template:

Twig example:

```twig
<projects-map class="{{ _selection.classes }}projects-map">
    <div class="projects-map__items">
        {% for post_id in _selection.post_ids %}
            [avf-layout id="{{ _card.view_id }}" object-id="{{ post_id }}" class="projects-map__item"]
        {% endfor %}
    </div>

    <div class="projects-map__wrapper">
        <div class="projects-map__map"></div>
    </div>

</projects-map>
```

This template renders every queried post using the Layout you created earlier. The rendered HTML will later become the popup content for each map marker.

### 3.2) Add map styles

Import Leaflet's stylesheet and add the following CSS to the **Post Selection** stylesheet.

```css
@import url('/wp-content/themes/YOUR_THEME/assets/leaflet/leaflet.css');

#post-selection {
    display: block;
}

#this__items {
    display: none;
}

#this__wrapper {
    position: relative;
    width: 100%;
    height: 60vh;
}

#this__map {
    position: absolute;
    inset: 0;
}
```

Replace `YOUR_THEME` with the path to your active theme.

The rendered Layouts are hidden because they're used only as the source for marker popups—the visible output is the interactive map.

### 3.3) Add map JavaScript

Next, add the JavaScript that initializes the map and creates a marker for each queried item.

The **Advanced Views** automatically scopes JavaScript to the Post Selection and loads it only on pages where the shortcode is present, so no additional enqueue logic is required.

Add the following script:

```javascript
import '/wp-content/themes/YOUR_THEME/assets/leaflet/leaflet.min.js';

let _this = this;

function setupMap(element) {
    let currentZoom = window.innerWidth < 992 ?
        0.75 :
        2;
    let minZoom = window.innerWidth < 992 ?
        0.25 :
        2;

    let map = L.map(element, {
        // todo map settings
        minZoom: minZoom,
        maxZoom: 8,
        attributionControl: false,
        zoomControl: false,
        zoomSnap: 0.25, // allow fractional zoom levels
        worldCopyJump: true,
    }).setView([40, 10], currentZoom);

    if (window.innerWidth < 992) {
        map.fitWorld();
    }

    // todo you can choose any provider from the following list https://leaflet-extras.github.io/leaflet-providers/preview/

    L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
        bounds: [[-75, -180], [81, 180]],
    }).addTo(map);

    return map;
}

function setupMarkers(map, items) {
    let icons = {};

    let popupMaxWidth = window.innerWidth < 992 ?
        300 :
        600;

    items.forEach(item => {
        let lat = item.dataset['lat'] || 0;
        let lng = item.dataset['lng'] || 0;

        if (false === icons.hasOwnProperty(icon)) {
            icons[icon] = L.icon({
                // iconUrl: '', // todo you can put use your own icon url here
                iconSize: [49, 49], // size of the icon
                iconAnchor: [20, 49], // point of the icon which will correspond to marker's location
                popupAnchor: [5, -49] // point from which the popup should open relative to the iconAnchor
            });
        }

        let markerPosition = L.latLng(lat, lng);

        let marker = L.marker(markerPosition, {
            icon: icons[icon]
        }).bindPopup(item.outerHTML, {
            maxWidth: popupMaxWidth,
        });

        marker.addTo(map);
    });
}

function init() {
    let map = setupMap(_this.querySelector('.projects-map__map'));

    setupMarkers(map, _this.querySelectorAll('.projects-map__item'));
}

init();

```

{% hint style="warning" %}
Replace `YOUR_THEME` with the correct path to your Leaflet assets.
{% endhint %}

The comments marked with `todo` highlight areas you can customize, such as:

* map options
* default zoom level
* tile provider
* marker icon

#### Script explanation

The script consists of three main functions.

`setupMap()`

This function initializes the Leaflet map.

It:

* creates the map instance;
* configures the default zoom level;
* adjusts the zoom for smaller screens;
* loads the OpenStreetMap tiles;
* returns the initialized map object.

You're free to replace OpenStreetMap with any other Leaflet-compatible tile provider.

`setupMarkers()`

This function creates one marker for each rendered Layout.

For every `.projects-map__item`, it:

* reads the `data-lat` and `data-lng` attributes;
* creates a Leaflet marker;
* optionally assigns a custom icon;
* uses the rendered Layout HTML as the popup content;
* adds the marker to the map.

Since the popup contains the complete Layout HTML, you can customize its appearance using the Layout template and stylesheet.

`init()`

The `init()` function ties everything together.

It initializes the map, locates all rendered Layouts, and creates a marker for each one.

Because the script runs inside the Advanced Views Web Component, multiple maps can coexist on the same page without interfering with one another.

## 4. Configure the marker Layout

The final step is to expose the marker coordinates in the Layout template.

Open your **Layout** template and add `data-lat` and `data-lng` attributes to the root element.

Twig example:

```twig
<project-map-item
    class="{{ _view.classes }} project-map-item project-map-item--id--{{ _view.id }} project-map-item--object-id--{{ _view.object_id }}"
    data-lat="{{ location.lat }}" data-lng="{{ location.lng }}">
```

Replace `location` with the variable corresponding to your own map field. (you can identify the correct variable by inspecting the automatically generated Layout template)

The `data-lat` and `data-lng` attributes are used by the JavaScript to position each marker on the map.

Everything rendered by the Layout becomes the popup content, so you can freely customize the markup, styles, and additional fields displayed when users click a marker.

## 5. Summary

Reload the page containing your Post Selection shortcode, and you should see an **OpenStreetMap** displaying your custom post type items as markers. Clicking a marker will open a popup containing the content rendered by your **Layout**.

This example provides a solid foundation that you can extend to suit your project's requirements. For example, you can store a [custom marker icon in an *Image* field](/advanced-views/field-types/media/map-field/marker-from-image-field.md) and expose its URL through a data attribute, allowing each item to display its own marker image.

If your map contains a large number of markers, consider using the [Leaflet Marker Cluster plugin](https://github.com/Leaflet/Leaflet.markercluster). It groups nearby markers into clusters and automatically expands them as users zoom in, improving both performance and usability.

Leaflet offers a rich ecosystem of plugins and customization options, making it easy to add features such as marker clustering, filtering, custom controls, route overlays, heatmaps, or alternative tile providers as your project evolves.
