I’ve been really enjoying working with Wikidata lately, using it to make a map of government agencies with presence in the fediverse, a catalogue of prominent fediverse accounts, and a few fun bots. And this month’s Glitch Code Jams topic being “teaching a thing or two”, this is a great opportunity to teach how to use Wikidata and Mapbox together. Two things!
Here’s a quick look at what we will be making.
But first, a bit of introduction.
Wikidata is, in the words of its creators, a “central storage for the structured data” used by sites like Wikipedia, Wikivoyage, Wiktionary, Wikisource, and “many other sites and services”. You can use a special query language called SPARQL to get data out of it yourself, and they also offer an API that we will be using for our project.
And you can think of Mapbox as a Google Maps alternative, offering all the typical functionality of a good quality mapping software. We will use it to place the data retrieved from Wikidata on a map.
For this tutorial we are going to be using Glitch, a popular and user-friendly web-based editor that lets you make websites and apps. To follow along, you don’t really need an account, but you might want to create one if you’d like to keep your project, otherwise it will be removed after a few days.
Start by creating a “remix” of my starter website project.

Let’s first create a new file using the big plus sign next to Files button. As a file name, use js/modules/wikidata.js
. This will create a wikidata.js
file inside the js/modules
folder.
Inside this file we will define a function that will run our Wikidata query.
export default async (query) => {
const apiUrl = `https://query.wikidata.org/sparql?query=${encodeURIComponent(query)}&format=json`;
const resp = await fetch(apiUrl);
const respJSON = await resp.json();
const items = respJSON?.results?.bindings || [];
return items;
};
Great. Now, before we start writing our first query, let’s get familiar with SPARQL. Let’s head over to the Wikidata Query Service website. Here, click on the Examples button, and pick the very first one, labeled “Cats”. You will see a fairly simple query. Try running it using the big blue “Play” button.

Now try playing with the other examples, and clicking some of the links that come back with the results. Hopefully this will give you an idea of some of the possibilities that Wikidata gives us.
Let’s switch back to Glitch and open thejs/script.js
script file where we can use the wikidata
function we just wrote. I will go ahead and use the query from the first example.
import ready from "./modules/ready.js";
import wikidata from "./modules/wikidata.js";
ready(async () => {
const data = wikidata(`
SELECT ?item ?itemLabel
WHERE
{
?item wdt:P31 wd:Q146. # Must be a cat
SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE],en". } # Helps get the label in your language, if not, then en language
}
`)
});
With this code in place, you can open your browser’s developer console and you will see the results of the query. Great start!
Now, let’s go back to the Wikidata query editor. For this project, I would like to list all the different UNESCO World Heritage Sites. Here’s a query that will do just that.
SELECT DISTINCT ?item ?itemLabel ?itemDescription ?lon ?lat ?image
{
?item wdt:P1435 wd:Q9259 .
?item wdt:P131 ?place .
?item schema:description ?itemDescription FILTER (LANG(?itemDescription) = "en") .
?item wdt:P18 ?image;
p:P625 [
ps:P625 ?coord;
psv:P625 [
wikibase:geoLongitude ?lon;
wikibase:geoLatitude ?lat;
] ;
]
SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE],en". }
}
Note that I am doing a few things here in order to get location information and description. I won’t go too much into detail here, as this tutorial is meant to be a quick introduction, but you can browse the official SPARQL documentation yourself to learn more about how to build these kinds of queries.
Here’s the full code snippet.
import ready from "./modules/ready.js";
import wikidata from "./modules/wikidata.js";
ready(async () => {
const data = await wikidata(`
SELECT DISTINCT ?item ?itemLabel ?itemDescription ?lon ?lat ?image
{
?item wdt:P1435 wd:Q9259 .
?item wdt:P131 ?place .
?item schema:description ?itemDescription FILTER (LANG(?itemDescription) = "en") .
?item wdt:P18 ?image;
p:P625 [
ps:P625 ?coord;
psv:P625 [
wikibase:geoLongitude ?lon;
wikibase:geoLatitude ?lat;
] ;
]
SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE],en". }
}
`);
console.log(data);
});
If you check the browser console again, you will see a list of objects returned from Wikidata. (You can also run this query inside the Wikidata query editor to get a better visual representation.)
You can see that we get a lot of information, so let’s start by filtering out what we need by mapping the data
variable into a new worldHeritageSites
object, or rather an array of objects.
const worldHeritageSites = data.map((item) => {
return {
label: item?.itemLabel?.value,
description: item?.itemDescription?.value,
image: item?.image?.value,
url: item?.item?.value,
lat: item?.lat?.value,
lon: item?.lon?.value,
};
});
console.log(worldHeritageSites);
Great, things are moving along. Now it’s time to look at Mapbox. This will require you to set up a free account. Once you have one, go to your account dashboard, and create an API token for your project.
You can keep all of the default settings for now, just give it a name, and optionally restrict it to only work on your project’s URL, which you can get from the menu of your project’s preview.

We can use the example code from this article Mapbox tutorial, which shows how to add a marker to a map with a popup.
First, we will need to load some external files that contain the main Mapbox JavaScript and CSS code. Add the following two lines before the closing </head>
tag.
<link href="https://api.mapbox.com/mapbox-gl-js/v3.3.0/mapbox-gl.css" rel="stylesheet">
<script src="https://api.mapbox.com/mapbox-gl-js/v3.3.0/mapbox-gl.js"></script>
Next, add a DIV
element with its id
attribute set to "map"
, where you want the map to load, like this.
<main class="flex-shrink-0">
<div class="container">
<h1 class="mt-5">Hello world</h1>
<div id="map"></div>
</div>
</main>
Now, let’s add some styles from the Mapbox tutorial to our css/style.css
file. We can skip the custom marker icon as we will be using the default one for now.
#map {
width: 100%;
min-height: 700px;
}
.mapboxgl-popup {
max-width: 200px;
}
And now, let’s create a new JavaScript module called js/modules/mapbox.js
where we will set up our main mapbox
function, similar to how we did with wikidata.js
.
Referencing the same Mapbox tutorial, with some minor adjustments, our script will look like this:
export default (elementId, data) => {
mapboxgl.accessToken = "YOUR_MAPBOX_TOKEN";
const map = new mapboxgl.Map({
container: elementId,
style: "mapbox://styles/mapbox/light-v11",
center: [0, 0],
zoom: 1,
});
map.on("load", () => {
map.flyTo({
center: [data[0].lon, data[1].lat],
duration: 3000,
zoom: 2,
});
data.forEach((item, index) => {
const popup = new mapboxgl.Popup({ offset: 25 }).setHTML(
`<p>
${item.label}, ${item.description}
</p>
<p>
<a href="${item.url}">Learn more</a>
</p>`
);
new mapboxgl.Marker()
.setLngLat([item.lon, item.lat])
.setPopup(popup)
.addTo(map);
});
});
};
Here’s a breakdown of what we’re doing here:
- First, we create a new Mapbox map.
- Once the map is ready, we initiate a zoom animation from the globe’s center to the location of the first item in the dataset.
- And finally, we cycle through all the data points and add them to our map.
And now we have a pretty nice prototype. There’s a few things you could do to further improve it, as homework.
- Consider the large amount of data points on the map. Perhaps these can be added dynamically? Or maybe you could update your SPARQL query to only get a certain number of them, maybe sorted by a specific quality?
- Because Mapbox adds all the popups at once, adding images would mean loading them all at the same time. Is there a better way to handle this?
- A golden rule of using APIs is to cache the results, or save them and reuse them. This can be a bit tricky without a back end. Is there another way? Maybe saving the data in the browser so each visitor only calls the API once? That might work for smaller datasets. If you go back to the Wikidata query editor, did you notice the data export option? Maybe you can work with a JSON or CSV file instead of the API?
Hope you enjoyed this walkthrough. Until next time!