Vanilla JavaScript
This guide covers using maplibre-yaml in vanilla JavaScript projects - no frameworks, no build tools required.
You have two options:
- Web Component (
<ml-map>) - Zero JavaScript, just HTML (recommended for simplicity) - JavaScript API (
YAMLParser+MapRenderer) - More control and customization
Quick Start: Web Component (Recommended)
Section titled “Quick Start: Web Component (Recommended)”The fastest way to get started - no JavaScript code needed!
-
Create an HTML file
<!DOCTYPE html><html><head><title>My Map</title><linkhref="https://unpkg.com/maplibre-gl/dist/maplibre-gl.css"rel="stylesheet"/><script type="module">import "https://unpkg.com/@maplibre-yaml/core/register";</script><style>body {margin: 0;}ml-map {display: block;width: 100vw;height: 100vh;}</style></head><body><ml-map src="map.yaml"></ml-map></body></html> -
Create a YAML configuration file
Create
map.yaml:type: mapid: my-first-mapconfig:center: [-74.006, 40.7128]zoom: 12mapStyle: "https://demotiles.maplibre.org/style.json"layers:- id: pointstype: circlesource:type: geojsondata:type: FeatureCollectionfeatures:- type: Featuregeometry:type: Pointcoordinates: [-74.006, 40.7128]properties:name: "New York City"paint:circle-radius: 10circle-color: "#3b82f6" -
Serve and view
Terminal window npx serve .
Quick Start: JavaScript API (More Control)
Section titled “Quick Start: JavaScript API (More Control)”For cases where you need programmatic control or want to use the lower-level APIs:
-
Create an HTML file
<!DOCTYPE html><html><head><title>My Map</title><linkhref="https://unpkg.com/maplibre-gl/dist/maplibre-gl.css"rel="stylesheet"/><style>body {margin: 0;}#map {width: 100vw;height: 100vh;}</style></head><body><div id="map"></div><script type="module" src="map.js"></script></body></html> -
Create a YAML configuration file
Create
map.yaml(same as above) -
Create your JavaScript file
Create
map.js:import { YAMLParser, MapRenderer } from "https://unpkg.com/@maplibre-yaml/core";// Fetch and parse YAML configurationconst response = await fetch("map.yaml");const yamlText = await response.text();// Parse and validateconst result = YAMLParser.safeParseMapBlock(yamlText);if (!result.success) {console.error("Configuration errors:", result.errors);document.getElementById("map").innerHTML = `<div style="color: red; padding: 20px;"><h3>Configuration Error</h3><pre>${JSON.stringify(result.errors, null, 2)}</pre></div>`;} else {// Render the mapconst renderer = new MapRenderer(document.getElementById("map"), result.data);renderer.on("load", () => {console.log("Map loaded successfully!");});} -
Serve and view
Terminal window npx serve .
Which Approach Should I Use?
Section titled “Which Approach Should I Use?”Use the Web Component (<ml-map>) if:
- ✅ You want the simplest possible integration
- ✅ You don’t need programmatic control
- ✅ You’re embedding maps in static pages
- ✅ You want zero JavaScript code
Use the JavaScript API (YAMLParser + MapRenderer) if:
- ✅ You need to programmatically modify the map
- ✅ You want to build custom UI controls
- ✅ You need access to the MapLibre GL instance
- ✅ You’re building a complex interactive application
Installation Options
Section titled “Installation Options”CDN (No Build Tools)
Section titled “CDN (No Build Tools)”For quick prototypes or simple projects:
<script type="module"> import { YAMLParser, MapRenderer, } from "https://unpkg.com/@maplibre-yaml/core"; // Your code here</script>npm (With Build Tools)
Section titled “npm (With Build Tools)”For production projects with bundlers:
npm install @maplibre-yaml/core maplibre-glpnpm add @maplibre-yaml/core maplibre-glyarn add @maplibre-yaml/core maplibre-glimport { YAMLParser, MapRenderer } from '@maplibre-yaml/core';import 'maplibre-gl/dist/maplibre-gl.css';Loading YAML Configuration
Section titled “Loading YAML Configuration”Recommended: External YAML Files
Section titled “Recommended: External YAML Files”External files are the recommended approach for most projects:
// Load from fileconst response = await fetch("/configs/map.yaml");const yamlText = await response.text();const result = YAMLParser.safeParseMapBlock(yamlText);Benefits:
- Clean separation of configuration and code
- Easy to edit without touching JavaScript
- Proper YAML indentation preserved
- Can be validated independently
- Cacheable by browsers
Alternative: Inline YAML Strings
Section titled “Alternative: Inline YAML Strings”For simple cases where you want everything in one file:
// ⚠️ Important: Start content immediately after backtick// No leading newline!const yamlConfig = `type: mapid: inline-mapconfig: center: [-74.006, 40.7128] zoom: 12 mapStyle: "https://demotiles.maplibre.org/style.json"layers: []`;
const result = YAMLParser.safeParseMapBlock(yamlConfig);Core APIs
Section titled “Core APIs”YAMLParser
Section titled “YAMLParser”The parser validates YAML against the maplibre-yaml schema.
import { YAMLParser } from "@maplibre-yaml/core";
// Safe parsing (recommended) - returns result objectconst result = YAMLParser.safeParseMapBlock(yamlText);
if (result.success) { console.log("Valid config:", result.data);} else { console.error("Errors:", result.errors); // errors: [{ path: 'config.center', message: 'Expected array' }, ...]}
// Direct parsing - throws on errortry { const config = YAMLParser.parseMapBlock(yamlText);} catch (error) { console.error("Parse error:", error.message);}MapRenderer
Section titled “MapRenderer”The renderer creates and manages the MapLibre GL map.
import { MapRenderer } from "@maplibre-yaml/core";
const renderer = new MapRenderer(container, config);
// Eventsrenderer.on("load", () => { /* Map ready */});renderer.on("error", ({ error }) => { /* Handle error */});renderer.on("layer:click", ({ layerId, feature }) => { /* Handle click */});
// Methodsrenderer.destroy(); // Clean up resourcesFull Event List
Section titled “Full Event List”// Map lifecyclerenderer.on("load", () => {});renderer.on("error", ({ error }) => {});
// Layer eventsrenderer.on("layer:loading", ({ layerId }) => {});renderer.on("layer:loaded", ({ layerId, featureCount }) => {});renderer.on("layer:error", ({ layerId, error }) => {});
// Interaction eventsrenderer.on("layer:click", ({ layerId, feature, coordinates }) => {});renderer.on("layer:hover", ({ layerId, feature }) => {});
// Data events (for live data)renderer.on("layer:data-update", ({ layerId, featureCount }) => {});Working with Live Data
Section titled “Working with Live Data”Polling (Auto-refresh)
Section titled “Polling (Auto-refresh)”type: mapid: live-mapconfig: center: [-120, 37] zoom: 5 mapStyle: "https://demotiles.maplibre.org/style.json"layers: - id: earthquakes type: circle source: type: geojson url: "https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/all_hour.geojson" refreshInterval: 60000 # Refresh every 60 seconds updateStrategy: replace paint: circle-radius: 8 circle-color: "#ef4444"// Control polling programmaticallyrenderer.pauseRefresh("earthquakes");renderer.resumeRefresh("earthquakes");await renderer.refreshNow("earthquakes");Handling Data Updates
Section titled “Handling Data Updates”renderer.on("layer:loading", ({ layerId }) => { showSpinner(layerId);});
renderer.on("layer:loaded", ({ layerId, featureCount }) => { hideSpinner(layerId); updateStatus(`Loaded ${featureCount} features`);});
renderer.on("layer:error", ({ layerId, error }) => { hideSpinner(layerId); showError(`Failed to load ${layerId}: ${error.message}`);});Accessing the MapLibre Instance
Section titled “Accessing the MapLibre Instance”For advanced use cases, access the underlying MapLibre GL map:
const renderer = new MapRenderer(container, config);
renderer.on("load", () => { // Access the MapLibre GL map instance const map = renderer.getMap();
// Use any MapLibre GL API map.flyTo({ center: [-122.4, 37.8], zoom: 14 });
// Add custom controls map.addControl(new maplibregl.NavigationControl());
// Add custom event handlers map.on("moveend", () => { console.log("Map moved to:", map.getCenter()); });});Complete Example: Interactive Dashboard
Section titled “Complete Example: Interactive Dashboard”Here’s a complete example showing a map with live data and UI controls:
index.html:
<!DOCTYPE html><html> <head> <title>Earthquake Dashboard</title> <link href="https://unpkg.com/maplibre-gl/dist/maplibre-gl.css" rel="stylesheet" /> <style> body { margin: 0; font-family: system-ui, sans-serif; } #map { width: 100vw; height: 100vh; } .controls { position: absolute; top: 10px; left: 10px; background: white; padding: 15px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); z-index: 1; } .status { margin-top: 10px; font-size: 14px; color: #666; } button { padding: 8px 16px; margin-right: 8px; border: 1px solid #ddd; border-radius: 4px; background: white; cursor: pointer; } button:hover { background: #f5f5f5; } button.active { background: #3b82f6; color: white; border-color: #3b82f6; } </style> </head> <body> <div id="map"></div> <div class="controls"> <button id="pause">Pause Updates</button> <button id="refresh">Refresh Now</button> <div class="status" id="status">Loading...</div> </div> <script type="module" src="dashboard.js"></script> </body></html>earthquakes.yaml:
type: mapid: earthquake-dashboardconfig: center: [-120, 40] zoom: 4 mapStyle: "https://demotiles.maplibre.org/style.json"layers: - id: earthquakes type: circle source: type: geojson url: "https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/all_day.geojson" refreshInterval: 60000 updateStrategy: replace paint: circle-radius: - interpolate - ["linear"] - ["get", "mag"] - 0 - 4 - 5 - 20 circle-color: - interpolate - ["linear"] - ["get", "mag"] - 0 - "#22c55e" - 3 - "#eab308" - 5 - "#ef4444" circle-opacity: 0.8 interactive: hover: cursor: pointer click: popup: - h3: - property: title - p: - str: "Magnitude: " - property: magdashboard.js:
import { YAMLParser, MapRenderer } from "https://unpkg.com/@maplibre-yaml/core";
// Statelet isPaused = false;let renderer = null;
// DOM elementsconst statusEl = document.getElementById("status");const pauseBtn = document.getElementById("pause");const refreshBtn = document.getElementById("refresh");
// Load and initializeasync function init() { try { // Load YAML configuration const response = await fetch("earthquakes.yaml"); const yamlText = await response.text();
// Parse and validate const result = YAMLParser.safeParseMapBlock(yamlText);
if (!result.success) { statusEl.textContent = `Config error: ${result.errors[0].message}`; statusEl.style.color = "#ef4444"; return; }
// Create renderer renderer = new MapRenderer(document.getElementById("map"), result.data);
// Set up event handlers renderer.on("load", () => { statusEl.textContent = "Map ready, loading data..."; });
renderer.on("layer:loading", ({ layerId }) => { statusEl.textContent = `Loading ${layerId}...`; });
renderer.on("layer:loaded", ({ layerId, featureCount }) => { const time = new Date().toLocaleTimeString(); statusEl.textContent = `${featureCount} earthquakes · Updated ${time}`; statusEl.style.color = "#666"; });
renderer.on("layer:error", ({ layerId, error }) => { statusEl.textContent = `Error: ${error.message}`; statusEl.style.color = "#ef4444"; }); } catch (error) { statusEl.textContent = `Failed to initialize: ${error.message}`; statusEl.style.color = "#ef4444"; }}
// Button handlerspauseBtn.addEventListener("click", () => { if (!renderer) return;
isPaused = !isPaused;
if (isPaused) { renderer.pauseRefresh("earthquakes"); pauseBtn.textContent = "Resume Updates"; pauseBtn.classList.add("active"); } else { renderer.resumeRefresh("earthquakes"); pauseBtn.textContent = "Pause Updates"; pauseBtn.classList.remove("active"); }});
refreshBtn.addEventListener("click", async () => { if (!renderer) return;
refreshBtn.disabled = true; refreshBtn.textContent = "Refreshing...";
await renderer.refreshNow("earthquakes");
refreshBtn.disabled = false; refreshBtn.textContent = "Refresh Now";});
// Initializeinit();Troubleshooting
Section titled “Troubleshooting”Map Shows But No Data Appears
Section titled “Map Shows But No Data Appears”Symptoms: Base map renders, but layers with URL data don’t show any features.
Common Causes:
-
CORS blocking the request
Check the browser console for CORS errors. The data URL must allow cross-origin requests.
// Check network tab - is the request being made?renderer.on("layer:error", ({ layerId, error }) => { console.error(`${layerId} failed:`, error);});-
URL returns invalid GeoJSON
Test the URL directly in your browser to verify it returns valid GeoJSON.
-
Data loaded but features have no geometry
renderer.on("layer:loaded", ({ layerId, featureCount }) => { console.log(`${layerId}: ${featureCount} features`); // If featureCount is 0, the GeoJSON may be empty or malformed});YAML Parsing Errors
Section titled “YAML Parsing Errors”Symptoms: safeParseMapBlock returns success: false with errors.
Common Causes:
-
Indentation issues
YAML requires consistent indentation (2 spaces recommended):
# ✅ Correct config: center: [-74, 40] zoom: 10
# ❌ Wrong - inconsistent indentation config: center: [-74, 40] zoom: 10- Missing required fields
# ❌ Missing required fields type: map layers: []
# ✅ Correct - has all required fields type: map id: my-map config: center: [0, 0] zoom: 2 mapStyle: "https://..." layers: []- Invalid values
# ❌ center must be [longitude, latitude] config: center: "New York"
# ✅ Correct config: center: [-74.006, 40.7128]Template Literal YAML Issues
Section titled “Template Literal YAML Issues”Symptoms: YAML works in a file but fails when inline in JavaScript.
Cause: JavaScript template literals can mangle whitespace.
// ❌ WRONG - Leading newline causes issuesconst yaml = `type: mapconfig: center: [-74, 40]`;
// ✅ CORRECT - Start immediately after backtickconst yaml = `type: mapid: my-mapconfig: center: [-74, 40] zoom: 10 mapStyle: "https://demotiles.maplibre.org/style.json"layers: []`;Best Practice: Use external YAML files whenever possible.
Map Container Has Zero Size
Section titled “Map Container Has Zero Size”Symptoms: Map doesn’t render, or renders as a tiny sliver.
Cause: The container element needs explicit dimensions.
/* ❌ Container has no size */#map {}
/* ✅ Explicit dimensions */#map { width: 100%; height: 400px;}
/* ✅ Or fill viewport */#map { width: 100vw; height: 100vh;}Events Not Firing
Section titled “Events Not Firing”Symptoms: renderer.on('load', ...) callback never runs.
Common Causes:
- Event registered after map loaded
// ❌ Map might already be loadedconst renderer = new MapRenderer(container, config);setTimeout(() => { renderer.on("load", () => {}); // Might miss the event}, 1000);
// ✅ Register immediatelyconst renderer = new MapRenderer(container, config);renderer.on("load", () => {});- Configuration error prevented map creation
renderer.on("error", ({ error }) => { console.error("Map failed to initialize:", error);});Next Steps
Section titled “Next Steps”- Web Components Integration - Even simpler HTML-only approach
- Astro Integration - For Astro-based sites
- Live Data Guide - Deep dive into polling and streaming
- API Reference - Complete API documentation