/SKILL.md
Expert coding skill for building interactive web maps with MapTiler SDK JS (built on MapLibre GL JS). USE WHEN the user wants to add a map to a web application, show locations on a map, display geographic data, build a store locator, create data visualizations on maps, add geocoding or address search, show routes or GPS tracks, display 3D terrain or globe view, use satellite imagery, add markers or popups, create heatmaps or clustering, embed static map images, get elevation data, or do IP geolocation. Also USE WHEN the user mentions MapLibre GL JS — MapTiler SDK extends MapLibre with built-in cloud services, helpers, and session billing. Covers React, Vue, Svelte, Angular, Next.js, and vanilla JS. Includes MapTiler Cloud REST APIs, vector layer helpers, weather visualization, and framework-specific patterns.
npx skillsauth add maptiler/agent-skill maptiler-sdk-jsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
@maptiler/sdk · GitHub · Docs
MapTiler SDK JS extends MapLibre GL JS with opinionated defaults for MapTiler Cloud. It is 100 % compatible with vanilla MapLibre sources and layers.
Always import from @maptiler/sdk, never from maplibre-gl directly. The SDK re-exports everything from MapLibre and adds:
config.apiKey — single place for the API key; enables session-based billing (lower cost than per-request)MapStyle enum — always up-to-date map styles without hardcoded URLshelpers.* — one-liner polyline, polygon, point, heatmap, screenshot (with clustering, color ramps, GPX/KML)terrain: true — 3D terrain in one constructor flagprojection: 'globe' — globe view with halo and space atmospherenavigationControl: true, geolocateControl: true, etc.MapLibre plugins remain fully compatible — the SDK's Map inherits from MapLibre's Map.
npm install @maptiler/sdk
CSS — must be imported separately:
import '@maptiler/sdk/dist/maptiler-sdk.css';
<script src="https://cdn.maptiler.com/maptiler-sdk-js/latest/maptiler-sdk.umd.min.js"></script>
<link href="https://cdn.maptiler.com/maptiler-sdk-js/latest/maptiler-sdk.css" rel="stylesheet" />
Do NOT hardcode a fake API key. Ask the user for theirs, or instruct them to get one at https://cloud.maptiler.com/account/keys/
Framework-specific env variable names:
VITE_MAPTILER_API_KEYNEXT_PUBLIC_MAPTILER_API_KEYREACT_APP_MAPTILER_API_KEYimport * as maptilersdk from '@maptiler/sdk';
maptilersdk.config.apiKey = import.meta.env.VITE_MAPTILER_API_KEY;
const map = new maptilersdk.Map({
container: 'map', // DOM element or ID
style: maptilersdk.MapStyle.STREETS, // enum — always latest
center: [14.4178, 50.1167], // [lng, lat] — NOT [lat, lng]!
zoom: 12,
});
Critical: The container element must have explicit dimensions (e.g.,
height: 100vh), otherwise the map is invisible.
const map = new maptilersdk.Map({
container: 'map',
style: maptilersdk.MapStyle.STREETS,
center: [lng, lat],
zoom: 12,
pitch: 0, // 0-85 degrees
bearing: 0, // rotation
projection: 'mercator', // or 'globe'
terrain: false, // true = 3D terrain
terrainExaggeration: 1,
hash: true, // sync viewport with URL hash
language: maptilersdk.Language.AUTO,
cooperativeGestures: true, // Cmd+scroll to zoom
geolocate: maptilersdk.GeolocationType.POINT, // auto-center on visitor IP
// Controls (boolean or position string):
navigationControl: true,
geolocateControl: true,
scaleControl: true,
terrainControl: false,
fullscreenControl: false,
projectionControl: false,
});
| Style | Variants |
|-------|----------|
| MapStyle.STREETS | .DARK, .LIGHT, .PASTEL |
| MapStyle.SATELLITE | — |
| MapStyle.HYBRID | — |
| MapStyle.OUTDOOR | .DARK |
| MapStyle.TOPO | .SHINY, .PASTEL, .TOPOGRAPHIQUE |
| MapStyle.DATAVIZ | .DARK, .LIGHT |
| MapStyle.BASIC | .DARK, .LIGHT |
| MapStyle.OCEAN | — |
Full list with all 16 styles and variants:
references/map-styles.md
maptilersdk.config.primaryLanguage = maptilersdk.Language.ENGLISH;
map.setLanguage(maptilersdk.Language.FRENCH); // runtime change
Special: Language.AUTO (browser), Language.LOCAL, Language.VISITOR
new maptilersdk.Marker({ color: '#FF0000' })
.setLngLat([14.4178, 50.1167])
.setPopup(new maptilersdk.Popup().setHTML('<h3>Prague</h3>'))
.addTo(map);
const result = await maptilersdk.geocoding.forward('Prague', {
language: [maptilersdk.Language.ENGLISH],
limit: 5,
proximity: [14.4178, 50.1167],
});
const coords = result.features[0].geometry.coordinates; // [lng, lat]
const result = await maptilersdk.geocoding.reverse([14.4178, 50.1167]);
console.log(result.features[0].place_name);
// Polyline (also accepts GPX/KML URLs or MapTiler dataset UUIDs)
maptilersdk.helpers.addPolyline(map, {
data: routeGeoJSON,
lineColor: '#0066FF',
lineWidth: 4,
outline: true,
beforeId: 'waterway-label',
});
// Polygon
maptilersdk.helpers.addPolygon(map, {
data: zonesGeoJSON,
fillColor: '#FF0000',
fillOpacity: 0.5,
beforeId: 'waterway-label',
});
maptilersdk.helpers.addPoint(map, {
data: pointsGeoJSON,
pointColor: '#FF0000',
pointRadius: 8,
cluster: true,
showLabel: true,
beforeId: 'waterway-label',
});
maptilersdk.helpers.addHeatmap(map, {
data: pointsGeoJSON,
colorRamp: maptilersdk.ColorRamp.TURBO,
property: 'magnitude',
radius: 25,
opacity: 0.8,
});
const map = new maptilersdk.Map({
container: 'map',
style: maptilersdk.MapStyle.OUTDOOR,
terrain: true,
terrainExaggeration: 1.5,
terrainControl: true,
pitch: 60,
});
const map = new maptilersdk.Map({
container: 'map',
style: maptilersdk.MapStyle.SATELLITE,
projection: 'globe',
halo: true,
space: true,
center: [0, 20],
zoom: 1.5,
});
map.flyTo({
center: [14.4178, 50.1167],
zoom: 15,
pitch: 60,
bearing: 30,
duration: 3000,
essential: true,
});
const url = maptilersdk.staticMaps.centered(
[14.4178, 50.1167], 12,
{ hiDPI: true, width: 800, height: 600, style: maptilersdk.MapStyle.OUTDOOR }
);
Full helpers API with all options:
references/helpers-api.mdWorking HTML examples (complete, copy-paste ready):
scripts/basic-map.html,scripts/geocoding-search.html,scripts/3d-terrain.html,scripts/globe-projection.html,scripts/clustering.html,scripts/heatmap.html,scripts/interactive-layers.html,scripts/helpers-dataviz.html
import { useEffect, useRef } from 'react';
import * as maptilersdk from '@maptiler/sdk';
import '@maptiler/sdk/dist/maptiler-sdk.css';
maptilersdk.config.apiKey = import.meta.env.VITE_MAPTILER_API_KEY;
function MapComponent() {
const containerRef = useRef(null);
const mapRef = useRef(null);
useEffect(() => {
if (mapRef.current) return; // Strict Mode guard
mapRef.current = new maptilersdk.Map({
container: containerRef.current,
style: maptilersdk.MapStyle.STREETS,
center: [14.4178, 50.1167],
zoom: 12,
});
return () => { mapRef.current?.remove(); mapRef.current = null; };
}, []);
return <div ref={containerRef} style={{ width: '100%', height: '400px' }} />;
}
Next.js App Router: Add "use client"; at the top. Use NEXT_PUBLIC_MAPTILER_API_KEY. For SSR: dynamic(() => import('./Map'), { ssr: false }).
<script setup>
import { ref, onMounted, onUnmounted } from 'vue';
import * as maptilersdk from '@maptiler/sdk';
import '@maptiler/sdk/dist/maptiler-sdk.css';
maptilersdk.config.apiKey = import.meta.env.VITE_MAPTILER_API_KEY;
const container = ref(null);
let map = null;
onMounted(() => {
map = new maptilersdk.Map({
container: container.value,
style: maptilersdk.MapStyle.STREETS,
center: [14.4178, 50.1167], zoom: 12,
});
});
onUnmounted(() => { map?.remove(); map = null; });
</script>
<template><div ref="container" style="width:100%;height:400px" /></template>
Svelte, Angular, and advanced patterns:
references/frameworks.md
All APIs use config.apiKey automatically. No extra packages needed.
| Module | Purpose | Key Methods |
|--------|---------|-------------|
| geocoding | Place search | forward(), reverse(), batch() |
| geolocation | IP-based visitor location | info() → city, country, coords |
| elevation | Altitude lookup | at(), batch(), fromLineString() |
| staticMaps | Map image URLs | centered(), bounded(), automatic() |
| coordinates | CRS transform (10k+ EPSG) | search(), transform() |
| data | MapTiler Cloud datasets | get(uuid) |
| math | Geo calculations (local) | haversineDistanceWgs84() |
// IP geolocation
const loc = await maptilersdk.geolocation.info();
// Elevation
const point = await maptilersdk.elevation.at([14.4178, 50.1167]);
// Returns [lng, lat, elevationMeters]
SDK wrappers for all APIs:
references/cloud-apis.md· REST API docs: docs.maptiler.com/cloud/api
| Problem | Fix |
|---------|-----|
| Map invisible | Container needs explicit height (height: 100vh or position: absolute; inset: 0) |
| Wrong location | Coordinates are [lng, lat] not [lat, lng] |
| Layers vanish after setStyle() | Re-add in map.once('styledata', ...) |
| Data covers labels | Use beforeId: 'waterway-label' |
| Slow with many points | Enable cluster: true in source or helpers |
| Memory leaks in SPA | Always call map.remove() on unmount |
| Importing maplibre-gl separately | Use @maptiler/sdk — it includes and extends MapLibre |
All 13 gotchas + reusable patterns:
references/patterns-gotchas.md
| Package | Purpose |
|---------|---------|
| @maptiler/geocoding-control | Search bar UI for address lookup |
| @maptiler/weather | Wind, temperature, pressure, radar layers (60fps animation) |
| @maptiler/3d | glTF/GLB 3D model placement on map |
| @maptiler/elevation-profile | Elevation profile chart for routes |
| @maptiler/marker-layout | Non-colliding DOM marker overlays |
| @maptiler/ar | Augmented reality 3D terrain view |
| @maptiler/client | Headless API client (Node.js / browser, no map needed) |
Detailed usage for each module:
references/ecosystem.md
references/helpers-api.md — Complete helpers API with option tables and typesreferences/patterns-gotchas.md — 13 gotchas + 13 reusable code patternsreferences/map-styles.md — All MapStyle variants and Language enum valuesreferences/events.md — Lifecycle, camera, interaction, data eventsreferences/cloud-apis.md — MapTiler Cloud REST API endpointsreferences/frameworks.md — React, Vue, Svelte, Angular, Next.js patternsreferences/ecosystem.md — Weather, 3D, AR, elevation-profile, geocoding-control modulestools
Use when work should span one or more detached tasks but still behave like one job with a single owner context. TaskFlow is the durable flow substrate under authoring layers like Lobster, ACPX, plugins, or plain code. Keep conditional logic in the caller; use TaskFlow for flow identity, child-task linkage, waiting state, revision-checked mutations, and user-facing emergence.
tools
# Lobster Lobster executes multi-step workflows with approval checkpoints. Use it when: - User wants a repeatable automation (triage, monitor, sync) - Actions need human approval before executing (send, post, delete) - Multiple tool calls should run as one deterministic operation ## When to use Lobster | User intent | Use Lobster? | | ------------------------------------------------------ | --------------------------
tools
# Lobster Lobster executes multi-step workflows with approval checkpoints. Use it when: - User wants a repeatable automation (triage, monitor, sync) - Actions need human approval before executing (send, post, delete) - Multiple tool calls should run as one deterministic operation ## When to use Lobster | User intent | Use Lobster? | | ------------------------------------------------------ | --------------------------
tools
A CLI tool for making authenticated requests to the X (Twitter) API. Use this skill when you need to post tweets, reply, quote, search, read posts, manage followers, send DMs, upload media, or interact with any X API v2 endpoint.