<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Mapbox</title>
    <description>The latest articles on Forem by Mapbox (@mapbox).</description>
    <link>https://forem.com/mapbox</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F230%2F827ddd41-6aca-479c-967d-6f8d16fed8a3.png</url>
      <title>Forem: Mapbox</title>
      <link>https://forem.com/mapbox</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/mapbox"/>
    <language>en</language>
    <item>
      <title>Demystifying Mapbox Styles</title>
      <dc:creator>Chris Whong</dc:creator>
      <pubDate>Tue, 05 May 2026 17:32:07 +0000</pubDate>
      <link>https://forem.com/mapbox/demystifying-mapbox-styles-2igc</link>
      <guid>https://forem.com/mapbox/demystifying-mapbox-styles-2igc</guid>
      <description>&lt;p&gt;If you've spent any time building digital maps with Mapbox, you've used a style — but you may not have given it much thought. Most tutorials and example code abstract it down to a single URL, and the map &lt;em&gt;just works&lt;/em&gt;. That's by design.&lt;/p&gt;

&lt;p&gt;But styles are doing a lot more than most developers realize, and understanding how they actually work opens up patterns that are useful whether you're just getting started or have been shipping Mapbox maps for years.&lt;/p&gt;

&lt;p&gt;This post pulls back the curtain: what a style really is, how it gets from a server to your screen, and a few tips and tricks for working with styles more intentionally.&lt;/p&gt;

&lt;h2&gt;
  
  
  What even is a style?
&lt;/h2&gt;

&lt;p&gt;When you load a map in a web or mobile app it is a &lt;code&gt;style&lt;/code&gt; that tells the map what to draw and how to draw it. The map rendering library (&lt;a href="https://docs.mapbox.com/mapbox-gl-js/guides/" rel="noopener noreferrer"&gt;Mapbox GL JS&lt;/a&gt;, or the Maps SDK for &lt;a href="https://docs.mapbox.com/ios/maps/guides/" rel="noopener noreferrer"&gt;iOS&lt;/a&gt;/&lt;a href="https://docs.mapbox.com/android/maps/guides/" rel="noopener noreferrer"&gt;Android&lt;/a&gt;/&lt;a href="https://docs.mapbox.com/flutter/maps/guides/" rel="noopener noreferrer"&gt;Flutter&lt;/a&gt;) loads and parses the style, then fetches whatever data is needed and renders it to the map canvas using GL technology. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F666y39qjncc37cftxkk3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F666y39qjncc37cftxkk3.png" alt=" " width="800" height="687"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://docs.mapbox.com/map-styles/standard/guides/" rel="noopener noreferrer"&gt;Mapbox Standard Style&lt;/a&gt; is the default style for all renderers, but works in a slightly different way than our Classic Styles, but we'll get to that later. First, let's talk about how styles have worked historically. &lt;/p&gt;

&lt;p&gt;In the earlier days of Mapbox maps, most developers would start with one of Mapbox's &lt;a href="https://docs.mapbox.com/api/maps/styles/#classic-mapbox-styles" rel="noopener noreferrer"&gt;Classic Styles&lt;/a&gt;, a URL that looks like &lt;code&gt;mapbox://styles/mapbox/streets-v12&lt;/code&gt;. See all of the &lt;a href="https://docs.mapbox.com/api/maps/styles/#classic-mapbox-styles" rel="noopener noreferrer"&gt;Slassic Styles here&lt;/a&gt;. These styles were professionally designed by our cartographers, and purpose built for different use cases. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Streets&lt;/strong&gt; is a great all-purpose style, with a wide variety of colors and details. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Outdoors&lt;/strong&gt; is designed for hiking and outdoor activities, with terrain shading, contour lines, and vibrant colors. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Light&lt;/strong&gt; and &lt;strong&gt;Dark&lt;/strong&gt; are designed to be more subdued, ideal for maps where the overlaid data should be the star of the show, and the basemap should recede into the background. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With a &lt;code&gt;mapbox://&lt;/code&gt; URL to a classic style, a center point, and a zoom level, you can summon an amazingly detailed, fluidly scrolling map with five lines of code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;mapboxgl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mapbox://styles/mapbox/streets-v12&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;center&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;103.64548&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;51.14245&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;zoom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1.78&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/chris-whong/embed/pvNzPgO?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;What you may not know is that &lt;code&gt;mapbox://styles/mapbox/streets-v12&lt;/code&gt; is shorthand for a call to the &lt;a href="https://docs.mapbox.com/api/maps/styles/" rel="noopener noreferrer"&gt;Mapbox Styles API&lt;/a&gt;, whose sole purpose in life is (you guessed it) storing and serving up map styles! The actual API call is to &lt;code&gt;https://api.mapbox.com/styles/v1/mapbox/streets-v12&lt;/code&gt;, which returns the style as JSON:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Mapbox Streets"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sprite"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"mapbox://sprites/mapbox/streets-v12"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"glyphs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"mapbox://fonts/mapbox/{fontstack}/{range}.pbf"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"center"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="mf"&gt;-92.25&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="mf"&gt;37.75&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"zoom"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"fog"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"projection"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"globe"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"visibility"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"public"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"layers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sources"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"composite"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"mapbox://mapbox.mapbox-streets-v8,mapbox.mapbox-terrain-v2,mapbox.mapbox-bathymetry-v2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"vector"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;  
  &lt;/span&gt;&lt;span class="nl"&gt;"created"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1970-01-01T00:00:00.000Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"modified"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1970-01-01T00:00:00.000Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"owner"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"mapbox"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"streets-v12"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"draft"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;em&gt;actual&lt;/em&gt; JSON is about 13,000 lines long when pretty-printed, and 99% of it is in the &lt;code&gt;layers&lt;/code&gt; array. That's really where the action is, where every detail shown on the map is defined, often using complex &lt;a href="https://docs.mapbox.com/style-spec/reference/expressions/" rel="noopener noreferrer"&gt;expressions&lt;/a&gt; to handle zoom-dependent styling, multi-language support, etc. Here's just one, of no particular importance, so you can see what we're working with in the &lt;code&gt;layers&lt;/code&gt; array. &lt;code&gt;road-minor&lt;/code&gt; handles rendering of minor roads. It's a &lt;code&gt;line&lt;/code&gt; layer (other types include &lt;code&gt;fill&lt;/code&gt;, &lt;code&gt;circle&lt;/code&gt;, &lt;code&gt;symbol&lt;/code&gt;, etc) and pulls data from the &lt;code&gt;road&lt;/code&gt; source layer.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Identifies this layer within the style.&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;road-minor&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

  &lt;span class="c1"&gt;// Tells the renderer to draw the features as lines.&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;line&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

  &lt;span class="c1"&gt;// Names the source dataset this layer reads from.&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;source&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;composite&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

  &lt;span class="c1"&gt;// Chooses the specific source layer inside the vector tileset.&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;source-layer&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;road&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

  &lt;span class="c1"&gt;// Prevents the layer from appearing before zoom level 13.&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;minzoom&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;13&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

  &lt;span class="c1"&gt;// Limits which road features are included in this layer.&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;filter&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;all&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;match&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;get&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;class&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;track&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;service&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;step&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;zoom&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;match&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;get&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;structure&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;none&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ford&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;==&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;geometry-type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;LineString&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;

  &lt;span class="c1"&gt;// Controls how line ends and corners are shaped.&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;layout&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;line-cap&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;step&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;zoom&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;butt&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;round&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;line-join&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;step&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;zoom&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;miter&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;round&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;

  &lt;span class="c1"&gt;// Defines the visible styling, including width and color.&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;paint&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;line-width&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;interpolate&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;exponential&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1.5&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;zoom&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;22&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;line-color&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hsl(0, 0%, 100%)&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;

  &lt;span class="c1"&gt;// Stores internal grouping and categorization metadata.&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;metadata&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mapbox:featureComponent&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;road-network&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mapbox:group&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Road network, surface&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All that just for one layer? Yes, and this is one of the simpler ones! &lt;/p&gt;

&lt;p&gt;You'll also notice that in the JSON for this style, there are even more magic &lt;code&gt;mapbox://&lt;/code&gt; URLs for the &lt;code&gt;sprite&lt;/code&gt; and &lt;code&gt;glyphs&lt;/code&gt;, and the &lt;code&gt;sources&lt;/code&gt;. &lt;a href="https://docs.mapbox.com/mapbox-gl-js/style-spec/sprite/" rel="noopener noreferrer"&gt;&lt;code&gt;sprite&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://docs.mapbox.com/mapbox-gl-js/style-spec/glyphs/" rel="noopener noreferrer"&gt;&lt;code&gt;glyphs&lt;/code&gt;&lt;/a&gt; are similar shorthand URLs for the styles API endpoints that serve additional resources needed to render the style. &lt;/p&gt;

&lt;p&gt;&lt;code&gt;sprite&lt;/code&gt; gets back an composite image full of all those little icons you see on the map for restaurants, hotels, parks, attractions, etc. &lt;/p&gt;

&lt;p&gt;&lt;code&gt;glyphs&lt;/code&gt; gets back font files that are used to render all the labels. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.mapbox.com/mapbox-gl-js/style-spec/sources/" rel="noopener noreferrer"&gt;&lt;code&gt;sources&lt;/code&gt;&lt;/a&gt; includes shorthand URLs for a different API, the &lt;a href="https://docs.mapbox.com/api/maps/vector-tiles/" rel="noopener noreferrer"&gt;Mapbox Vector Tiles API&lt;/a&gt;. These &lt;a href="https://docs.mapbox.com/data/tilesets/guides/vector-tiles-introduction/" rel="noopener noreferrer"&gt;vector tilesets&lt;/a&gt; contain the actual data shown on the map, served over the network as small chunks of unstyled data representing a quadrangle of the earth's surface. &lt;a href="https://docs.mapbox.com/data/tilesets/reference/mapbox-streets-v8/" rel="noopener noreferrer"&gt;&lt;code&gt;mapbox-streets-v8&lt;/code&gt;&lt;/a&gt; does the heavy lifting and contains data for most of what you'll see on any Mapbox style (streets, place names, parks/land use, buildings, etc). The image below contains a simplified rendering of the rich data included in the streets-v8 tileset, including streets, place labels, points of interest, building footprints, etc.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6v9x09hbzt82awkehrua.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6v9x09hbzt82awkehrua.png" alt=" " width="800" height="523"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;All this complexity gets you to a pretty great starting point, a well-designed "basemap" of the whole planet, at every zoom level from the full globe down to streets and intersections. Many developers never need to edit layers in the basemap, they take it as-is and concern themselves only with styling their own data they add to the map. Others may want complete control, changing colors to match a brand, removing or modifying whole sets of map labels, or changing the width of streets.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Style Specification
&lt;/h3&gt;

&lt;p&gt;The &lt;a href="https://docs.mapbox.com/style-spec/" rel="noopener noreferrer"&gt;Mapbox Style Specification&lt;/a&gt; is the formal definition of what a style is, and how it should be structured. It lives at &lt;a href="https://docs.mapbox.com/style-spec/" rel="noopener noreferrer"&gt;docs.mapbox.com/style-spec&lt;/a&gt; and defines the required properties, valid values for each property, and the overall structure of the style JSON. If you want to understand how styles work under the hood, or if you want to create your own custom styles from scratch, the style spec is the place to start. It is also a great reference for understanding the different properties and options available when working with styles, and can help you troubleshoot issues when your style isn't working as expected. &lt;/p&gt;

&lt;p&gt;Every possible style property is outlined in the &lt;a href="https://docs.mapbox.com/style-spec" rel="noopener noreferrer"&gt;style spec&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjcl16cz9wc2irokougdp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjcl16cz9wc2irokougdp.png" alt=" " width="800" height="528"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you are maintaining complex styles on your own, there are &lt;a href="https://github.com/mapbox/mapbox-gl-js/blob/main/src/style-spec/README.md" rel="noopener noreferrer"&gt;various utilities&lt;/a&gt; available to help you validate your style JSON against the specification, and to catch errors before they cause issues in your apps. &lt;/p&gt;

&lt;h2&gt;
  
  
  Other ways to get a style JSON into your map
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;mapbox://&lt;/code&gt; URLs are the most common way to get a style into your map, but they are not the only way.&lt;/p&gt;

&lt;p&gt;As mentioned earlier, the style is just JSON, and that JSON can come from anywhere. For example, if I were to strip down the complex style JSON above, and include only a single layer for buildings, the style might look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"layers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"building"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"fill"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"streets-v8"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"source-layer"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"building"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"minzoom"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"filter"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="s2"&gt;"all"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"!="&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"get"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"building:part"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"=="&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"get"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"underground"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"false"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"layout"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"paint"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"fill-color"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"#ccc"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"sources"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"streets-v8"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"mapbox://mapbox.mapbox-streets-v8"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"vector"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We have also removed optional properties: &lt;code&gt;center&lt;/code&gt;, &lt;code&gt;zoom&lt;/code&gt;, &lt;code&gt;fog&lt;/code&gt;, &lt;code&gt;projection&lt;/code&gt;, &lt;code&gt;visibility&lt;/code&gt;, &lt;code&gt;created&lt;/code&gt;, &lt;code&gt;modified&lt;/code&gt;, &lt;code&gt;owner&lt;/code&gt;, &lt;code&gt;id&lt;/code&gt;, and &lt;code&gt;draft&lt;/code&gt;. Bear in mind that the above is the &lt;em&gt;bare minimum&lt;/em&gt; that the renderer needs to display something from the streets-v8 tileset. So how do we get this style into the map?&lt;/p&gt;

&lt;h3&gt;
  
  
  Load JSON from your codebase
&lt;/h3&gt;

&lt;p&gt;You can load style JSON from a file in your codebase, or as a JSON string defined in your source code. This codepen shows the minimal style JSON above, implemented as a JSON object in the JavaScript code just before map instantiation:&lt;/p&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/chris-whong/embed/myObmBw?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h4&gt;
  
  
  Web
&lt;/h4&gt;

&lt;p&gt;In a web project, this style could be saved as &lt;code&gt;style.json&lt;/code&gt; and imported as a JSON object using a bundler:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../data/style.json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; 

&lt;span class="p"&gt;...&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;map&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;mapboxgl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;center&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;72&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;41&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;zoom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;15.5&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It can also be defined as an object in the JavaScript:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;version&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;layers&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sources&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="p"&gt;...&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;map&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;mapboxgl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;center&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;72&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;41&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;zoom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;15.5&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Mobile Applications (iOS, Android, Flutter)
&lt;/h4&gt;

&lt;p&gt;For mobile applications, there's no equivalent to loading the style JSON from an object stored in a variable, so you'll be working with the JSON as a string. You can load the JSON from an asset file in your codebase, or define it as a string directly in your code.&lt;/p&gt;

&lt;p&gt;For example, in iOS you could load the JSON from a file in your app bundle:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;styleURL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;Bundle&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;forResource&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"style"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;withExtension&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"json"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;map&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;MapView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bounds&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;styleURL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;styleURL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addSubview&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or in Flutter you could define the string directly in your code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;style&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;'''{
  "version": 8,
  "layers": [ ... ],
  "sources": { ... }         
}'''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;map&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;MapboxMap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nl"&gt;styleString:&lt;/span&gt; &lt;span class="n"&gt;style&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;initialCameraPosition:&lt;/span&gt; &lt;span class="n"&gt;CameraPosition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nl"&gt;target:&lt;/span&gt; &lt;span class="n"&gt;LatLng&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;41&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;72&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nl"&gt;zoom:&lt;/span&gt; &lt;span class="mf"&gt;15.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Host a JSON file somewhere (anywhere!)
&lt;/h3&gt;

&lt;p&gt;You can add any URL to the &lt;code&gt;style&lt;/code&gt; option in Mapbox GL JS. This means you can host your style JSON on your own server, an S3 bucket, github raw link, or any other web-accessible location. The only requirement is that the style be accessible at the time of map instantiation, and that it be valid style JSON:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;map&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;mapboxgl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://example.com/my-style.json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;center&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;72&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;41&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;zoom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;15.5&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On mobile, loading a style from a URL works the same way, just make sure the URL is accessible from the device:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;styleURL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;string&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"https://example.com/my-style.json"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;map&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;MapView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bounds&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;styleURL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;styleURL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addSubview&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  When would I ever need to do this?
&lt;/h3&gt;

&lt;p&gt;Loading a style JSON from your codebase can be useful in development, where you want to move quickly and just get something on the screen without worrying about hosting and network requests. It also allows you to keep your style JSON in version control alongside your app's code, which can be helpful for tracking changes and collaborating with other developers.&lt;/p&gt;

&lt;p&gt;There are also plenty of use cases where a full-on basemap isn't needed, such as indoor maps, transit maps, event seating charts, or any map where the data is the star of the show and a real-world basemap would just be visual noise. In these cases, you can maintain a simple style JSON with just your custom sources and layers, and load it directly into the map without having to worry about the complexity of a full basemap style.&lt;/p&gt;

&lt;p&gt;For a full style with hundreds of layers and thousands of lines of JSON, it's often less practical to maintain it in your codebase, but some teams may prefer this so they can keep styles in the same codebase as their app, be able to explore changes in version control, and have full control over how and when styles are updated and deployed. &lt;/p&gt;

&lt;p&gt;However, modern changes to the style spec allow for importing one style into another, which makes maintaining your own top-level style JSON much more manageable. See the &lt;strong&gt;Enter the Standard Style (and style "imports")&lt;/strong&gt; section below.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementation patterns for using Mapbox-hosted Classic Styles
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1) Load a Mapbox classic style (mapbox://styles/mapbox/streets-v12) and add your data at runtime
&lt;/h3&gt;

&lt;p&gt;This approach treats the initial map load and its style as the basemap and adds additional sources and layers after the map has loaded. This approach lends itself well to using GeoJSON sources and layers when you have small datasets that you want to add to a style.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;map&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;mapboxgl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mapbox://styles/mapbox/streets-v12&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;center&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;72&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;41&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;zoom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;15.5&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;load&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addSource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;my-data&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;geojson&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://example.com/my-data.geojson&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addLayer&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;my-data-layer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;circle&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;my-data&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;paint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;circle-radius&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;circle-color&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#f00&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2) Make a custom style based on (e.g. cloned from) a Mapbox-hosted style
&lt;/h3&gt;

&lt;p&gt;With this approach, you start with a clone of a Mapbox style and add your data in the Mapbox Style Editor. Once your style is published, you can use it in your frontend maps using its &lt;code&gt;mapbox://&lt;/code&gt; URL. Behind the scenes there is still a style JSON with thousands of lines, but you the developer never have to interact with it directly, and can make all changes through the UI of the Style Editor.&lt;/p&gt;

&lt;p&gt;This approach is more common for developers who have custom tilesets hosted on Mapbox, either created using Mapbox Tiling Service or via the Data Workbench. Adding a custom tileset as a &lt;code&gt;vector&lt;/code&gt; source at runtime is still possible, but it's more straightforward to add the tileset to a custom style and use the Style Editor to get it looking the way you want. &lt;/p&gt;

&lt;p&gt;This image from the Style Editor shows the &lt;strong&gt;Dark&lt;/strong&gt; classic style, with full control over each of its 100+ layers. Developers get full control, but manual updates are required if the underlying data changes or new features are released.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fql2h28kxpzadv0p10evs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fql2h28kxpzadv0p10evs.png" alt=" " width="800" height="514"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This approach is also ideal if you want the same map on multiple platforms, as you can maintain one style in the Style Editor and load it on web, iOS, Android, and/or Flutter with the same &lt;code&gt;mapbox://&lt;/code&gt; URL. No additional code is necessary to add your custom sources and layers, as they are already baked into the custom style.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The Style Editor used to allow for the creation of new styles based on the Classic Styles (Streets, Light, Dark, etc.), but this option has been removed. You can still use this approach by adding any of the styles in the &lt;a href="https://www.mapbox.com/gallery" rel="noopener noreferrer"&gt;Mapbox Gallery's&lt;/a&gt; &lt;strong&gt;Community Templates&lt;/strong&gt; tab. Copying a style from the gallery clones it, and your new style is a carbon copy of the original template. Any changes you make are yours to manage.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;map&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;mapboxgl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mapbox://styles/your-username/your-style-id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;center&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;72&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;41&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;zoom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;15.5&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The "Maintenance Tax" of Classic Styles
&lt;/h3&gt;

&lt;p&gt;The classic style paradigm has been around since the earliest days of GL-based mapping at Mapbox, and it remains a powerful and flexible approach — but that flexibility comes with a cost: &lt;strong&gt;you own the style JSON, which means you own the maintenance burden.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When you fork a classic style, you're taking a snapshot frozen in time. Mapbox continuously improves both the renderer and the style spec together — new features like HD Roads or Indoor Maps aren't just style layer changes, they're coordinated updates across the rendering engine, the style specification, and the style JSON simultaneously. Classic style users who want these features have to manually integrate those style-side changes without breaking their own customizations. &lt;/p&gt;

&lt;p&gt;The more custom styles you maintain, and the more heavily you've modified them, the steeper this tax becomes. What's the solution? Read on to learn about how the &lt;strong&gt;Mapbox Standard Style&lt;/strong&gt; changes the whole style paradigm. &lt;/p&gt;

&lt;h2&gt;
  
  
  Enter the Mapbox Standard Style (and style "imports")
&lt;/h2&gt;

&lt;p&gt;I've spent all this time telling you about the old way of doing things and the underlying structure of a style so I can better explain the value of &lt;em&gt;the new way&lt;/em&gt;. The &lt;a href="https://docs.mapbox.com/map-styles/standard/guides/" rel="noopener noreferrer"&gt;Mapbox Standard Style&lt;/a&gt; is our premier, multipurpose basemap. At its core it is a style just like any other, with sources, layers, sprites, glyphs, etc, but it is designed to be &lt;em&gt;imported&lt;/em&gt; and &lt;em&gt;configured&lt;/em&gt; instead of being &lt;em&gt;cloned&lt;/em&gt; and &lt;em&gt;modified&lt;/em&gt;. This is a (relatively) new pattern for Mapbox styles, abstracting away the complexity of the basemap and allowing developers to focus their attention on the data they are presenting &lt;em&gt;on top of the basemap&lt;/em&gt;. &lt;/p&gt;

&lt;p&gt;In fact, when you load a map with the Mapbox Standard Style (either by not specifying a style at all, or by using the &lt;code&gt;mapbox://styles/mapbox/standard&lt;/code&gt; URL), the renderer actually creates a new style on the fly that &lt;em&gt;imports&lt;/em&gt; the Standard Style. The style that's actually loaded looks something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sources"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"layers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"imports"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"basemap"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"mapbox://styles/mapbox/standard"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Mapbox Standard also introduces the concept of &lt;a href="https://docs.mapbox.com/style-spec/reference/slots/" rel="noopener noreferrer"&gt;slots&lt;/a&gt;, which are placeholders in the layer stack of the imported style where any custom layers from the importing style can be inserted. For example, you may want your custom polygon layer to appear above paths and roads, but below buildings, models and labels, so you could place it in the &lt;code&gt;middle&lt;/code&gt; slot. For more on slots in Mapbox Standard, see the &lt;a href="http://docs.mapbox.com/map-styles/standard/guides/#slots" rel="noopener noreferrer"&gt;Mapbox Standard Style documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;But what if you want to modify the individual layers in the Standard Style? &lt;em&gt;Direct manipulation of these layers is not possible.&lt;/em&gt; This is part of the tradeoff of style imports. Instead of direct access to the layers, the style has been designed with a set of &lt;a href="https://docs.mapbox.com/map-styles/standard/api/#configuration-properties" rel="noopener noreferrer"&gt;configurable properties&lt;/a&gt;. Think of it as an API for the style. You can summon standard with or without map labels, set the color of water, land, and park areas, and toggle 3D buildings.&lt;/p&gt;

&lt;p&gt;Here's what that looks like in a Style JSON:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sources"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"layers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"imports"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"basemap"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"mapbox://styles/mapbox/standard"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"config"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="err"&gt;showPlaceLabels:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="err"&gt;show&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="err"&gt;dObjects:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="err"&gt;colorWater:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'#&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt;&lt;span class="err"&gt;F'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Best of all, the Standard Style is in continuous development, and because you are importing it instead of copying it, you get all the updates and improvements as they are released, without having to do any work to maintain it. &lt;/p&gt;

&lt;h2&gt;
  
  
  Implementation Patterns for the Mapbox Standard Style
&lt;/h2&gt;

&lt;p&gt;Here I'll outline three common patterns for implementing the Mapbox Standard Style in your maps, and the pros and cons of each.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Instantiate with standard + config parameters, add data as runtime layers and/or Markers/Annotations
&lt;/h3&gt;

&lt;p&gt;Mapbox Standard is now the default style for all Mapbox renderers, so specifying a &lt;code&gt;style&lt;/code&gt; when you instantiate a map is no longer required. The renderers also accept &lt;code&gt;config&lt;/code&gt; properties during map instantiation, so you can configure the Standard Style without having to create style JSON.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;map&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;mapboxgl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;center&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;72&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;41&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;zoom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;15.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="c1"&gt;// configure the Mapbox Standard Style before the map loads&lt;/span&gt;
    &lt;span class="na"&gt;basemap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;showPlaceLabels&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;show3dObjects&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;colorWater&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#00F&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;load&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addSource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;my-data&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;geojson&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://example.com/my-data.geojson&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addLayer&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;my-data-layer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;circle&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;my-data&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;paint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;circle-radius&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;circle-color&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#f00&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pros:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Simple and straightforward, with no need to maintain any style JSON&lt;/li&gt;
&lt;li&gt;Always up to date with the latest version of the Standard Style&lt;/li&gt;
&lt;li&gt;Configurable without needing to understand the complexity of the style JSON&lt;/li&gt;
&lt;li&gt;The basemap "just works" and you focus on adding your data after the map loads&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Cons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Duplicate client-side configuration if you are using the same style on multiple platforms (web, iOS, Android, Flutter)&lt;/li&gt;
&lt;li&gt;Verbose if you are adding a lot of runtime layers and sources, as all of that has to be done in code instead of in a style JSON.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Create a custom style JSON that imports the Standard Style, and load the map with the custom style's URL
&lt;/h3&gt;

&lt;p&gt;This approach means crafting your own style JSON, but it's a more manageable size since the Standard Style is abstracted away via an &lt;code&gt;import&lt;/code&gt;. You can add your custom sources and layers to the style JSON, and configure the Standard Style via the &lt;code&gt;config&lt;/code&gt; property in the import. This style JSON can be hosted anywhere, or included directly in your app's codebase.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;version&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sources&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;my-data&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;geojson&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;data&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://example.com/my-data.geojson&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;layers&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;my-data-layer&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;circle&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;source&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;my-data&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;paint&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;circle-radius&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;circle-color&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#f00&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;imports&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;basemap&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;url&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mapbox://styles/mapbox/standard&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;config&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;showPlaceLabels&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;show3dObjects&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;colorWater&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#00F&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;map&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;mapboxgl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;center&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;72&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;41&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;zoom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;15.5&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pros:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Centralized style management in a single JSON file, with no need for client-side configuration&lt;/li&gt;
&lt;li&gt;Still up to date with the latest version of the Standard Style, without having to manage it yourself&lt;/li&gt;
&lt;li&gt;Easier to maintain and update custom sources and layers in a style JSON than in code&lt;/li&gt;
&lt;li&gt;Compatible with GeoJSON, image, and video source types&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Cons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Requires understanding of the style JSON structure and the concept of imports&lt;/li&gt;
&lt;li&gt;Still requires maintaining a custom style JSON, which may be more work than just adding runtime layers&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Create a custom style in the Style Editor
&lt;/h3&gt;

&lt;p&gt;All custom styles created via the &lt;a href="https://studio.mapbox.com/" rel="noopener noreferrer"&gt;Mapbox Style Editor&lt;/a&gt; now include the Standard Style as an import, so if you are using the Style Editor to create your styles, you are already using the Standard Style under the hood, and can take advantage of all its features and updates without having to do anything extra. You can upload your data to the &lt;a href="https://console.mapbox.com/studio/data-workbench" rel="noopener noreferrer"&gt;data workbench&lt;/a&gt;, add it to a style, configure the basemap, and get a mapbox:// URL that you can use across all platforms, with the confidence that your basemap is always up to date and looking great.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;map&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;mapboxgl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mapbox://styles/your-username/your-style-id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;center&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;72&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;41&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;zoom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;15.5&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pros:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No need to maintain any style JSON, as everything is done through the Mapbox Style Editor and served via a mapbox:// URL&lt;/li&gt;
&lt;li&gt;The most user-friendly way to create and manage styles, with a visual editor and real-time preview&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Cons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;May not be ideal for teams that prefer to manage styles in code or version control, as styles in the Style Editor are not as easily versioned or diffed as JSON files&lt;/li&gt;
&lt;li&gt;Only compatible with vector and raster source types from Mapbox-hosted tilesets. If you want to use GeoJSON, image, or video sources, you would need to add those as runtime sources and layers in your app's code, which may be less than ideal if you have a lot of custom data to add.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Comparing The Standard Style with Classic Styles
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Style&lt;/th&gt;
&lt;th&gt;Usage approaches&lt;/th&gt;
&lt;th&gt;Customization&lt;/th&gt;
&lt;th&gt;Updates&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Mapbox Standard Style&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;small&gt;- Load by style URL; add your data layers at runtime&lt;br&gt;- Create a custom style JSON that &lt;code&gt;imports&lt;/code&gt; Standard as a dependency&lt;br&gt;- Build a custom style in the Mapbox Style Editor that imports Standard&lt;/small&gt;&lt;/td&gt;
&lt;td&gt;&lt;small&gt;- Predefined configuration parameters (no raw layer editing)&lt;br&gt;- Theme via lookup table (LUT) for cohesive color grading&lt;br&gt;- Specify key colors to globally tint the style&lt;br&gt;- Toggle POIs, landmarks, transit on/off&lt;br&gt;- Light presets (day, dusk, dawn, night)&lt;/small&gt;&lt;/td&gt;
&lt;td&gt;&lt;small&gt;&lt;strong&gt;Automatic&lt;/strong&gt; — Referenced by URL or import, Mapbox continuously updates the style and you receive all improvements (new features, data, rendering quality) automatically, without any action on your part.&lt;/small&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Mapbox Classic Styles&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;small&gt;- Load a Mapbox-hosted style URL (Streets, Satellite, Outdoors, etc.); add your data layers at runtime&lt;br&gt;- Fork a Mapbox classic style into your own style JSON and customize freely&lt;br&gt;- Create a custom style in the Mapbox Style Editor based on a classic style template, add your own data sources and layers&lt;/small&gt;&lt;/td&gt;
&lt;td&gt;&lt;small&gt;- Full access to every layer — edit colors, fonts, filters, zoom ranges directly&lt;br&gt;- Add, remove, or reorder any layer&lt;br&gt;- Customize data sources, expressions, and layout properties without restriction&lt;/small&gt;&lt;/td&gt;
&lt;td&gt;&lt;small&gt;&lt;strong&gt;Manual&lt;/strong&gt; — When you fork or copy a classic style, it becomes a snapshot. You own the style JSON and must manually apply any upstream changes from Mapbox; there are no automatic updates.&lt;/small&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Standard Style replaces multiple Classic Styles
&lt;/h3&gt;

&lt;p&gt;The Mapbox Standard Style also includes config parameters for color themes and light presets, and combing these can yield drastically different looks. &lt;/p&gt;

&lt;p&gt;With Classic Styles, we had whole different styles for different looks, like &lt;code&gt;streets&lt;/code&gt;, &lt;code&gt;light&lt;/code&gt;, and &lt;code&gt;dark&lt;/code&gt;. Streets was a good all-purpose style for a basemap, with a wide variety of colors and details. Light and dark were designed to be more subdued, ideal for maps where the overlaid data should be the star of the show, and the basemap should recede into the background. With the Standard Style, you can achieve all three looks with one style, and have more control over the colors and details than ever before.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Classic Style&lt;/th&gt;
&lt;th&gt;Equivalent using Standard Style&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Mapbox Streets&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;lightPreset: 'day'&lt;/code&gt; (default)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mapbox Light&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;lightPreset: 'day'&lt;/code&gt; + &lt;code&gt;colorTheme: 'monochrome'&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mapbox Dark&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;lightPreset: 'night'&lt;/code&gt; + &lt;code&gt;colorTheme: 'monochrome'&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;There's no quick configuration to replicate the &lt;strong&gt;Outdoors&lt;/strong&gt; classic style with Mapbox Standard, but there is a style template which imports Standard and adds 3D terrain, hillshading, and topographic lines to get you the same effect. See the &lt;a href="https://www.mapbox.com/gallery#mapbox-outdoors" rel="noopener noreferrer"&gt;Outdoors template&lt;/a&gt; in the Mapbox Style Gallery. You can clone this template in the Style Editor and add your own data to it. Keep in mind that you'll get continuous updates to the Standard Style since it's imported, but the additional layers are yours to manage. &lt;/p&gt;

&lt;h2&gt;
  
  
  Modifying styles at runtime
&lt;/h2&gt;

&lt;p&gt;Layers in a map style can be modified at runtime, and involves passing in bits of layer JSON to the &lt;code&gt;setLayoutProperty&lt;/code&gt; and &lt;code&gt;setPaintProperty&lt;/code&gt; methods on the map. This is a common pattern for adding interactivity to a map, such as changing the color of a layer when it's clicked, or toggling the visibility of a layer with a button click.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setLayoutProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;my-layer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;visibility&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;none&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setPaintProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;my-layer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;circle-color&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#0f0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since imported styles don't allow direct control of their internal map layers, control happens using the imported style's config parameters. All Mapbox renderers now include a method for modifying the config parameters of an imported style at runtime. This means you can change the colors, labels, and 3D settings of the Standard Style on the fly, without having to reload the map or create a new style JSON. This could be used to make the light preset (dawn, day, dusk, night) dynamic based on the user's actual time of day, or to toggle place labels on and off with a button click.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setConfigProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;basemap&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;lightPreset&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dusk&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setConfigProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;basemap&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;showPlaceLabels&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;See the &lt;a href="https://docs.mapbox.com/mapbox-gl-js/example/set-config-property/" rel="noopener noreferrer"&gt;setConfigProperty example&lt;/a&gt; for more details.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building your own imported styles
&lt;/h2&gt;

&lt;p&gt;While not widely documented, the style import pattern is not limited to just the Mapbox Standard Style. You can create your own styles that are designed to be imported into other styles, and have their own configuration options specific to your needs. This is a powerful pattern for managing complex styles, as it allows you to break down a large style into smaller, more manageable pieces, and reuse common elements across multiple styles.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sources"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"layers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"imports"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"basemap"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"mapbox://styles/mapbox/standard"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"config"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"transit"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"mapbox://styles/your-username/transit-style"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"config"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; 
        &lt;/span&gt;&lt;span class="err"&gt;showAcessibleTransit:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="err"&gt;showTransitLabels:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Other style tips and tricks
&lt;/h2&gt;

&lt;p&gt;Here are a few more tips.&lt;/p&gt;

&lt;h3&gt;
  
  
  Define GeoJSON source data in the style
&lt;/h3&gt;

&lt;p&gt;You can store data for &lt;a href="https://docs.mapbox.com/mapbox-gl-js/style-spec/sources/#geojson" rel="noopener noreferrer"&gt;GeoJSON sources&lt;/a&gt; right in the style JSON, instead of linking to it. This is ideal for small datasets that are tightly coupled to the style and don't need to be updated independently. Just add a &lt;code&gt;data&lt;/code&gt; property to your source definition and include the GeoJSON directly in the style JSON.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sources"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"my-data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"geojson"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"FeatureCollection"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"features"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Feature"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"geometry"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Point"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="nl"&gt;"coordinates"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;-72&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;41&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"properties"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"My Point"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Manipulate a style via code
&lt;/h3&gt;

&lt;p&gt;When working with a style as a JavaScript object, you can provide GeoJSON data as a variable or assemble any part of the object programmatically before using it in a map.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;myData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;FeatureCollection&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;features&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Feature&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;geometry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Point&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;coordinates&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;72&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;41&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;My Point&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;myStyle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;sources&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;my-data&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;geojson&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;myData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// Add a source to the style&lt;/span&gt;
&lt;span class="nx"&gt;myStyle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sources&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;vector&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://someserver.com/path-to-tilejson.json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Define the default camera position in the style
&lt;/h3&gt;

&lt;p&gt;Mapbox renderers honor camera properties (&lt;code&gt;center&lt;/code&gt;, &lt;code&gt;zoom&lt;/code&gt;, &lt;code&gt;pitch&lt;/code&gt;, &lt;code&gt;bearing&lt;/code&gt;) from the style JSON, so you can set the initial map view in the style instead of in code. This is useful for ensuring a consistent initial view across platforms, or when maintaining a style separately from the app code. These properties exist at the &lt;a href="https://docs.mapbox.com/style-spec/reference/root/" rel="noopener noreferrer"&gt;root&lt;/a&gt; of the style object.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"center"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;58.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sources"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"layers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;When it comes to styles, you've got options. Whether you just want a quick and easy way to get a map on the screen, or you want complete control over every layer and source, the style specification and Mapbox's tools are flexible and can accommodate your needs. &lt;/p&gt;

&lt;p&gt;The Mapbox Standard Style and the style import pattern have made it easier than ever to get a great-looking basemap up and running, while still allowing for customization and control when you need it.&lt;/p&gt;

&lt;p&gt;Hopefully this post has demystified some of the magic and recent changes around map styles, and given you some ideas for how to work with them in your own projects. If you have any questions or want to share your own style tips and tricks, feel free to reach out or leave a comment!&lt;/p&gt;

</description>
      <category>mapbox</category>
      <category>webmaps</category>
      <category>mobilemaps</category>
      <category>mapdesign</category>
    </item>
    <item>
      <title>Mapbox GL JS adds support for PMTiles vector sources</title>
      <dc:creator>Chris Whong</dc:creator>
      <pubDate>Thu, 30 Apr 2026 16:15:47 +0000</pubDate>
      <link>https://forem.com/mapbox/mapbox-gl-js-adds-support-for-pmtiles-vector-and-raster-sources-141b</link>
      <guid>https://forem.com/mapbox/mapbox-gl-js-adds-support-for-pmtiles-vector-and-raster-sources-141b</guid>
      <description>&lt;p&gt;Mapbox GL JS &lt;code&gt;v3.21.0&lt;/code&gt; added support for &lt;code&gt;vector&lt;/code&gt; sources using the &lt;a href="https://docs.protomaps.com/pmtiles/" rel="noopener noreferrer"&gt;PMTiles&lt;/a&gt; protocol — another option in your toolkit for bringing spatial data into Mapbox-powered web maps.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is PMTiles?
&lt;/h2&gt;

&lt;p&gt;PMTiles is a single-file &lt;a href="https://github.com/protomaps/PMTiles/blob/main/spec/v3/spec.md" rel="noopener noreferrer"&gt;open source&lt;/a&gt; format for storing tiled map data — vector or raster — built for random access over HTTP range requests. Host one on any static file server and you have a fully functional tileset, no complicated backend required.&lt;/p&gt;

&lt;p&gt;Traditional tile serving follows a familiar pattern: each tile gets its own URL using an x/y/z scheme, and behind that URL is either a directory of pre-cut files or a service dynamically pulling tiles from a database. Either way, there's infrastructure involved.&lt;/p&gt;

&lt;p&gt;PMTiles sidesteps all of that. Every tile request hits the same URL — what changes is the byte range. A tile's x/y/z coordinates are converted into a specific byte offset within the file, and the server returns just that chunk. The client receives it and renders it like any other tile, none the wiser.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzcbervynq3keujssynm8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzcbervynq3keujssynm8.png" alt=" " width="800" height="567"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Using PMTiles in Mapbox GL JS
&lt;/h2&gt;

&lt;p&gt;You're probably already familiar with adding a vector tileset to your map at runtime as a &lt;code&gt;vector&lt;/code&gt; source. The existing options are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A URL to a &lt;code&gt;TileJSON&lt;/code&gt;(&lt;a href="https://docs.mapbox.com/help/glossary/tilejson/" rel="noopener noreferrer"&gt;https://docs.mapbox.com/help/glossary/tilejson/&lt;/a&gt;) metadata file containing the tileset's URL template&lt;/li&gt;
&lt;li&gt;A Mapbox-hosted tileset via its &lt;code&gt;mapbox://&lt;/code&gt; URL (a shorthand for a TileJSON hosted by Mapbox)&lt;/li&gt;
&lt;li&gt;An explicit URL template with &lt;code&gt;{z}/{x}/{y}&lt;/code&gt; placeholders
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// TileJSON URL&lt;/span&gt;
&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addSource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mapbox-terrain&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vector&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://somedomain.com/tileset.json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Mapbox-hosted tileset&lt;/span&gt;
&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addSource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mapbox-terrain&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vector&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mapbox://mapbox.mapbox-terrain-v2&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Explicit x/y/z URL template&lt;/span&gt;
&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addSource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;some-vector-tileset&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vector&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;tiles&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://somedomain.com/tileset/{z}/{x}/{y}&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In all three cases, Mapbox GL JS ends up with a &lt;code&gt;{z}/{x}/{y}&lt;/code&gt; template URL and uses it to request tiles based on what's in view.&lt;/p&gt;

&lt;p&gt;With PMTiles support, you can now pass a &lt;code&gt;.pmtiles&lt;/code&gt; file URL directly as the &lt;code&gt;url&lt;/code&gt; property:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addSource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mapbox-terrain&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vector&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://somedomain.com/tileset.pmtiles&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. Mapbox GL JS detects the &lt;code&gt;.pmtiles&lt;/code&gt; extension and handles range requests behind the scenes.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Working Example
&lt;/h2&gt;

&lt;p&gt;The example below shows &lt;a href="https://earthquake.usgs.gov/earthquakes/feed/v1.0/geojson.php" rel="noopener noreferrer"&gt;USGS Earthquake data&lt;/a&gt; served from a PMTiles file and visualized with a &lt;a href="https://docs.mapbox.com/style-spec/reference/layers/#circle" rel="noopener noreferrer"&gt;&lt;code&gt;circle&lt;/code&gt;&lt;/a&gt;layer. &lt;/p&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/chris-whong/embed/PwGLJvN?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.mapbox.com/mapbox-gl-js/example/pmtiles-vector-source/" rel="noopener noreferrer"&gt;See the full example on docs.mapbox.com →&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here's the JavaScript where &lt;code&gt;addSource()&lt;/code&gt; is called with a URL to &lt;code&gt;.pmtiles&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;mapboxgl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;accessToken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;YOUR_MAPBOX_ACCESS_TOKEN&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;map&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;mapboxgl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;container&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;map&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mapbox://styles/mapbox/standard&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;basemap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;monochrome&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;lightPreset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;night&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;zoom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1.78&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;center&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;103.64548&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;51.14245&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;load&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addSource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pmtiles-earthquakes&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vector&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://docs.mapbox.com/mapbox-gl-js/assets/earthquakes.pmtiles&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addLayer&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pmtiles-vector-layer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;circle&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pmtiles-earthquakes&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;source-layer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;earthquakes&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;paint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;circle-color&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;transparent&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;circle-stroke-color&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;teal&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;circle-stroke-width&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;circle-radius&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;circle-emissive-strength&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How do I make a PMTiles?
&lt;/h2&gt;

&lt;p&gt;The PMTiles file in the example above was generated with &lt;a href="https://github.com/felt/tippecanoe" rel="noopener noreferrer"&gt;&lt;code&gt;tippecanoe&lt;/code&gt;&lt;/a&gt; from a week's worth of USGS earthquake data (~2,400 point features) and hosted on the same S3 bucket as docs.mapbox.com:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;tippecanoe &lt;span class="nt"&gt;-o&lt;/span&gt; public/earthquakes.pmtiles &lt;span class="nt"&gt;-Z0&lt;/span&gt; &lt;span class="nt"&gt;-z6&lt;/span&gt; &lt;span class="nt"&gt;-r1&lt;/span&gt; &lt;span class="nt"&gt;-pk&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt; earthquakes data/earthquakes-week.geojson &lt;span class="nt"&gt;--force&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's what each flag does:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Flag&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-o public/earthquakes.pmtiles&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Output file path&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-Z0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Minimum zoom level (global view)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-z6&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Maximum zoom level&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-r1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Drop rate of 1 — don't drop any features at any zoom&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-pk&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Allow tiles to exceed the 500KB size limit to preserve all features&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-l earthquakes&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Layer name, referenced as &lt;code&gt;source-layer&lt;/code&gt; in your style&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;data/earthquakes-week.geojson&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Input GeoJSON&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;--force&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Overwrite output file without prompting&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Try It Yourself
&lt;/h2&gt;

&lt;p&gt;Looking for publicly hosted PMTiles files to experiment with? Check out &lt;a href="https://pmtiles.io/" rel="noopener noreferrer"&gt;pmtiles.io&lt;/a&gt; — it includes several global vector and raster tilesets and is a handy tool for previewing PMTiles files you generate yourself.&lt;/p&gt;

&lt;p&gt;We'd love to hear about what you're building — drop into the &lt;code&gt;#web&lt;/code&gt; channel in the &lt;a href="https://discord.com/invite/uMpcC5RmJh" rel="noopener noreferrer"&gt;Mapbox Discord&lt;/a&gt; or share a post on &lt;a href="https://www.reddit.com/r/mapbox/" rel="noopener noreferrer"&gt;r/mapbox&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>spatialdata</category>
      <category>pmtiles</category>
      <category>web</category>
      <category>webmapping</category>
    </item>
    <item>
      <title>A GUI editor for Mapbox Color Themes</title>
      <dc:creator>Chris Whong</dc:creator>
      <pubDate>Tue, 03 Mar 2026 17:38:03 +0000</pubDate>
      <link>https://forem.com/mapbox/a-gui-editor-for-mapbox-color-themes-5fho</link>
      <guid>https://forem.com/mapbox/a-gui-editor-for-mapbox-color-themes-5fho</guid>
      <description>&lt;p&gt;&lt;em&gt;Introducing a browser-based prototype tool for iterating on custom Mapbox Standard Style themes in real time — no terminal required.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;tl;dr: &lt;strong&gt;&lt;a href="https://chriswhong.github.io/mapboxgl-theme-editor/" rel="noopener noreferrer"&gt;Open the Mapbox Standard Style Theme Editor →&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;Maps are a visual medium, and color is one of the most powerful tools in a cartographer's toolkit. Whether you're building a dark-mode experience for a nighttime delivery app, a muted editorial look for a data visualization, or a vivid branded style for a consumer product, the color of your basemap sets the entire tone.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.mapbox.com/map-styles/standard/guides/" rel="noopener noreferrer"&gt;Mapbox Standard&lt;/a&gt; — Mapbox's premier basemap style — supports deep color customization through a mechanism called &lt;strong&gt;Lookup Tables (LUTs)&lt;/strong&gt;. Most developers haven't encountered LUTs before. They're more at home in film post-production than web development. But once you understand how they work, they're an elegant and powerful way to retheme a map.&lt;/p&gt;

&lt;p&gt;This post explains what LUTs are, how Mapbox Standard uses them, and introduces a prototype web tool I built for experimenting with custom color themes in real time: the &lt;a href="https://chriswhong.github.io/mapboxgl-theme-editor/" rel="noopener noreferrer"&gt;Mapbox Standard Style Theme Editor&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Mapbox Standard Style
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://docs.mapbox.com/map-styles/standard/guides/" rel="noopener noreferrer"&gt;Mapbox Standard&lt;/a&gt; is Mapbox's flagship basemap. It features 3D buildings, photorealistic landmarks, dynamic lighting, and a set of built-in configuration options that make it easy to adapt for a wide range of use cases.&lt;/p&gt;

&lt;p&gt;For many applications, the quickest path to a custom look is the &lt;code&gt;lightPreset&lt;/code&gt; option, which controls ambient and directional lighting to simulate different times of day — dawn, day, dusk, or night. Combined with predefined &lt;code&gt;theme&lt;/code&gt; options like &lt;code&gt;faded&lt;/code&gt;, and &lt;code&gt;monochrome&lt;/code&gt;, you can achieve a lot without writing much code.&lt;/p&gt;

&lt;p&gt;Below you can see Mapbox Standard with its default color theme and daytime light as compared to the &lt;code&gt;night&lt;/code&gt; lighting preset and &lt;code&gt;monochrome&lt;/code&gt; theme, creating a subdued dark basemap:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhmbra9qb2iczrbedunn9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhmbra9qb2iczrbedunn9.png" alt=" " width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgi7bljau1job3ri31bfq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgi7bljau1job3ri31bfq.png" alt=" " width="800" height="797"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can also control some colors in Mapbox Standard via the configuration options, such as &lt;code&gt;colorWater&lt;/code&gt;, &lt;code&gt;colorLand&lt;/code&gt;, &lt;code&gt;colorGreenSpace&lt;/code&gt;, etc, so a Lookup Table is not required to start customizing colors on the map.&lt;/p&gt;

&lt;p&gt;You can try out the preset themes and individual color config options in the &lt;a href="https://docs.mapbox.com/playground/standard-style/" rel="noopener noreferrer"&gt;Mapbox Standard Style Playground&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;But for those who want event more control over the color palette, Mapbox Standard supports setting &lt;code&gt;theme&lt;/code&gt; to &lt;code&gt;custom&lt;/code&gt; and supplying your own &lt;strong&gt;Lookup Table&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Exactly Is a LUT?
&lt;/h2&gt;

&lt;p&gt;A Lookup Table — or LUT — is a data structure that maps input color values to output color values. In image and video processing, a LUT is typically encoded as a small image (sometimes called a "HALD CLUT" or "color cube") where each pixel's position in the image encodes an input RGB value, and its color encodes the corresponding output.&lt;/p&gt;

&lt;p&gt;Think of it as a complete color transformation recipe. Every possible input color has a predetermined output, and the LUT defines that mapping across the entire visible spectrum at once. Apply the LUT to any image, and every pixel is remapped accordingly.&lt;/p&gt;

&lt;p&gt;This image shows the "identity LUT", with no colors modified. It's a png with a repeating set of "squares" laid out horizontally. Each square shows red values along the x-axis, and green values along the y-axis. If you can mentally stack the squares, they become a cube, with blue values along the z-axis. The cube represents all possible color values and is used to map any input color to a corresponding output color.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4091tqu8a1v8q4r14psk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4091tqu8a1v8q4r14psk.png" width="256" height="16"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In video production, LUTs create consistent "looks" — film emulation, color grading, log-to-linear conversions. In photography, they power the one-click preset packs ubiquitous in Lightroom marketplaces. In Mapbox Standard, they're the engine behind custom color themes.&lt;/p&gt;

&lt;p&gt;The key insight is that the LUT doesn't know or care what it's being applied to. It just says: "If you see this color, output that color." Mapbox Standard renders the map to an intermediate image, applies your LUT, and out comes a fully rethemed map — buildings, roads, labels, land, water — all transformed consistently.&lt;/p&gt;

&lt;p&gt;Here is a custom LUT that applies a 15-degree hue rotation and increased saturation. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6juw9pyu8pkvwxtf3up2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6juw9pyu8pkvwxtf3up2.png" alt=" " width="256" height="16"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At first glance it looks the same as the identity LUT above, but look closely and you'll see subtle differences. You can see it applied to both a standalone image (from &lt;a href="https://commons.wikimedia.org/wiki/File:Eristalis_tenax_auf_Tragopogon_pratensis_01.JPG" rel="noopener noreferrer"&gt;wikimedia commons&lt;/a&gt;), and to the Mapbox Standard Style below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhlmoggu6ciy7fvicdtqt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhlmoggu6ciy7fvicdtqt.png" alt=" " width="800" height="296"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0ox0j9sd5rtr5bluqay6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0ox0j9sd5rtr5bluqay6.png" alt=" " width="800" height="350"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Try this &lt;a href="https://lut.tgratzer.com/" rel="noopener noreferrer"&gt;handy LUT tool&lt;/a&gt; by Tony Gratzer to see how different LUTs affect an input image.&lt;/p&gt;




&lt;h2&gt;
  
  
  Where Do LUTs Come From?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Download one
&lt;/h3&gt;

&lt;p&gt;There's a whole ecosystem of pre-made LUTs available online — similar to stock photography. Sites like &lt;a href="https://fixthephoto.com/free-luts" rel="noopener noreferrer"&gt;Fixthephoto&lt;/a&gt; and &lt;a href="https://www.rocketstock.com/free-after-effects-templates/35-free-luts-for-color-grading-videos/" rel="noopener noreferrer"&gt;RocketStock&lt;/a&gt; offer free and paid collections. Searching for "free HALD CLUT" will surface many more.&lt;/p&gt;

&lt;h3&gt;
  
  
  Build one in Photoshop
&lt;/h3&gt;

&lt;p&gt;Photoshop's color grading tools — Curves, Hue/Saturation, Color Balance — can be applied to a standard identity LUT image and then exported. If you're working with a designer, this is a natural fit. Mapbox has a step-by-step tutorial here: &lt;a href="https://docs.mapbox.com/help/tutorials/create-a-custom-color-theme/" rel="noopener noreferrer"&gt;Create a custom color theme with Photoshop&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Build one in the browser
&lt;/h3&gt;

&lt;p&gt;Several web-based tools let you create LUTs without any desktop software. &lt;a href="https://o-l-l-i.github.io/lut-maker/" rel="noopener noreferrer"&gt;LUT Maker by o-l-l-i&lt;/a&gt; provides sliders for hue rotation, saturation, brightness, contrast, and more, and exports the result as a PNG.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Challenge: Iterating on Custom Themes in a Mapbox Map
&lt;/h2&gt;

&lt;p&gt;When I set out to learn custom theming in Mapbox Standard, I arrived at what felt like the natural iteration loop:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open a browser-based LUT editor and adjust the sliders&lt;/li&gt;
&lt;li&gt;Export the LUT as a PNG&lt;/li&gt;
&lt;li&gt;Base64-encode it in the terminal: &lt;code&gt;base64 -i my-lut.png | pbcopy&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Open the &lt;a href="https://labs.mapbox.com/labs/standard-style-playground/" rel="noopener noreferrer"&gt;Mapbox Standard Style Playground&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Paste the string into the theme field&lt;/li&gt;
&lt;li&gt;Look at the result on the map&lt;/li&gt;
&lt;li&gt;Go back to step 1&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This worked. But it was tedious. Each iteration meant downloading a file, switching to the terminal, running a command, switching windows, and pasting a very long string — just to preview one change. For something as tactile and visual as color grading, that friction destroys creative momentum.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The fix was obvious: put the LUT editor and the map in the same window.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That's exactly what the Mapbox GL Theme Editor does.&lt;/p&gt;




&lt;h2&gt;
  
  
  Introducing the Mapbox GL Theme Editor
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://chriswhong.github.io/mapboxgl-theme-editor/" rel="noopener noreferrer"&gt;→ Try it now&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The tool is a single-page web app pairing a LUT generation UI with a live Mapbox Standard map. It's &lt;a href="https://github.com/chriswhong/mapboxgl-theme-editor" rel="noopener noreferrer"&gt;open source&lt;/a&gt; and hosted on GitHub Pages.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgq0u160e3tvjdx2dgw34.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgq0u160e3tvjdx2dgw34.png" alt=" " width="800" height="497"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Global controls
&lt;/h3&gt;

&lt;p&gt;The main panel applies broad color adjustments across the entire palette:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Hue rotation&lt;/strong&gt; — shifts all colors around the color wheel&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Saturation&lt;/strong&gt; — makes colors more vivid or more muted&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Brightness&lt;/strong&gt; — lightens or darkens the overall output&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Contrast&lt;/strong&gt; — expands or compresses the tonal range&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Temperature&lt;/strong&gt; — pushes the palette warmer (golden) or cooler (blue)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Drag any slider and the LUT preview in the sidebar updates instantly — and so does the map.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyveevsual44w0y2apal9.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyveevsual44w0y2apal9.gif" alt=" " width="720" height="391"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Targeted color corrections
&lt;/h3&gt;

&lt;p&gt;For more surgical edits, the tool includes a color correction mode. An eyedropper lets you select a specific color on the map — the blue of the water, the tan of building facades, the green of parks — and remap it to a different hue. A tolerance slider controls how broadly neighboring colors are pulled along.&lt;/p&gt;

&lt;p&gt;This is the right tool when you want to make precise changes: darken just the water, push the buildings to a warmer gray, or give vegetation a more stylized tint — without affecting anything else. The image below shows a targeted color correction changing water from light blue to light violet with low tolerance.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Warning:&lt;/strong&gt; The Mapbox Standard Style is in active development and default colors could change in the future. An LUT with targeted color corrections may not apply as expected if the input color changes in a future release of Mapbox Standard.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff7cotojdodgxlkjtuzqt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff7cotojdodgxlkjtuzqt.png" alt=" " width="800" height="492"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Using Your Theme
&lt;/h2&gt;

&lt;p&gt;You can use the LUT you create in the theme editor in your real-world map development:&lt;/p&gt;

&lt;h3&gt;
  
  
  In Mapbox Studio
&lt;/h3&gt;

&lt;p&gt;In Mapbox Studio, apply a custom LUT by navigating to the Standard style's theme settings, setting the theme to "Custom" and uploading your LUT as an image file. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4bngkus0b46dtjmv1k53.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4bngkus0b46dtjmv1k53.png" alt=" " width="800" height="506"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  In the Standard Style Playground
&lt;/h3&gt;

&lt;p&gt;You can also test directly in the &lt;a href="https://labs.mapbox.com/labs/standard-style-playground/" rel="noopener noreferrer"&gt;Mapbox Standard Style Playground&lt;/a&gt; by setting the theme to custom and pasting in the base64 encoded string for your LUT. You can combine your custom theme with other Standard configuration options — toggling 3D buildings, point-of-interest labels, light presets — and see how they interact.&lt;/p&gt;

&lt;p&gt;The Standard Style Playground also provides handy code snippets for applying your theme at runtime in Mapbox GL JS or the Mapbox Mobile Maps SDKs.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhbgnyez5oedr5826bf1p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhbgnyez5oedr5826bf1p.png" alt=" " width="800" height="422"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  In your frontend code
&lt;/h3&gt;

&lt;p&gt;You can use the base64 encoded string in your frontend code when using Mapbox GL JS or the Maps SDKs for iOS and Android along with the &lt;code&gt;theme&lt;/code&gt; and &lt;code&gt;theme-data&lt;/code&gt; Mapbox Standard Style config properties.&lt;/p&gt;

&lt;p&gt;Here's an example of how to instantiate a web map using the Mapbox Standard Style and a custom theme in a web project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;map&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;mapboxgl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;container&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;map&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mapbox://styles/mapbox/standard&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;basemap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;custom&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;theme-data&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;YOUR_LUT_STRING&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;center&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;73.99059&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;40.74012&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;zoom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;11.50&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Why Build a Custom Tool?
&lt;/h2&gt;

&lt;p&gt;Beyond solving the iteration problem, there was an educational motivation for building this.&lt;/p&gt;

&lt;p&gt;LUTs are unfamiliar territory for most web and mobile developers. The best way to build intuition for how they work is to &lt;em&gt;play with one&lt;/em&gt; — to drag a hue slider and immediately see every green park on the map turn magenta, or to pick the water with an eyedropper and watch the rivers shift from steel blue to teal. The tool makes an abstract concept concrete in a way that documentation simply can't.&lt;/p&gt;

&lt;p&gt;It also demystifies the pipeline. When the LUT preview image updates in the same window as the map, you can see exactly what a LUT is and what it does. You start to understand &lt;em&gt;why&lt;/em&gt; cranking contrast makes building shadows punch harder, or why rotating the hue by 180 degrees turns a daylight city into something that looks like a photo negative. That understanding makes you a more intentional user of the feature.&lt;/p&gt;

&lt;p&gt;On the practical side, the faster loop changes the quality of the work. Color choices that used to require five minutes of file-downloading and clipboard gymnastics now take five seconds of slider dragging. That's not just a time savings — it's the difference between exploring ten variations and exploring a hundred.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's Next?
&lt;/h2&gt;

&lt;p&gt;This is a prototype. It works and it's fun to use, but there's room to grow. The repo is open source at &lt;a href="https://github.com/chriswhong/mapboxgl-theme-editor" rel="noopener noreferrer"&gt;github.com/chriswhong/mapboxgl-theme-editor&lt;/a&gt;. Issues and pull requests are welcome. And there's a conversation worth having about whether this kind of functionality should eventually live inside Mapbox Studio or the Standard Style Playground — or whether the complexity of a dedicated theming workflow makes it better as its own tool.&lt;/p&gt;




&lt;h2&gt;
  
  
  Give It a Try
&lt;/h2&gt;

&lt;p&gt;The best way to understand LUTs and custom theming in Mapbox Standard is to open the tool and start dragging sliders. It takes about thirty seconds before you're looking at a map that doesn't look like any map you've seen before.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://chriswhong.github.io/mapboxgl-theme-editor/" rel="noopener noreferrer"&gt;Open the Mapbox Standard Style Theme Editor →&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When you find a look you like, grab the base64 string and drop it into your project — or share a screenshot and tell me what you made.&lt;/p&gt;

</description>
      <category>design</category>
      <category>showdev</category>
      <category>tooling</category>
      <category>webdev</category>
    </item>
    <item>
      <title>How Lemontree improved map performance with a small refactor</title>
      <dc:creator>Chris Whong</dc:creator>
      <pubDate>Fri, 13 Feb 2026 21:51:45 +0000</pubDate>
      <link>https://forem.com/mapbox/how-lemontree-improved-map-performance-with-a-small-refactor-2272</link>
      <guid>https://forem.com/mapbox/how-lemontree-improved-map-performance-with-a-small-refactor-2272</guid>
      <description>&lt;p&gt;&lt;em&gt;A Q&amp;amp;A with &lt;a href="https://www.linkedin.com/in/samuelcole" rel="noopener noreferrer"&gt;Samuel Cole&lt;/a&gt;, CTO of Lemontree&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;a href="https://www.foodhelpline.org/" rel="noopener noreferrer"&gt;Lemontree&lt;/a&gt; is a New York-based nonprofit on a mission to fight food insecurity by connecting hungry Americans to free food resources near them. Often described as a "Yelp for food banks," they curate and manage rich data on thousands of food pantries across the U.S., making it accessible through an easy-to-use digital platform with maps, SMS alerts, and more. In 2025, Lemontree helped &lt;strong&gt;over a million people&lt;/strong&gt; locate nearby food pantries.&lt;/p&gt;

&lt;p&gt;At the heart of their product is an interactive map — powered by &lt;a href="https://docs.mapbox.com/mapbox-gl-js/" rel="noopener noreferrer"&gt;Mapbox GL JS&lt;/a&gt; — that lets users instantly see nearby pantry locations, hours, and available foods. Recently, Lemontree overhauled how they load and display map data to dramatically improve performance. We sat down with Samuel Cole, CTO of Lemontree, to learn how they approached the problem and what they'd do differently from the start.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fenqn0js9pqccyuy1i90p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fenqn0js9pqccyuy1i90p.png" alt=" " width="800" height="486"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Chris:&lt;/strong&gt; Let's start with the original setup. What were you displaying on the map, where did the data come from, and what was falling short in terms of user experience?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Samuel:&lt;/strong&gt; When we load a map, we first call an endpoint to find the best 10 resources for the focus point of the map using our AI sorting algorithm, but then we don't want you to miss out on the other resources in the area, so we built an API called &lt;code&gt;markersWithinBounds&lt;/code&gt; that returns &lt;em&gt;all the resources&lt;/em&gt; within the bounding box of the map. These are visualized as many smaller background markers in addition to the larger "best 10 resources" markers.&lt;/p&gt;

&lt;p&gt;We're using &lt;a href="https://visgl.github.io/react-map-gl/" rel="noopener noreferrer"&gt;&lt;code&gt;react-map-gl&lt;/code&gt;&lt;/a&gt; so it was straightforward to loop over the results of that api call with a &lt;code&gt;.map()&lt;/code&gt; and create a component for each one. When we first built that, we were testing pretty zoomed in to a few blocks around the location, so while the density of food pantries is pretty high (there are more food pantries in the US than McDonalds!), it seemed like it was scaling ok, because most people are only interested in the immediate area. &lt;/p&gt;

&lt;p&gt;But in real life, pretty much everybody immediately zoomed out! All of a sudden the API call returned thousands of points, and rendering thousands of Markers on the screens was chugging. Users' whole device was lagging and slow, and then if they started panning around? It was wild.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fayjq7gfmdnidd1cc0uae.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fayjq7gfmdnidd1cc0uae.png" alt=" " width="800" height="539"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Zooming out on the map triggered an API call for new data and could contain hundreds of points or more.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Chris:&lt;/strong&gt; This is a classic Mapbox GL JS pitfall that catches a lot of developers by surprise. Markers are the most intuitive way to get point features on a map — the API is simple, positioning is easy, and you can drop in a custom image without much fuss. But they're rendered as individual DOM elements, which means &lt;strong&gt;performance degrades quickly as the count climbs&lt;/strong&gt;. Beyond a few hundred markers, you're fighting the browser itself. After you hit this wall, what solutions did you explore, and how did you arrive at the right path forward?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Samuel:&lt;/strong&gt; When I was preparing &lt;a href="//youtube.com/watch?si=5cvDlGFkV4LClk41&amp;amp;v=Axpahz-KpT0&amp;amp;feature=youtu.be"&gt;my talk for the BUILD with Mapbox&lt;/a&gt; virtual conference, you actually pulled me aside and said: "Hey, if you used a symbol layer for this it would scale a ton better."&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Chris:&lt;/strong&gt; Ah, well you were in the right place to get a direct suggestion! Hopefully this post will help a lot more people who might be hitting the same issue. Symbol layers are rendered by the GPU as part of the map's WebGL canvas, which means they handle thousands of points with ease and pan or zoom without any jank. The tradeoff is that they're less intuitive to set up, especially in a React codebase where you're used to thinking declaratively. Can you walk us through what the refactor actually looked like?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Samuel:&lt;/strong&gt; We have a lot going on at Lemontree! I wasn't able 📍 immediately prioritize that work, but one Friday I took it on as sort of a 'hack day' project. Unfortunately, I got a bit stuck because the &lt;code&gt;react-map-gl&lt;/code&gt; &lt;code&gt;&amp;lt;Layer&amp;gt;&lt;/code&gt; component, which was declarative in the React style, was being added before the style was loaded, and I just ran out of time in my hack day to figure it out. When I got access to AI-assisted development through Copilot and Claude Code, I was looking for little test cases, and I remembered this performance issue: I asked Claude, it added an &lt;code&gt;styleLoaded &amp;amp;&amp;amp;&lt;/code&gt; pattern, and we were off to the races! You can compare the code for the Marker implementation vs the Symbol layer:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Before: Markers 📍&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* Old approach: Render a DOM marker for each resource */&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;otherMarkersData&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;features&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;feature&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Marker&lt;/span&gt;
      &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;feature&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="na"&gt;latitude&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;feature&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;geometry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;coordinates&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="na"&gt;longitude&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;feature&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;geometry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;coordinates&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Image&lt;/span&gt;
        &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;purplePin&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="na"&gt;alt&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Resource"&lt;/span&gt;
        &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pointer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/resources/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;feature&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Marker&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;))}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Problems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Creates a DOM node for every marker (100s+ of elements)&lt;/li&gt;
&lt;li&gt;Heavy re-renders when panning/zooming&lt;/li&gt;
&lt;li&gt;Poor performance with many markers&lt;/li&gt;
&lt;li&gt;Limited styling options&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;After: Symbol Layer 🚀&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* New approach: Single GeoJSON source with symbol layer */&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Source&lt;/span&gt;
    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"other-resources"&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"geojson"&lt;/span&gt;
    &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;filteredOtherMarkersData&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="na"&gt;promoteId&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"id"&lt;/span&gt;
  &lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Layer&lt;/span&gt;
      &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"other-resources-layer"&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"symbol"&lt;/span&gt;
      &lt;span class="na"&gt;layout&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;icon-image&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;purpleMarker&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;icon-allow-overlap&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Source&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Benefits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Single GeoJSON source for all markers&lt;/li&gt;
&lt;li&gt;GPU-accelerated rendering&lt;/li&gt;
&lt;li&gt;Smooth performance with 1000+ markers&lt;/li&gt;
&lt;li&gt;Feature state for hover effects&lt;/li&gt;
&lt;li&gt;Click/hover handled via interactiveLayerIds&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Performance Impact:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;10x faster rendering&lt;/li&gt;
&lt;li&gt;Butter-smooth panning and zooming&lt;/li&gt;
&lt;li&gt;Reduced memory footprint&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;Chris:&lt;/strong&gt; That's a great example of AI tooling unblocking a task that kept slipping down the priority list. The &lt;code&gt;styleLoaded&lt;/code&gt; guard is a small but critical detail — symbol layers depend on the map style being fully initialized before you can add sources or layers to the canvas at runtime, and it's an easy thing to miss until things break in subtle ways. What has the impact been since shipping? Are users noticing the difference?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Samuel:&lt;/strong&gt; Our clients are spending a bit more time exploring around the map, but honestly our recommendation algorithm gives plenty of food pantries near where they live, so in surprising ways exploring the map isn't their primary way of discovering food resources, and they use the map more for navigating the pantries we've already referred them to. Where the map really shines is with our partners! Organizations that run many pantries love to see their whole network, foundations love to see our coverage, and governments are interested in how food access is working in their districts.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Chris:&lt;/strong&gt; That's a fascinating insight — power users for the map include the partner organizations doing network-level planning, not just the end users trying to find nearby food. It reframes what "map performance" means for your product. Speaking of which, you've been building on Mapbox for a long time. What's your overall take on the platform — what's working well, and where do you think there's room to grow?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Samuel:&lt;/strong&gt; We love Mapbox; I've been using maps for my whole career, ever since my very first tech startup job, and Mapbox reflects my values for open, accessible, and extensible maps. The reality is that other mapping providers just don't offer the extensibility of Mapbox's platform, whenever I want to create a more targeted experience (which I believe people experiencing food insecurity deserve the best experiences!) the out-of-the-box approach from other map providers, just isn't going to cut it. I don't want a Google Map, I want a Lemontree Map.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Chris:&lt;/strong&gt; "I don't want a Google Map, I want a Lemontree Map" — I love that. Mapbox gives you all you need to make the whole map experience your own creation. What's on the roadmap next for mapping at Lemontree?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Samuel:&lt;/strong&gt; I love maps! Around Lemontree, my coworkers all call me the map guy. I think as we grow, it's going to be even more important for our partners to visualize how food access works around the U.S., so I'm excited to make more visualizations to tell stories like "Where are the food pantries that focus on elderly people in New Jersey, and where are the gaps?" There are almost 50 million people who experience food insecurity in the US, and we have a lot of work to do to understand where they are, and what resources they have available to them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Chris:&lt;/strong&gt; Great, hopefully Mapbox maps can help with that data storytelling to raise awareness about food insecurity. Thanks for chatting with me about your map's technical hiccup and the fix. I'm glad to hear the map is performing better, especially given the importance of the data you're displaying on it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Takeaways
&lt;/h2&gt;

&lt;p&gt;Lemontree's Markers to symbol layer refactor is a great case study in a problem many Mapbox developers encounter: &lt;strong&gt;Markers and symbol layers are both valid tools for displaying point data, but they have very different performance profiles.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Markers are DOM elements — straightforward to set up, but expensive to render at scale. Symbol layers are drawn by WebGL on the map's canvas, making them capable of displaying tens of thousands of points without breaking a sweat. If you're showing dynamic data that can grow beyond a few hundred points, a symbol layer is almost always the right call.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdbi3drm22imwbr9hu0ck.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdbi3drm22imwbr9hu0ck.gif" alt=" " width="720" height="402"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff64mdi5k3fx7dow3culn.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff64mdi5k3fx7dow3culn.gif" alt=" " width="720" height="504"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;These graphics help explain the difference between Markers, which are DOM elements displayed "on the map" versus symbol layers which are rendered "in the map"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;For teams building in React with &lt;code&gt;react-map-gl&lt;/code&gt;, the key implementation detail is guarding your layer additions behind a &lt;code&gt;styleLoaded&lt;/code&gt; check — a small fix that, as Samuel found, can make the difference between a polished experience and a broken one. (the equivalent when using Mapbox GL JS without a wrapping library is listening for the &lt;a href="https://docs.mapbox.com/mapbox-gl-js/api/map/#map.event:style.load" rel="noopener noreferrer"&gt;&lt;code&gt;map.on(style.load)&lt;/code&gt;&lt;/a&gt; event. &lt;/p&gt;

&lt;p&gt;You can learn more about when to use Markers versus symbol layers in the &lt;a href="https://docs.mapbox.com/mapbox-gl-js/guides/" rel="noopener noreferrer"&gt;Add your Data guide&lt;/a&gt; in the Mapbox GL JS documentation.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>mapbox</category>
      <category>webmap</category>
      <category>community</category>
    </item>
    <item>
      <title>Squarespace X Mapbox</title>
      <dc:creator>Chris Whong</dc:creator>
      <pubDate>Thu, 12 Feb 2026 14:48:12 +0000</pubDate>
      <link>https://forem.com/mapbox/squarespace-x-mapbox-1k44</link>
      <guid>https://forem.com/mapbox/squarespace-x-mapbox-1k44</guid>
      <description>&lt;h1&gt;
  
  
  How to Add an Interactive Mapbox Map to Your Squarespace Website
&lt;/h1&gt;

&lt;p&gt;This guide shows developers how to integrate &lt;a href="https://docs.mapbox.com/mapbox-gl-js" rel="noopener noreferrer"&gt;Mapbox GL JS&lt;/a&gt; into a &lt;a href="https://squarespace.com" rel="noopener noreferrer"&gt;Squarespace&lt;/a&gt; website using Squarespace's &lt;em&gt;embed block&lt;/em&gt;. If you're already familiar with Mapbox GL JS development and want to add custom interactive maps to your Squarespace site, this post will show you exactly where to place your code to get it working.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;✅ What this guide covers:&lt;/strong&gt; The technical steps to embed Mapbox GL JS code in a Squarespace website &lt;br&gt;
&lt;strong&gt;❌ What this guide doesn't cover:&lt;/strong&gt; Details of interactive map development with Mapbox GL JS (the &lt;a href="https://docs.mapbox.com/mapbox-gl-js/exmaples" rel="noopener noreferrer"&gt;examples&lt;/a&gt; are a great place to start for inspiration and working code snippets)&lt;/p&gt;

&lt;p&gt;We'll show a &lt;strong&gt;simple example&lt;/strong&gt; of displaying a business location with a marker, popup, and rotating camera to demonstrate the integration. Once you understand where the code goes and how it's structured in Squarespace, you can implement any Mapbox GL JS functionality you need by changing the JavaScript code.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbbxl46sarfeguckfn8sm.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbbxl46sarfeguckfn8sm.gif" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here's a codepen so you can get a better look at the actual map and animation shown in this guide. It uses the &lt;a href="https://docs.mapbox.com/map-styles/standard/guides/" rel="noopener noreferrer"&gt;Mapbox Standard Style&lt;/a&gt; as a basemap, showing detailed 3D buildings and an optional monochrome theme. The popup is styled to match the green colors used on the Squarespace site it is embedded in:&lt;/p&gt;

&lt;p&gt;

&lt;iframe height="600" src="https://codepen.io/Chris-Whong/embed/NPrBPeb?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;


&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; This technique of adding custom HTML, CSS and JavaScript via the Squarespace editor is suitable for simpler Mapbox GL JS implementations but does not include version control or other benefits of a separate development workflow. If your map becomes more complex, you may want to deploy it as a standalone web app, then embed it in your Squarespace site using its URL. &lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;To follow this guide, you'll need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;A Squarespace website&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Working knowledge of Mapbox GL JS&lt;/strong&gt; - you should be comfortable writing HTML, CSS, and JavaScript to create maps&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A Mapbox account and access token&lt;/strong&gt; - if you don't have one yet, &lt;a href="https://account.mapbox.com" rel="noopener noreferrer"&gt;sign up here&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Getting Your Mapbox Access Token
&lt;/h2&gt;

&lt;p&gt;Before we start, you'll need a &lt;strong&gt;Mapbox access token&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to &lt;a href="https://account.mapbox.com" rel="noopener noreferrer"&gt;mapbox.com&lt;/a&gt; and sign up for a free account (if you don't have one)&lt;/li&gt;
&lt;li&gt;Once logged in, navigate to your &lt;a href="https://console.mapbox.com" rel="noopener noreferrer"&gt;console&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Find the "Tokens" link in the left sidebar to go to the &lt;a href="https://console.mapbox.com/account/access-tokens/" rel="noopener noreferrer"&gt;tokens page&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Copy your &lt;strong&gt;default public token&lt;/strong&gt; (it starts with &lt;code&gt;pk.&lt;/code&gt;)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Keep this token handy—you'll need it in a moment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Adding the Code Block
&lt;/h2&gt;

&lt;p&gt;Let's add the HTML that will contain your map code:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open your Squarespace Editor and navigate to the page where you want to add the map&lt;/li&gt;
&lt;li&gt;Click the &lt;strong&gt;+ ADD BLOCK&lt;/strong&gt; button
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fefeycfqv5ivli77uua9x.png" alt=" "&gt;
&lt;/li&gt;
&lt;li&gt;Scroll down in the block selection panel to find "Embed"
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsazbuvwsimthgyby51oe.png" alt=" "&gt;
&lt;/li&gt;
&lt;li&gt;Drag and resize the embed block to position it wherever you want it on your site layout.
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fscijvvhcunv1iypzcczg.gif" alt=" "&gt;
&lt;/li&gt;
&lt;li&gt;Double-click on the embed block to open its &lt;strong&gt;Content&lt;/strong&gt; settings panel. Under &lt;strong&gt;EMBED AS&lt;/strong&gt;, choose &lt;strong&gt;Code Snippet&lt;/strong&gt;, then click the &lt;strong&gt;Embed Data&lt;/strong&gt; button. 
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbz6qz59qjf9rqfj0qy44.png" alt=" "&gt;
&lt;/li&gt;
&lt;li&gt;A code editor will appear. You will add code to this input in the next step.
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fudlyirvwczahun8rxwkd.png" alt=" "&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Step 2: Add Mapbox GL JS Code
&lt;/h2&gt;

&lt;p&gt;Now you'll add Mapbox GL JS code to get a map working in the code block. The code block accepts any HTML, so you can include CSS and JavaScript by using &lt;code&gt;&amp;lt;style&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tags.&lt;/p&gt;

&lt;p&gt;Here's a working example that shows a business location with a marker and rotating camera. Copy this code and replace &lt;code&gt;YOUR_MAPBOX_ACCESS_TOKEN&lt;/code&gt; with your actual access token:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- import Mapbox GL JS and its CSS file --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;'https://api.mapbox.com/mapbox-gl-js/v3.18.1/mapbox-gl.js'&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;'https://api.mapbox.com/mapbox-gl-js/v3.18.1/mapbox-gl.css'&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;'stylesheet'&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- override default styles for the map and popup --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;style&amp;gt;&lt;/span&gt;
  &lt;span class="nc"&gt;.mapboxgl-popup-content&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#e6f2d2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#2f5f48&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/style&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- add a container element for the map --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"map-container"&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"position: absolute; height: 100%; width: 100%"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
  &lt;span class="c1"&gt;// your Mapbox access token&lt;/span&gt;
  &lt;span class="nx"&gt;mapboxgl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;accessToken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;YOUR_MAPBOX_ACCESS_TOKEN&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// initialize the map&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;map&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;mapboxgl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;container&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;map-container&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// container ID&lt;/span&gt;
    &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mapbox://styles/mapbox/standard&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// style URL&lt;/span&gt;
    &lt;span class="na"&gt;center&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;73.99986&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;40.75300&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="c1"&gt;// starting position [lng, lat]&lt;/span&gt;
    &lt;span class="na"&gt;zoom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;15.36&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// starting zoom&lt;/span&gt;
    &lt;span class="na"&gt;bearing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;28.00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// bearing in degrees&lt;/span&gt;
    &lt;span class="na"&gt;pitch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;56.50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// pitch in degrees&lt;/span&gt;
    &lt;span class="na"&gt;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;basemap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;monochrome&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// use the monochrome basemap theme&lt;/span&gt;
        &lt;span class="na"&gt;showPointOfInterestLabels&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="c1"&gt;// hide point of interest labels&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="c1"&gt;// disable scroll zoom so the map won't zoom as the user scrolls down the page&lt;/span&gt;
  &lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scrollZoom&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;disable&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="c1"&gt;// create a popup with address details and a link to get directions on Google Maps&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;popup&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;mapboxgl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Popup&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;closeOnClick&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;closeButton&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nx"&gt;setHTML&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;`&amp;lt;div&amp;gt;
      450 W 33rd St&amp;lt;br/&amp;gt;
      New York, NY 10001&amp;lt;/div&amp;gt;
      &amp;lt;a href="https://maps.app.goo.gl/6WzErvMBMi2Hn64Z7" target="_blank" rel="noopener noreferrer"&amp;gt;&amp;lt;span class="text-xs"&amp;gt;Get Directions&amp;lt;/span&amp;gt;&amp;lt;/a&amp;gt;
    `&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// create a map marker with a custom color, connect the popup, add it to the map, and open the popup&lt;/span&gt;
  &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;mapboxgl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Marker&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#2f5f48&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setLngLat&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;73.99986&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;40.75300&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setPopup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;popup&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;togglePopup&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="c1"&gt;// this function rotates the camera continuously&lt;/span&gt;
  &lt;span class="c1"&gt;// see https://docs.mapbox.com/mapbox-gl-js/example/animate-camera-around-point/&lt;/span&gt;
  &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;rotateCamera&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rotateTo&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;timestamp&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;360&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="nf"&gt;requestAnimationFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rotateCamera&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;load&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// start the camera animation.&lt;/span&gt;
    &lt;span class="nf"&gt;rotateCamera&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Paste the Mapbox GL JS snippet from above into the code editor.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Important&lt;/strong&gt;: Replace the placeholder text &lt;code&gt;YOUR_MAPBOX_ACCESS_TOKEN&lt;/code&gt; with the actual token you copied from your Mapbox account.&lt;/p&gt;

&lt;p&gt;Click anywhere outside the editor panel to apply your code update. You will see a warning about embedded scripts, with an option to &lt;strong&gt;preview in safe mode&lt;/strong&gt;. This is normal, as Squarespace prevents execution of JavaScript code in the editor by default. &lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo9xzfdggqxcy90hjutqy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo9xzfdggqxcy90hjutqy.png" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Exit the editor. You should see an animated map with a monochrome basemap, a marker centered over New York City, and a slowly rotating camera. That's it, you're ready to publish this map on your site!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5ro3k7gwa759ao8p1pki.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5ro3k7gwa759ao8p1pki.png" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Understanding the Code Structure
&lt;/h2&gt;

&lt;p&gt;Here's how the code for this example map breaks down:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Library Imports&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;'https://api.mapbox.com/mapbox-gl-js/v3.18.1/mapbox-gl.js'&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;'https://api.mapbox.com/mapbox-gl-js/v3.18.1/mapbox-gl.css'&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;'stylesheet'&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These load Mapbox GL JS and its required stylesheet from the Mapbox CDN.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Custom Styles&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;style&amp;gt;&lt;/span&gt;
  &lt;span class="nc"&gt;.mapboxgl-popup-content&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#e6f2d2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#2f5f48&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/style&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The popup styles customize the appearance to match the page theme.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Map Container&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"map-container"&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"position: absolute; height: 100%; width: 100%"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This div is where the map renders. The inline styles ensure it fills the available space.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Your Map Logic&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
  &lt;span class="c1"&gt;// All your Mapbox GL JS code goes here&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is where you implement your map functionality. In this example, we're:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Setting the access token&lt;/li&gt;
&lt;li&gt;Initializing the map with specific style and camera settings&lt;/li&gt;
&lt;li&gt;Disabling scroll zoom (important for Squarespace pages!)&lt;/li&gt;
&lt;li&gt;Creating a popup and marker&lt;/li&gt;
&lt;li&gt;Adding a camera rotation animation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can replace or extend this code with any Mapbox GL JS functionality you need. &lt;/p&gt;

&lt;p&gt;If you want to adapt this example of a single marker and popup, you should:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Change the map's center point, zoom, pitch and bearing to your desired initial camera view.&lt;/li&gt;
&lt;li&gt;Change the Marker's coordinates&lt;/li&gt;
&lt;li&gt;Change the Popup's content by editing the HTML code in &lt;code&gt;Popup.setHTML()&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Or, you can leave the imports and the map container in place and start over with the map JavaScript code and build whatever you need. See additional developer resources below.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resources for Mapbox GL JS Development
&lt;/h2&gt;

&lt;p&gt;Here are essential resources for building your map functionality:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mapbox Documentation&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://docs.mapbox.com/mapbox-gl-js/example/" rel="noopener noreferrer"&gt;Mapbox GL JS Examples&lt;/a&gt; - Dozens of live, interactive code examples&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.mapbox.com/mapbox-gl-js/" rel="noopener noreferrer"&gt;Mapbox GL JS Guides&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.mapbox.com/mapbox-gl-js/api/" rel="noopener noreferrer"&gt;Mapbox GL JS API Reference&lt;/a&gt; - Complete documentation of all map methods and options&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Key Examples&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://docs.mapbox.com/mapbox-gl-js/example/simple-map/" rel="noopener noreferrer"&gt;Display a map&lt;/a&gt; - Basic map initialization&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.mapbox.com/mapbox-gl-js/example/add-a-marker/" rel="noopener noreferrer"&gt;Add markers to a map&lt;/a&gt; - Working with markers&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.mapbox.com/mapbox-gl-js/example/popup/" rel="noopener noreferrer"&gt;Display a popup&lt;/a&gt; - Popup functionality&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.mapbox.com/mapbox-gl-js/example/geojson-markers/" rel="noopener noreferrer"&gt;Add GeoJSON data&lt;/a&gt; - Display custom data&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Community&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://reddit.com/r/mapbox" rel="noopener noreferrer"&gt;r/mapbox on Reddit&lt;/a&gt; - Get help and share ideas&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://discord.com/invite/mapboxdevs-1004826913229000704" rel="noopener noreferrer"&gt;Mapbox Developer Discord&lt;/a&gt; - Post in the &lt;code&gt;#web&lt;/code&gt; channel to ask questions and share what you build.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Troubleshooting
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Map not displaying?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Verify you've replaced &lt;code&gt;YOUR_MAPBOX_ACCESS_TOKEN&lt;/code&gt; with your actual token&lt;/li&gt;
&lt;li&gt;Check that your access token has the correct permissions&lt;/li&gt;
&lt;li&gt;Ensure the code block has sufficient height and width in the Squarespace editor &lt;strong&gt;and&lt;/strong&gt; that the map container div also has sufficient height and width&lt;/li&gt;
&lt;li&gt;Open your browser's developer console to check for JavaScript errors&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;By following the steps in this guide, you should now have a Mapbox GL JS map working in your Squarespace website and are ready to iterate the JavaScript code to customize the map to meet your needs.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Questions about this integration approach? Found a better way to do something? Share your experience in the comments!&lt;/em&gt;&lt;/p&gt;

</description>
      <category>squarespace</category>
      <category>mapbox</category>
      <category>webmap</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Introducing Mapbox Agent Skills</title>
      <dc:creator>Matthew Podwysocki</dc:creator>
      <pubDate>Wed, 11 Feb 2026 22:09:57 +0000</pubDate>
      <link>https://forem.com/mapbox/introducing-mapbox-agent-skills-1k19</link>
      <guid>https://forem.com/mapbox/introducing-mapbox-agent-skills-1k19</guid>
      <description>&lt;p&gt;I'm excited to share something we've been working on that makes building with Mapbox a lot easier, especially if you're using AI coding assistants like Claude Code, Cursor, or GitHub Copilot.&lt;/p&gt;

&lt;p&gt;We've built a collection of Agent Skills that teach AI assistants how to build better Mapbox applications. Think of it as giving your AI assistant a crash course in Mapbox development best practices.&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;Agent Skills teach AI assistants Mapbox development best practices. We've built 15 skills and counting covering web/iOS/Android integration, performance optimization, search implementation, geospatial operations, map design, migrations, security, and common patterns. Install them and your AI will know things like "cluster 1000+ markers" and "use Search Box API for autocomplete."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Install all skills:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx add-skill mapbox/mapbox-agent-skills
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Install specific skills:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx skills add mapbox/mapbox-agent-skills &lt;span class="nt"&gt;--skill&lt;/span&gt; mapbox-web-performance-patterns
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;List available skills:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx skills add mapbox/mapbox-agent-skills &lt;span class="nt"&gt;--list&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What Are Skills?
&lt;/h2&gt;

&lt;p&gt;If you've been using AI coding assistants, you've probably noticed they're pretty good at writing code, but sometimes they miss the nuances. They might suggest patterns that work but aren't optimal, or miss important details about how things should be done.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://agentskills.io/home" rel="noopener noreferrer"&gt;Agent Skills&lt;/a&gt; are a way to fix this. They're folders with instructions and guidance that AI assistants can load and use when helping you build. Unlike tools (which let AI do things) or prompts (which give specific instructions), skills provide domain expertise. The know-how.&lt;/p&gt;

&lt;p&gt;When an AI assistant has access to Mapbox skills, it knows things like "if you're adding 1000+ markers, you should use clustering" or "use the Search Box API for autocomplete, not the Geocoding API." It's the difference between an AI that knows how to code and one that knows how to code Mapbox apps specifically.&lt;/p&gt;

&lt;h2&gt;
  
  
  See It In Action
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmszl57sdlm1lpru87kcr.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmszl57sdlm1lpru87kcr.gif" alt="Store Locator App" width="760" height="405"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This production-ready store locator was built in minutes using Claude Code with Mapbox Agent Skills. The AI assistant handled marker patterns, distance calculations, search functionality, and responsive design automatically—following Mapbox best practices from the start.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Skills Are Important
&lt;/h2&gt;

&lt;p&gt;Here's the thing: Mapbox has a lot of surface area. We have SDKs for &lt;a href="https://docs.mapbox.com/mapbox-gl-js/guides/" rel="noopener noreferrer"&gt;web&lt;/a&gt;, &lt;a href="https://docs.mapbox.com/ios/maps/guides/" rel="noopener noreferrer"&gt;iOS&lt;/a&gt;, and &lt;a href="https://docs.mapbox.com/android/maps/guides/" rel="noopener noreferrer"&gt;Android&lt;/a&gt;. We have multiple search APIs with different use cases. We have performance patterns that matter at scale. We have migration paths from other platforms.&lt;/p&gt;

&lt;p&gt;When you're building with an AI assistant, you want it to know all this stuff. You don't want to spend time correcting it when it suggests using 1000 individual markers instead of clustering. You don't want it to miss that you should debounce search requests. You want it to just know.&lt;/p&gt;

&lt;p&gt;That's what these skills do. They encode the knowledge we've built up from working with thousands of developers building Mapbox applications. The patterns that work. The mistakes to avoid. The right tool for the job.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Inside the Box
&lt;/h2&gt;

&lt;p&gt;We've organized the skills into a few main areas. Here's what's available:&lt;/p&gt;

&lt;h3&gt;
  
  
  Platform Integration
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Web, iOS, and Android patterns&lt;/strong&gt; - These skills cover the fundamentals of integrating Mapbox into your app, whether you're building for the web with React, Vue, Svelte, or Angular, or going native with Swift/SwiftUI on iOS or Kotlin/Jetpack Compose on Android. They know about framework-specific patterns, lifecycle management, and platform best practices.&lt;/p&gt;

&lt;h3&gt;
  
  
  Performance
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Web performance patterns&lt;/strong&gt; - This one's huge. It covers everything from initialization strategies to handling thousands of markers, optimizing data loading, and managing memory. If you're building something that needs to perform at scale, this skill will save you a lot of debugging time.&lt;/p&gt;

&lt;h3&gt;
  
  
  Search
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Search integration and patterns&lt;/strong&gt; - We have two skills here. One walks through the complete workflow of adding search to your app (asking the right discovery questions, picking the right product, implementing it correctly). The other helps you choose between our different search tools and configure them properly. Between these two, your AI assistant will know exactly how to implement search for your use case.&lt;/p&gt;

&lt;h3&gt;
  
  
  Geospatial Operations
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Geometric calculations and routing&lt;/strong&gt; - This skill helps decide when to use offline geometric calculations (like measuring straight-line distance) versus when to use our routing APIs (like getting actual drive times). It's the "as the crow flies vs as the crow drives" decision framework, plus a ton of practical examples.&lt;/p&gt;

&lt;h3&gt;
  
  
  Design and Cartography
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Map design, styles, and quality&lt;/strong&gt; - Three skills covering cartographic principles (color theory, visual hierarchy, typography), common style patterns and layer configurations, and style validation and optimization. If you're doing custom map design, these will help you make maps that look professional and work well.&lt;/p&gt;

&lt;h3&gt;
  
  
  Migration
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Google Maps and MapLibre migration&lt;/strong&gt; - If you're moving from Google Maps Platform or MapLibre, these skills provide comprehensive migration guides with API equivalents and pattern translations. They know the gotchas and the breaking changes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Security
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Token security&lt;/strong&gt; - Best practices for handling access tokens securely across all platforms. This is one of those things that's easy to get wrong, so having your AI assistant know the right patterns from the start is valuable.&lt;/p&gt;

&lt;h3&gt;
  
  
  Common Patterns
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Store locators&lt;/strong&gt; - A focused skill on building location finders and store locators, with patterns for markers, filtering, distance calculations, and all the UX details that make these features work well.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;Installing skills is straightforward. If you're using Claude Code, Cursor, or another AI assistant that supports Agent Skills, you can add them all at once:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx skills add mapbox/mapbox-agent-skills
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or install specific skills if you only need certain ones:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx skills add mapbox/mapbox-agent-skills &lt;span class="nt"&gt;--skill&lt;/span&gt; mapbox-web-performance-patterns
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can list what's available:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx skills add mapbox/mapbox-agent-skills &lt;span class="nt"&gt;--list&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once installed, your AI assistant will automatically use these skills when you're working on Mapbox projects. You don't need to do anything special. Just start building and you'll notice better suggestions, fewer mistakes, and more production-ready code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pro tip:&lt;/strong&gt; These skills work great with our Mapbox MCP servers. We have the &lt;a href="https://github.com/mapbox/mcp-server" rel="noopener noreferrer"&gt;Mapbox MCP Server&lt;/a&gt; for core Mapbox operations and the &lt;a href="https://github.com/mapbox/mcp-devkit-server" rel="noopener noreferrer"&gt;Mapbox MCP DevKit Server&lt;/a&gt; for development workflows. Skills provide the expertise (how to design good maps, which patterns to use), while MCP tools provide the actions (create styles, generate tokens, preview maps). Together they make a pretty powerful development workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build Stuff and Let Us Know
&lt;/h2&gt;

&lt;p&gt;We built these skills based on what we've learned from years of working with developers building Mapbox applications. But they'll get better as more people use them and give us feedback.&lt;/p&gt;

&lt;p&gt;If you build something cool with these skills, or if you find gaps where your AI assistant could use more guidance, let us know. Open an issue on &lt;a href="https://github.com/mapbox/mapbox-agent-skills" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; or contribute a new skill if you've discovered patterns we should capture.&lt;/p&gt;

&lt;p&gt;We're excited to see what you build.&lt;/p&gt;

</description>
      <category>agents</category>
      <category>ai</category>
      <category>productivity</category>
      <category>tooling</category>
    </item>
    <item>
      <title>Unlocking Mapbox GL JS's Hidden Field of View Control</title>
      <dc:creator>Andrew Sepic</dc:creator>
      <pubDate>Tue, 10 Feb 2026 14:49:30 +0000</pubDate>
      <link>https://forem.com/mapbox/unlocking-mapbox-gl-jss-hidden-field-of-view-control-4h29</link>
      <guid>https://forem.com/mapbox/unlocking-mapbox-gl-jss-hidden-field-of-view-control-4h29</guid>
      <description>&lt;p&gt;Ever wished you could make your Mapbox map look more like a telephoto lens or a dramatic wide-angle shot? Turns out, there's an undocumented way to control the camera's field of view (FOV) in &lt;a href="https://docs.mapbox.com/mapbox-gl-js/" rel="noopener noreferrer"&gt;Mapbox GL JS&lt;/a&gt;—but with a big caveat: &lt;strong&gt;this is not a public API&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgllthmll1207hr4tsbos.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgllthmll1207hr4tsbos.jpeg" alt="Field of View A).64 radians B) 1.00 radians C) 1.5 radians"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;⚠️ &lt;strong&gt;Developer Warning&lt;/strong&gt;: What I'm about to show you involves accessing private internal properties. Use this only for experiments, prototypes, and creative projects. Don't rely on this in production—it could break in any future Mapbox version without notice.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem: Fixed Field of View
&lt;/h2&gt;

&lt;p&gt;By default, Mapbox GL JS uses a fixed vertical field of view of approximately &lt;strong&gt;36.87 degrees&lt;/strong&gt; (0.643 radians). This gives maps a consistent, neutral perspective. But what if you want to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create a telephoto effect (narrow FOV) for architectural visualization&lt;/li&gt;
&lt;li&gt;Achieve a dramatic wide-angle view (large FOV) for immersive experiences&lt;/li&gt;
&lt;li&gt;Dynamically adjust the visual perspective based on user interaction&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The official Mapbox API doesn't expose FOV control—but the internal &lt;code&gt;Transform&lt;/code&gt; class does.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is the Transform Class?
&lt;/h2&gt;

&lt;p&gt;Digging through the &lt;a href="https://github.com/mapbox/mapbox-gl-js/blob/main/src/geo/transform.ts" rel="noopener noreferrer"&gt;Mapbox GL JS source code&lt;/a&gt;, I discovered the &lt;code&gt;Transform&lt;/code&gt; class—the mathematical heart of Mapbox's rendering system. This 2,800+ line beast handles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Camera positioning and orientation&lt;/li&gt;
&lt;li&gt;Projection matrix calculations&lt;/li&gt;
&lt;li&gt;Coordinate system transformations (lat/lng ↔ screen pixels ↔ mercator)&lt;/li&gt;
&lt;li&gt;Zoom level calculations&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Field of view management&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;code&gt;Transform&lt;/code&gt; instance lives at &lt;code&gt;map.transform&lt;/code&gt; and contains a private &lt;code&gt;_fov&lt;/code&gt; property that controls the vertical field of view.&lt;/p&gt;

&lt;h2&gt;
  
  
  The (Unofficial) Solution
&lt;/h2&gt;

&lt;p&gt;Here's how you can control field of view with a custom function called &lt;code&gt;setFOV&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;setFOV&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fov&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Directly access the internal Transform instance&lt;/span&gt;
  &lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_fov&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;fov&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// Force recalculation of projection matrices&lt;/span&gt;
  &lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_calcMatrices&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="c1"&gt;// Trigger a map repaint&lt;/span&gt;
  &lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;triggerRepaint&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Initialize your map&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;map&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;mapboxgl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;container&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;map-container&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;center&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;71.06288&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;42.36834&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;zoom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;16.85&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;pitch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;75.67&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;bearing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;141.60&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;load&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Apply a custom FOV (in radians)&lt;/span&gt;
  &lt;span class="nf"&gt;setFOV&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.8&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Wide angle&lt;/span&gt;
  &lt;span class="c1"&gt;// setFOV(map, 0.4); // Telephoto&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  FOV Values Explained
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Radians&lt;/th&gt;
&lt;th&gt;FOV effect&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;0.1 - 0.5 radians&lt;/strong&gt; (~5-28°): Telephoto effect, compressed perspective&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fifbhky1vfb6us4cy64mv.png" alt=" "&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;0.643 radians&lt;/strong&gt; (~36.87°): Default Mapbox FOV&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxna7elnecedggscfn6ty.png" alt=" "&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;0.8 - 1.5 radians&lt;/strong&gt; (~45-86°): Wide angle, dramatic perspective&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe91k9gotg7etay0sc3lu.png" alt=" "&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  How It Works Under the Hood
&lt;/h2&gt;

&lt;p&gt;When you modify &lt;code&gt;_fov&lt;/code&gt;, three things need to happen:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Update the property&lt;/strong&gt;: &lt;code&gt;map.transform._fov = newValue&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Recalculate matrices&lt;/strong&gt;: &lt;code&gt;map.transform._calcMatrices()&lt;/code&gt; recomputes all the projection math including:

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;projMatrix&lt;/code&gt; - world to clip coordinates&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;pixelMatrix&lt;/code&gt; - world to screen pixels&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;cameraToCenterDistance&lt;/code&gt; - camera distance based on FOV&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trigger repaint&lt;/strong&gt;: &lt;code&gt;map.triggerRepaint()&lt;/code&gt; forces a redraw with new matrices&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The &lt;code&gt;_calcMatrices()&lt;/code&gt; method is where the magic happens. From the source:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cameraToCenterDistance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_fov&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; 
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_pixelsPerMercatorPixel&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This calculates how far the camera should be from the map center based on the FOV, ensuring the perspective projection renders correctly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Interactive Field of View Controller
&lt;/h2&gt;

&lt;p&gt;Want to let users adjust FOV dynamically? Here's a complete example with a slider you can check on on Codepen.&lt;/p&gt;

&lt;p&gt;

&lt;iframe height="600" src="https://codepen.io/AndrewSepic/embed/XJKyRwY?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;


&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Isn't This a Public API?
&lt;/h2&gt;

&lt;p&gt;You might wonder why Mapbox doesn't officially expose FOV control. A few reasons:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Complexity&lt;/strong&gt;: Changing FOV affects tile loading, symbol placement, and many other rendering systems&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Performance&lt;/strong&gt;: Non-standard FOV values can impact rendering performance&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Consistency&lt;/strong&gt;: A fixed FOV ensures consistent user experience across applications&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Maintenance&lt;/strong&gt;: Supporting arbitrary FOV would increase API surface area and testing burden&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The Risks
&lt;/h2&gt;

&lt;p&gt;Before you rush to implement this, understand the risks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;⚠️ &lt;strong&gt;Breaking changes&lt;/strong&gt;: Internal APIs can change without warning in minor/patch releases&lt;/li&gt;
&lt;li&gt;⚠️ &lt;strong&gt;Unsupported behavior&lt;/strong&gt;: You may encounter rendering artifacts or unexpected behavior&lt;/li&gt;
&lt;li&gt;⚠️ &lt;strong&gt;No guarantees&lt;/strong&gt;: Mapbox won't provide support for issues arising from this technique&lt;/li&gt;
&lt;li&gt;⚠️ &lt;strong&gt;Version locked&lt;/strong&gt;: You'll need to test across Mapbox versions if you update&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Alternative: The FreeCamera API
&lt;/h2&gt;

&lt;p&gt;For production use cases involving advanced camera control, check out Mapbox's official &lt;a href="https://docs.mapbox.com/mapbox-gl-js/api/properties/#freecameraoptions" rel="noopener noreferrer"&gt;FreeCamera API&lt;/a&gt;. While it doesn't expose FOV directly, it provides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Full camera position control&lt;/li&gt;
&lt;li&gt;Orientation adjustments&lt;/li&gt;
&lt;li&gt;Supported and stable API&lt;/li&gt;
&lt;li&gt;Works with all Mapbox features&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Exploring internal APIs like &lt;code&gt;Transform&lt;/code&gt; is a fantastic way to understand how sophisticated mapping libraries work under the hood. The FOV control technique shown here opens creative possibilities for enthusiasts and experimental projects.&lt;/p&gt;

&lt;p&gt;Just remember: &lt;strong&gt;with great power comes great responsibility&lt;/strong&gt;. Use internal APIs wisely, always have a fallback, and consider this a learning exercise rather than a production strategy.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Tested with Mapbox GL JS v3.18.1. Your mileage may vary with other versions.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Additional Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/mapbox/mapbox-gl-js" rel="noopener noreferrer"&gt;Mapbox GL JS GitHub Repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.mapbox.com/mapbox-gl-js" rel="noopener noreferrer"&gt;Mapbox GL JS Documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>frontend</category>
      <category>javascript</category>
      <category>tutorial</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Mapbox Demo: Bike route visualization color gradient and data scrubbing UX</title>
      <dc:creator>Chris Whong</dc:creator>
      <pubDate>Mon, 09 Feb 2026 18:45:07 +0000</pubDate>
      <link>https://forem.com/mapbox/mapbox-demo-bike-route-visualization-color-gradient-and-data-scrubbing-ux-9kb</link>
      <guid>https://forem.com/mapbox/mapbox-demo-bike-route-visualization-color-gradient-and-data-scrubbing-ux-9kb</guid>
      <description>&lt;p&gt;A developer on Reddit recently asked for &lt;a href="https://www.reddit.com/r/mapbox/comments/1qwg1ok/performance_advice_for_stravalike_ride_playback/" rel="noopener noreferrer"&gt;help building a ride playback feature&lt;/a&gt; with speed visualization on a Mapbox GL JS map. They had detailed GPS and speed data from a bike ride and wanted to show speed variations along the route. &lt;/p&gt;

&lt;h2&gt;
  
  
  The Challenge
&lt;/h2&gt;

&lt;p&gt;The original question centered on two main requirements:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Visualize speed variations&lt;/strong&gt; along the route with color-coded gradients (slow = green, medium = yellow, fast = red)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Interactive scrubbing&lt;/strong&gt; that shows detailed speed and time data when the user hovers over any point on the route&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The Solution
&lt;/h2&gt;

&lt;p&gt;Here's the working implementation on codepen. The line visualizes the variations in speed along the bike trip. Move your mouse over the line to "scrub" and see data for a specific spot on the line.&lt;/p&gt;

&lt;p&gt;

&lt;iframe height="600" src="https://codepen.io/Chris-Whong/embed/RNRBBPg?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;


&lt;/p&gt;

&lt;h3&gt;
  
  
  Key Implementation Details
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Speed Gradient&lt;/strong&gt;&lt;br&gt;
Instead of creating separate line segments for each speed zone, we use a single GeoJSON LineString with &lt;code&gt;lineMetrics: true&lt;/code&gt; and build a dynamic gradient using the &lt;code&gt;line-gradient&lt;/code&gt; paint property:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;line-gradient&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;interpolate&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;linear&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;line-progress&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;gradientStops&lt;/span&gt;  &lt;span class="c1"&gt;// Dynamically generated from speed data&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each speed data point gets mapped to a position along the line (0 to 1) and assigned a color based on its speed value. Check out the &lt;a href="https://docs.mapbox.com/mapbox-gl-js/example/line-gradient/" rel="noopener noreferrer"&gt;official line-gradient example&lt;/a&gt; for more details on this technique.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A Note on Accuracy&lt;/strong&gt;&lt;br&gt;
This demo simplifies the gradient calculation by evenly distributing speed data points along the line based on timestamps. In a production app, you'd want to map each speed measurement to its actual GPS coordinate along the route for precise gradient placement. This ensures the gradient accurately reflects where speed changes occurred geographically, not just temporally.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Interactive Scrubbing&lt;/strong&gt;&lt;br&gt;
The scrubber uses geometry calculations to find the nearest point on the route as you move your mouse, then interpolates between speed data points to show accurate values at that location. A pink circle marker follows your cursor along the route, displaying the time and speed in a floating tooltip.&lt;/p&gt;

&lt;h2&gt;
  
  
  Performance Benefits
&lt;/h2&gt;

&lt;p&gt;This approach renders a single line layer with a gradient instead of dozens of individual geometries, resulting in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Faster initial render&lt;/li&gt;
&lt;li&gt;Smooth interactions&lt;/li&gt;
&lt;li&gt;Lower memory usage&lt;/li&gt;
&lt;li&gt;Simpler state management&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Cross-Platform Compatibility
&lt;/h2&gt;

&lt;p&gt;While this example uses Mapbox GL JS for the web, the same &lt;code&gt;line-gradient&lt;/code&gt; technique works on &lt;a href="https://docs.mapbox.com/ios/maps/guides/" rel="noopener noreferrer"&gt;iOS&lt;/a&gt; and &lt;a href="https://docs.mapbox.com/android/maps/guides/" rel="noopener noreferrer"&gt;Android&lt;/a&gt; using the native Maps SDKs. The gradient expression syntax is consistent across all platforms.&lt;/p&gt;

&lt;p&gt;Check out the full code in the CodePen above to see how the speed interpolation and distance calculations work. Happy mapping!&lt;/p&gt;

</description>
      <category>mapbox</category>
      <category>dataviz</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Wix X Mapbox</title>
      <dc:creator>Chris Whong</dc:creator>
      <pubDate>Wed, 04 Feb 2026 17:43:31 +0000</pubDate>
      <link>https://forem.com/mapbox/mapbox-x-wix-44j5</link>
      <guid>https://forem.com/mapbox/mapbox-x-wix-44j5</guid>
      <description>&lt;h1&gt;
  
  
  How to Add an Interactive Mapbox Map to Your Wix Website
&lt;/h1&gt;

&lt;p&gt;This guide shows developers how to integrate &lt;a href="https://docs.mapbox.com/mapbox-gl-js" rel="noopener noreferrer"&gt;Mapbox GL JS&lt;/a&gt; into a &lt;a href="https://wix.com" rel="noopener noreferrer"&gt;Wix&lt;/a&gt; website using the Wix editor's embed code feature. If you're already familiar with Mapbox GL JS development and want to add custom interactive maps to your Wix site, this post will show you exactly where to place your code to get it working.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;✅ What this guide covers:&lt;/strong&gt; The technical steps to embed Mapbox GL JS code in a Wix website &lt;br&gt;
&lt;strong&gt;❌ What this guide doesn't cover:&lt;/strong&gt; Details of interactive map development with Mapbox GL JS (the &lt;a href="https://docs.mapbox.com/mapbox-gl-js/exmaples" rel="noopener noreferrer"&gt;examples&lt;/a&gt; are a great place to start for inspiration and working code snippets)&lt;/p&gt;

&lt;p&gt;We'll show a &lt;strong&gt;simple example&lt;/strong&gt; of displaying a business location with a marker, popup, and rotating camera to demonstrate the integration. Once you understand where the code goes and how it's structured in Wix, you can implement any Mapbox GL JS functionality you need by changing the JavaScript code.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx08aynzhpi9s7mimtoay.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx08aynzhpi9s7mimtoay.gif" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here's a codepen so you can get a better look at the actual map and animation shown in this guide. It uses the &lt;a href="https://docs.mapbox.com/map-styles/standard/guides/" rel="noopener noreferrer"&gt;Mapbox Standard Style&lt;/a&gt; as a basemap, showing detailed 3D buildings and an optional monochrome theme. The popup is styled to match the green colors used on the Wix site it is embedded in:&lt;/p&gt;

&lt;p&gt;

&lt;iframe height="600" src="https://codepen.io/Chris-Whong/embed/NPrBPeb?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;


&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; This technique of adding custom HTML, CSS and JavaScript via the Wix editor is suitable for simpler Mapbox GL JS implementations but does not include version control or other benefits of a separate development workflow. If your map becomes more complex, you may want to deploy it as a standalone web app, then embed it in your Wix site using its URL. &lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;To follow this guide, you'll need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;A Wix website&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Working knowledge of Mapbox GL JS&lt;/strong&gt; - you should be comfortable writing HTML, CSS, and JavaScript to create maps&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A Mapbox account and access token&lt;/strong&gt; - if you don't have one yet, &lt;a href="https://account.mapbox.com" rel="noopener noreferrer"&gt;sign up here&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Getting Your Mapbox Access Token
&lt;/h2&gt;

&lt;p&gt;Before we start, you'll need a &lt;strong&gt;Mapbox access token&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to &lt;a href="https://account.mapbox.com" rel="noopener noreferrer"&gt;mapbox.com&lt;/a&gt; and sign up for a free account (if you don't have one)&lt;/li&gt;
&lt;li&gt;Once logged in, navigate to your &lt;a href="https://console.mapbox.com" rel="noopener noreferrer"&gt;console&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Find the "Tokens" link in the left sidebar to go to the &lt;a href="https://console.mapbox.com/account/access-tokens/" rel="noopener noreferrer"&gt;tokens page&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Copy your &lt;strong&gt;default public token&lt;/strong&gt; (it starts with &lt;code&gt;pk.&lt;/code&gt;)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Keep this token handy—you'll need it in a moment.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Wix Embeds Work
&lt;/h2&gt;

&lt;p&gt;Before we dive in, it's important to understand what's happening under the hood. When you add an embed element in the Wix editor, it becomes an &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/iframe" rel="noopener noreferrer"&gt;&lt;code&gt;&amp;lt;iframe&amp;gt;&lt;/code&gt;&lt;/a&gt; in your published site. This iframe contains whatever HTML, CSS, and JavaScript you add to it—completely isolated from the rest of your Wix page.&lt;/p&gt;

&lt;p&gt;This isolation is actually a benefit: you have complete control over the content inside the iframe without worrying about conflicts with Wix's own code. You can implement any Mapbox GL JS functionality you want, exactly as you would in a standalone HTML page.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Adding the Embed Element
&lt;/h2&gt;

&lt;p&gt;Let's add the embed element that will contain your map code:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open your &lt;a href="https://editor.wix.com/" rel="noopener noreferrer"&gt;Wix Editor&lt;/a&gt; and navigate to the page where you want to add the map&lt;/li&gt;
&lt;li&gt;Click the "+ Add" button (in the top left) to add an element&lt;/li&gt;
&lt;li&gt;Scroll through the different elements and select "Embed"
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb7ihd14cpf0v2zjrwwha.png" alt=" "&gt;
&lt;/li&gt;
&lt;li&gt;Choose "Embed Code" on the next panel
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fue3opef0ojes7uow8cwy.png" alt=" "&gt;
&lt;/li&gt;
&lt;li&gt;Drag and resize the embed element to your desired size on the page
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvgn1rubtp944mp10nain.gif" alt=" "&gt;
&lt;/li&gt;
&lt;li&gt;Double click the embed element to open its Settings panel&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7aqp0mrkm4h7rvdpnj13.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7aqp0mrkm4h7rvdpnj13.png" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Add Mapbox GL JS Code
&lt;/h2&gt;

&lt;p&gt;Now you'll add Mapbox GL JS code to get a map working in the embed. The embed accepts any HTML, so you can include CSS and JavaScript by using &lt;code&gt;&amp;lt;style&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tags.&lt;/p&gt;

&lt;p&gt;Here's a working example that shows a business location with a marker and rotating camera. Copy this code and replace &lt;code&gt;YOUR_MAPBOX_ACCESS_TOKEN&lt;/code&gt; with your actual access token:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- import Mapbox GL JS and its CSS file --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;'https://api.mapbox.com/mapbox-gl-js/v3.18.1/mapbox-gl.js'&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;'https://api.mapbox.com/mapbox-gl-js/v3.18.1/mapbox-gl.css'&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;'stylesheet'&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- override default styles for the map and popup --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;style&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;body&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nc"&gt;.mapboxgl-popup-content&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#e6f2d2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#2f5f48&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nc"&gt;.mapboxgl-popup-tip&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;border-top-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#e6f2d2&lt;/span&gt; &lt;span class="cp"&gt;!important&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/style&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- add a container element for the map --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"map-container"&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"position: absolute; height: 100%; width: 100%"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
  &lt;span class="c1"&gt;// your Mapbox access token&lt;/span&gt;
  &lt;span class="nx"&gt;mapboxgl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;accessToken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;YOUR_MAPBOX_ACCESS_TOKEN&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// initialize the map&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;map&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;mapboxgl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;container&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;map-container&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// container ID&lt;/span&gt;
    &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mapbox://styles/mapbox/standard&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// style URL&lt;/span&gt;
    &lt;span class="na"&gt;center&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;73.99986&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;40.75300&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="c1"&gt;// starting position [lng, lat]&lt;/span&gt;
    &lt;span class="na"&gt;zoom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;15.36&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// starting zoom&lt;/span&gt;
    &lt;span class="na"&gt;bearing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;28.00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// bearing in degrees&lt;/span&gt;
    &lt;span class="na"&gt;pitch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;56.50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// pitch in degrees&lt;/span&gt;
    &lt;span class="na"&gt;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;basemap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;monochrome&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// use the monochrome basemap theme&lt;/span&gt;
        &lt;span class="na"&gt;showPointOfInterestLabels&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="c1"&gt;// hide point of interest labels&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="c1"&gt;// disable scroll zoom so the map won't zoom as the user scrolls down the page&lt;/span&gt;
  &lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scrollZoom&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;disable&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="c1"&gt;// create a popup with address details and a link to get directions on Google Maps&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;popup&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;mapboxgl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Popup&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;closeOnClick&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;closeButton&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nx"&gt;setHTML&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;`&amp;lt;div&amp;gt;
      450 W 33rd St&amp;lt;br/&amp;gt;
      New York, NY 10001&amp;lt;/div&amp;gt;
      &amp;lt;a href="https://maps.app.goo.gl/6WzErvMBMi2Hn64Z7" target="_blank" rel="noopener noreferrer"&amp;gt;&amp;lt;span class="text-xs"&amp;gt;Get Directions&amp;lt;/span&amp;gt;&amp;lt;/a&amp;gt;
    `&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// create a map marker with a custom color, connect the popup, add it to the map, and open the popup&lt;/span&gt;
  &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;mapboxgl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Marker&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#2f5f48&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setLngLat&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;73.99986&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;40.75300&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setPopup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;popup&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;togglePopup&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="c1"&gt;// this function rotates the camera continuously&lt;/span&gt;
  &lt;span class="c1"&gt;// see https://docs.mapbox.com/mapbox-gl-js/example/animate-camera-around-point/&lt;/span&gt;
  &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;rotateCamera&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rotateTo&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;timestamp&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;360&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="nf"&gt;requestAnimationFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rotateCamera&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;load&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// start the camera animation.&lt;/span&gt;
    &lt;span class="nf"&gt;rotateCamera&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Delete the existing code in the textarea labeled &lt;strong&gt;Enter HTML embed code&lt;/strong&gt; and paste the Mapbox GL JS snippet from above:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn9akdgn51zsuya5dr9nl.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn9akdgn51zsuya5dr9nl.gif" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Important&lt;/strong&gt;: Replace the placeholder text &lt;code&gt;YOUR_MAPBOX_ACCESS_TOKEN&lt;/code&gt; with the actual token you copied from your Mapbox account.&lt;/p&gt;

&lt;p&gt;Click "Apply" to save your code, then preview your site. You should see an interactive map with a monochrome basemap, a marker centered over New York City, and a slowly rotating camera.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx08aynzhpi9s7mimtoay.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx08aynzhpi9s7mimtoay.gif" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Understanding the Code Structure
&lt;/h2&gt;

&lt;p&gt;Here's how the code for this map breaks down:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Library Imports&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;'https://api.mapbox.com/mapbox-gl-js/v3.18.1/mapbox-gl.js'&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;'https://api.mapbox.com/mapbox-gl-js/v3.18.1/mapbox-gl.css'&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;'stylesheet'&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These load Mapbox GL JS and its required stylesheet from the Mapbox CDN.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Custom Styles&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;style&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;body&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nc"&gt;.mapboxgl-popup-content&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#e6f2d2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#2f5f48&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/style&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;body { margin: 0 }&lt;/code&gt; removes default margins so the map fills the entire iframe. The popup styles customize the appearance to match the page theme.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Map Container&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"map-container"&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"position: absolute; height: 100%; width: 100%"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This div is where the map renders. The inline styles ensure it fills the available space.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Your Map Logic&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
  &lt;span class="c1"&gt;// All your Mapbox GL JS code goes here&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is where you implement your map functionality. In this example, we're:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Setting the access token&lt;/li&gt;
&lt;li&gt;Initializing the map with specific style and camera settings&lt;/li&gt;
&lt;li&gt;Disabling scroll zoom (important for Wix pages!)&lt;/li&gt;
&lt;li&gt;Creating a popup and marker&lt;/li&gt;
&lt;li&gt;Adding a camera rotation animation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can replace or extend this code with any Mapbox GL JS functionality you need. &lt;/p&gt;

&lt;p&gt;If you want to adapt this example of a single marker and popup, you should:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Change the map's center point, zoom, pitch and bearing to your desired initial camera view.&lt;/li&gt;
&lt;li&gt;Change the Marker's coordinates&lt;/li&gt;
&lt;li&gt;Change the Popup's content by editing the HTML code in &lt;code&gt;Popup.setHTML()&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Or, you can leave the imports and the map container in place and start over with the map JavaScript code and build whatever you need. See additional developer resources below.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resources for Mapbox GL JS Development
&lt;/h2&gt;

&lt;p&gt;Here are essential resources for building your map functionality:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mapbox Documentation&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://docs.mapbox.com/mapbox-gl-js/example/" rel="noopener noreferrer"&gt;Mapbox GL JS Examples&lt;/a&gt; - Dozens of live, interactive code examples&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.mapbox.com/mapbox-gl-js/" rel="noopener noreferrer"&gt;Mapbox GL JS Guides&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.mapbox.com/mapbox-gl-js/api/" rel="noopener noreferrer"&gt;Mapbox GL JS API Reference&lt;/a&gt; - Complete documentation of all map methods and options&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Key Examples&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://docs.mapbox.com/mapbox-gl-js/example/simple-map/" rel="noopener noreferrer"&gt;Display a map&lt;/a&gt; - Basic map initialization&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.mapbox.com/mapbox-gl-js/example/add-a-marker/" rel="noopener noreferrer"&gt;Add markers to a map&lt;/a&gt; - Working with markers&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.mapbox.com/mapbox-gl-js/example/popup/" rel="noopener noreferrer"&gt;Display a popup&lt;/a&gt; - Popup functionality&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.mapbox.com/mapbox-gl-js/example/geojson-markers/" rel="noopener noreferrer"&gt;Add GeoJSON data&lt;/a&gt; - Display custom data&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Community&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://reddit.com/r/mapbox" rel="noopener noreferrer"&gt;r/mapbox on Reddit&lt;/a&gt; - Get help and share ideas&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://discord.com/invite/mapboxdevs-1004826913229000704" rel="noopener noreferrer"&gt;Mapbox Developer Discord&lt;/a&gt; - Post in the &lt;code&gt;#web&lt;/code&gt; channel to ask questions and share what you build.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Troubleshooting
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Map not displaying?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Verify you've replaced &lt;code&gt;YOUR_MAPBOX_ACCESS_TOKEN&lt;/code&gt; with your actual token&lt;/li&gt;
&lt;li&gt;Check that your access token has the correct permissions&lt;/li&gt;
&lt;li&gt;Ensure the embed element has sufficient height and width in the Wix editor &lt;strong&gt;and&lt;/strong&gt; that the map container div also has sufficient height and width&lt;/li&gt;
&lt;li&gt;Open your browser's developer console to check for JavaScript errors&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;By following the steps in this guide, you should now have a Mapbox GL JS map working in your Wix website and are ready to iterate the JavaScript code to customize the map to meet your needs.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Questions about this integration approach? Found a better way to do something? Share your experience in the comments!&lt;/em&gt;&lt;/p&gt;

</description>
      <category>wix</category>
      <category>mapbox</category>
      <category>webmap</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Server-rendered Maps for Web and Mobile</title>
      <dc:creator>Chris Whong</dc:creator>
      <pubDate>Thu, 29 Jan 2026 16:42:21 +0000</pubDate>
      <link>https://forem.com/mapbox/server-rendered-maps-for-web-and-mobile-51f7</link>
      <guid>https://forem.com/mapbox/server-rendered-maps-for-web-and-mobile-51f7</guid>
      <description>&lt;h2&gt;
  
  
  A Developer's Guide to the Mapbox Static Images API
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.mapbox.com/maps" rel="noopener noreferrer"&gt;Interactive maps&lt;/a&gt; are a powerful user experience element — they let users pan, zoom, and explore wide geographic areas or interact with dense datasets. But there's a catch: &lt;em&gt;not every use case needs the full weight of a client-rendered map.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Rendering vector maps in the client requires loading &lt;strong&gt;all the map data&lt;/strong&gt; into the browser or device, which means significant network traffic, device processing overhead, and performance costs.&lt;/p&gt;

&lt;p&gt;Sometimes you just need &lt;strong&gt;a picture of a map&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fstsls686nqpuq1evfiir.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fstsls686nqpuq1evfiir.png" alt="A static map image of Hyde Park in London, England" width="600" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If users are quickly scrolling past location information, or if you're displaying a long list where every item needs a map thumbnail, it doesn't make sense to bring in a vector map library or SDK and load lots of client-rendered data just to show a marker or two. &lt;/p&gt;

&lt;p&gt;That's where the &lt;a href="https://docs.mapbox.com/api/maps/static-images/" rel="noopener noreferrer"&gt;Mapbox Static Images API&lt;/a&gt; comes in.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is the Static Images API?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://docs.mapbox.com/api/maps/static-images" rel="noopener noreferrer"&gt;The Mapbox Static Images API&lt;/a&gt; is an HTTP endpoint that returns map images. All you need is a center point (latitude and longitude) and a zoom level (0 is zoomed out to world level, 18 or so gets you to street level). This will serve up a beautiful map image from anywhere in the world — no JavaScript libraries, no SDK packages,  no client-side rendering, just a clean image URL.&lt;/p&gt;

&lt;p&gt;To help you get an understanding of the URL structure for a call to the Static Images API, here are some map images of landmarks and their associated Static Images API URLs. You can try the URLs yourself, just replace &lt;code&gt;YOUR_MAPBOX_ACCESS_TOKEN&lt;/code&gt; with an access token from &lt;a href="https://account.mapbox.com" rel="noopener noreferrer"&gt;your Mapbox account&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Eiffel Tower&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The site of the Eiffel Tower in Paris (2.295,48.8584), France, using the &lt;a href="https://docs.mapbox.com/api/maps/styles/#classic-mapbox-styles" rel="noopener noreferrer"&gt;Mapbox Streets&lt;/a&gt; style at zoom &lt;code&gt;12.77&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq4c7tl5kkp12dyqxqh0f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq4c7tl5kkp12dyqxqh0f.png" alt="a static map image showing the Eiffel Tower site in Paris, France" width="800" height="600"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://api.mapbox.com/styles/v1/mapbox/streets-v12/static/2.295,48.8584,12.77,0/400x300@2x?access_token=YOUR_MAPBOX_ACCESS_TOKEN
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The site of the Golden Gate Bridge (-122.4717,37.8177) in San Francisco, California, USA, using the &lt;a href="https://docs.mapbox.com/api/maps/styles/#classic-mapbox-styles" rel="noopener noreferrer"&gt;Mapbox Dark&lt;/a&gt; style at zoom &lt;code&gt;10.56&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4qldnfp18ob2527o5jb3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4qldnfp18ob2527o5jb3.png" alt="a static map image showing the golden gate bridge site in San Francisco, California, USA" width="800" height="600"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://api.mapbox.com/styles/v1/mapbox/dark-v11/static/-122.4717,37.8177,10.56,0/400x300@2x?access_token=YOUR_MAPBOX_ACCESS_TOKEN
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The site of the Sydney Opera House in Sydney, Australia, using the &lt;a href="https://docs.mapbox.com/api/maps/styles/#classic-mapbox-styles" rel="noopener noreferrer"&gt;Mapbox Satellite Streets&lt;/a&gt; style at zoom &lt;code&gt;13.63&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5x16yv3y97qc5pzr418n.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5x16yv3y97qc5pzr418n.jpeg" alt="a static map image showing the site of the Sydney Opera House in Sydney, Australia" width="800" height="600"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://api.mapbox.com/styles/v1/mapbox/satellite-streets-v12/static/151.215,-33.858,13.63,0/400x300@2x?access_token=YOUR_MAPBOX_ACCESS_TOKEN
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see, the syntax for the URL structure is straightforward. Each of these images is 800x600 pixels (the request shows &lt;code&gt;400x300@2x&lt;/code&gt; for retina images), and include the longitude, latitude, zoom level, and style id.&lt;/p&gt;

&lt;p&gt;You can copy the URLs, add your own Mapbox access token and change out the longitude and latitude coordinates to get a map image for an area of interest to you. If you need to quickly get coordinates for a place, try our handy &lt;a href="https://labs.mapbox.com/location-helper/#3/40.78/-73.97" rel="noopener noreferrer"&gt;Location Helper&lt;/a&gt; tool.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding Your Data with Overlays
&lt;/h2&gt;

&lt;p&gt;It's rare that you would ever want &lt;strong&gt;just a basemap&lt;/strong&gt;. The real power of the Static Images API comes from its overlays feature. Overlays let you add your own data — &lt;strong&gt;markers, lines, and polygons&lt;/strong&gt; — directly onto the map.&lt;/p&gt;

&lt;p&gt;The overlay system is completely flexible: you can use simple longitude and latitude coordinates, or bring in encoded linestrings or GeoJSON for more complex geometries. This makes it trivial to combine Mapbox's professional basemaps with your application's location data.&lt;/p&gt;

&lt;p&gt;Overlays use a DSL and end up as sections of the URL. For example, a small (&lt;code&gt;s&lt;/code&gt;) red (&lt;code&gt;#b44646&lt;/code&gt;) marker at coordinates &lt;code&gt;-0.1717,51.5068&lt;/code&gt; is encoded as &lt;code&gt;pin-s+b44646(-0.1717,51.5068)&lt;/code&gt;. Don't worry about the learning curve for encoding overlays, we made a &lt;a href="https://docs.mapbox.com/playground/static" rel="noopener noreferrer"&gt;Developer Playground&lt;/a&gt; to help you configure them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three Real-World Use Cases
&lt;/h2&gt;

&lt;p&gt;Below are three practical examples that showcase how to combine Mapbox basemaps with overlays from the Static Images API.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Directions Route with Origin and Destination Markers&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A common use cases in location apps is showing directions and travel times from point A to point B. Whether you're building a ride-sharing app, a delivery service, or a travel planner, you need to visualize routes clearly.&lt;/p&gt;

&lt;p&gt;With the Static Images API, you can overlay a route &lt;a href="https://developers.google.com/maps/documentation/utilities/polylinealgorithm" rel="noopener noreferrer"&gt;polyline&lt;/a&gt; along with markers for the origin and destination. This gives users an instant visual understanding of the journey without requiring them to interact with a full map.&lt;/p&gt;

&lt;p&gt;Example:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9g8w0w9qyt2mco2pgzqj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9g8w0w9qyt2mco2pgzqj.png" alt="a static map image showing a directions route overlay and two markers for the origin and destination" width="800" height="533"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://api.mapbox.com/styles/v1/mapbox/streets-v12/static/path-7+6d8afd-0.8(ycmlFdweuM%3FglAsF%40B_CirAi%7CGkHyfG%7ByCspD%7B%7DGyfCyrAgsCenHcoCgbDylBiaHgkJ_lDabB_fGszFuxDqeJ%7BfCw%5Bq%7C%40%7BcAwcEczA_Aa%7D%40aR%5C%5C%3F),pin-l+51bd5e(-77.0393863890253,38.90253),pin-l+f54747(-76.612299,39.289643)/-76.7636,39.1125,8.45,0/600x400@2x?attribution=true&amp;amp;logo=true&amp;amp;access_token=YOUR_MAPBOX_ACCESS_TOKEN
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Use an &lt;a href="https://developers.google.com/maps/documentation/utilities/polylineutility" rel="noopener noreferrer"&gt;encoded polyline&lt;/a&gt; overlay for the route geometry (The &lt;a href="https://docs.mapbox.com/api/navigation/directions/" rel="noopener noreferrer"&gt;Mapbox Directions API&lt;/a&gt; can return this format, that's where the geometry in this example came from)&lt;/li&gt;
&lt;li&gt;Add marker overlays for origin and destination with different colors&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;2. Property Listing with 15-Minute Accessibility Isochrone&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Real estate and rental platforms need to communicate location value effectively. Showing a home's location is helpful, but showing what's accessible from that location is even better.&lt;/p&gt;

&lt;p&gt;An isochrone is a polygon that represents all the areas reachable from a point within a certain time frame. (&lt;a href="https://docs.mapbox.com/api/navigation/isochrone" rel="noopener noreferrer"&gt;We have an API&lt;/a&gt; for that too!) For a property listing, overlaying a 15-minute isochrone shows potential buyers or renters exactly how far they can get by walking, biking, or driving — highlighting the home's convenience and accessibility.&lt;/p&gt;

&lt;p&gt;This approach could also show the delivery area for a business or any other relevant polygon like a school district.&lt;/p&gt;

&lt;p&gt;Example:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu6rvcw17j8mxq996f0di.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu6rvcw17j8mxq996f0di.png" alt="a static map image showing a marker and a 10-minute travel time isochrone" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;https://api.mapbox.com/styles/v1/mapbox/dark-v11/static/geojson({"type":"FeatureCollection","features":[{"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[-76.508577,42.514225],[-76.509957,42.513128],[-76.509123,42.508674],[-76.51144,42.504265],[-76.513577,42.502387],[-76.510127,42.500128],[-76.511751,42.498128],[-76.512271,42.488128],[-76.511283,42.487128],[-76.511306,42.483128],[-76.510324,42.480128],[-76.504437,42.472268],[-76.504167,42.470538],[-76.50139,42.467128],[-76.501189,42.464516],[-76.505282,42.461833],[-76.507577,42.46181],[-76.508577,42.459468],[-76.503577,42.45937],[-76.505579,42.457126],[-76.505802,42.454903],[-76.507577,42.453848],[-76.515345,42.45436],[-76.51784,42.452865],[-76.513262,42.450444],[-76.515998,42.448128],[-76.519319,42.453386],[-76.521577,42.455901],[-76.525577,42.452965],[-76.523118,42.451587],[-76.522831,42.449128],[-76.520577,42.449318],[-76.517225,42.445128],[-76.521577,42.443245],[-76.521074,42.438128],[-76.524371,42.436128],[-76.521759,42.434128],[-76.514694,42.434245],[-76.511158,42.433548],[-76.510105,42.430128],[-76.505996,42.428128],[-76.502577,42.428537],[-76.500033,42.432584],[-76.498334,42.429128],[-76.500068,42.428619],[-76.500394,42.425945],[-76.503844,42.423395],[-76.503226,42.418128],[-76.505035,42.417128],[-76.505053,42.412128],[-76.50193,42.410481],[-76.501943,42.417128],[-76.499577,42.418592],[-76.497577,42.417807],[-76.495368,42.419919],[-76.497856,42.421407],[-76.494862,42.424128],[-76.489532,42.425173],[-76.485245,42.42246],[-76.483325,42.418128],[-76.480532,42.417083],[-76.482577,42.421654],[-76.484577,42.423622],[-76.482577,42.426795],[-76.485577,42.428684],[-76.486205,42.4315],[-76.476577,42.43128],[-76.475212,42.428493],[-76.470462,42.427243],[-76.468432,42.425273],[-76.469064,42.422641],[-76.464577,42.42142],[-76.462577,42.422323],[-76.460151,42.419554],[-76.456427,42.418278],[-76.455088,42.416617],[-76.451321,42.415128],[-76.455346,42.410897],[-76.459955,42.408506],[-76.458864,42.407414],[-76.455822,42.409373],[-76.451921,42.410785],[-76.449577,42.412473],[-76.443373,42.411332],[-76.437577,42.4076],[-76.436152,42.409554],[-76.439795,42.41091],[-76.447756,42.414949],[-76.453774,42.420325],[-76.448577,42.420775],[-76.448577,42.426175],[-76.453577,42.426839],[-76.456253,42.429452],[-76.461745,42.43196],[-76.461577,42.434379],[-76.457577,42.434404],[-76.4523,42.432406],[-76.446577,42.432548],[-76.450577,42.434781],[-76.455577,42.434836],[-76.458759,42.435946],[-76.458898,42.438128],[-76.461577,42.438809],[-76.466099,42.442606],[-76.464577,42.444003],[-76.458577,42.443598],[-76.454577,42.44578],[-76.452594,42.448111],[-76.450577,42.448707],[-76.446577,42.45155],[-76.440577,42.452472],[-76.438577,42.454801],[-76.442577,42.454598],[-76.446577,42.452706],[-76.449577,42.452709],[-76.450577,42.45048],[-76.45896,42.449511],[-76.466132,42.452128],[-76.46527,42.455128],[-76.473577,42.457868],[-76.475924,42.463128],[-76.476577,42.466596],[-76.471577,42.466572],[-76.467379,42.46493],[-76.464225,42.46748],[-76.465716,42.469267],[-76.468577,42.469357],[-76.469577,42.471132],[-76.472577,42.46976],[-76.475128,42.470128],[-76.475922,42.472473],[-76.474547,42.475128],[-76.477756,42.477128],[-76.477876,42.480128],[-76.474577,42.481343],[-76.468331,42.481374],[-76.469151,42.478702],[-76.467634,42.475071],[-76.4651,42.474128],[-76.465577,42.480314],[-76.461577,42.482593],[-76.455577,42.482704],[-76.454577,42.481743],[-76.450052,42.482128],[-76.454577,42.484442],[-76.462577,42.483733],[-76.463577,42.482681],[-76.468577,42.483644],[-76.476577,42.483066],[-76.481577,42.481804],[-76.486278,42.482829],[-76.491577,42.480834],[-76.495577,42.481012],[-76.493917,42.483128],[-76.500868,42.483837],[-76.502166,42.487128],[-76.504577,42.48936],[-76.506515,42.489128],[-76.505577,42.484372],[-76.503577,42.48469],[-76.502328,42.481879],[-76.504875,42.481426],[-76.504915,42.478128],[-76.508832,42.481128],[-76.50985,42.484128],[-76.509782,42.488128],[-76.510887,42.489128],[-76.510926,42.493128],[-76.509491,42.493042],[-76.508864,42.498841],[-76.506173,42.500128],[-76.509066,42.502128],[-76.509133,42.505128],[-76.507936,42.508128],[-76.508577,42.514225]]]},"properties":{"fill-opacity":0.33,"opacity":0.33,"fill":"%237dcff5","fillOpacity":0.33,"color":"%23bf4040","contour":10,"metric":"time"}}]}),pin-l-home+744ce1(-76.48957711003335,42.44212800096554)/-76.4821,42.4615,10.98,0/600x400@2x?attribution=true&amp;amp;logo=true&amp;amp;before_layer=admin-0-boundary-bg&amp;amp;access_token=YOUR_MAPBOX_ACCESS_TOKEN&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Generate the isochrone polygon using the &lt;a href="https://docs.mapbox.com/api/navigation/isochrone/" rel="noopener noreferrer"&gt;Mapbox Isochrone API&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Add it as a GeoJSON polygon overlay with a semi-transparent fill&lt;/li&gt;
&lt;li&gt;Place a marker overlay at the property location to clearly identify the starting point&lt;/li&gt;
&lt;li&gt;Use the &lt;code&gt;before_layer&lt;/code&gt; parameter to position overlay underneath the map labels&lt;/li&gt;
&lt;li&gt;Use a dark basemap style to make the colors pop&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;3. School Proximity Map with Labeled Markers&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Parents searching for a new home often prioritize school proximity. For a real estate or neighborhood information app, showing a home's location alongside nearby schools provides instant context about the area.&lt;br&gt;
Using the Static Images API, you can overlay multiple custom markers — one for the home and one for each nearby school — with labeled markers (A, B, C) to make it easy to cross-reference with a list displayed alongside the map.&lt;/p&gt;

&lt;p&gt;Example:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fed31mgxfyjm6hdwqaope.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fed31mgxfyjm6hdwqaope.png" alt="a static map image showing a marker for a home, and 3 markers for nearby schools labeled A, B, and C" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;https://api.mapbox.com/styles/v1/mapbox/light-v11/static/pin-s-c+4682b4(-122.3332,47.6113),pin-s-b+4682b4(-122.3401,47.6161),pin-s-a+4682b4(-122.3521,47.6138),pin-l-star+66ff2e(-122.3428,47.6139)/-122.3425,47.6141,13.8,0/600x400@2x?attribution=true&amp;amp;logo=true&amp;amp;access_token=YOUR_MAPBOX_ACCES_TOKEN&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use a distinct marker style for the home location&lt;/li&gt;
&lt;li&gt;Add labeled markers for each school with letters A, B, C (&lt;a href="https://docs.mapbox.com/api/maps/static-images/#marker" rel="noopener noreferrer"&gt;Glyphs are also available&lt;/a&gt; as marker labels using the &lt;code&gt;label&lt;/code&gt; parameter)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Use the Developer Playground to get started
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://docs.mapbox.com/playground/static/" rel="noopener noreferrer"&gt;Static Images API Playground&lt;/a&gt; makes it easy to get started without needing to learn the intricacies of the overlay DSL or manually dial your map in to your desired area and zoom level. The Playground provides a &lt;strong&gt;graphical user interface&lt;/strong&gt; where you can easily position the map, add overlays visually, and tinker with all the API parameters in real time.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsexqdgzovi764au87kku.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsexqdgzovi764au87kku.gif" alt="a gif showing the use of the Mapbox Static Images API Playground to move the map and create a marker overlay" width="720" height="414"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We recently improved the overlay UI to make it even simpler to add markers, lines, and polygons — just click, drag, and configure. Once your map image and overlays look exactly the way you want, simply copy the generated URL.&lt;/p&gt;

&lt;p&gt;You can use that URL directly in your frontend code as an &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; source, or use it as a template to generate similar URLs dynamically based on your application's data — just swap in different coordinates, zoom levels, or overlay parameters as needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to Choose Static Maps
&lt;/h2&gt;

&lt;p&gt;Static maps aren't a replacement for interactive maps — they're a complement. &lt;/p&gt;

&lt;p&gt;Use static maps when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You're displaying many maps at once (list views, galleries, grids)&lt;/li&gt;
&lt;li&gt;The map is supplementary context, not the primary interaction&lt;/li&gt;
&lt;li&gt;You need fast page loads and minimal JavaScript overhead&lt;/li&gt;
&lt;li&gt;You want a consistent image that won't change based on user interaction&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By combining beautiful Mapbox basemap styles with your own overlay data, the Static Images API gives you a lightweight, performant way to bring maps into your web and mobile applications — without the complexity or cost of full interactive rendering.&lt;/p&gt;

&lt;p&gt;Ready to get started? &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Try the &lt;a href="https://docs.mapbox.com/playground/static/" rel="noopener noreferrer"&gt;Static Images API Playground&lt;/a&gt; and see how easy it is to create custom static maps for your next project. &lt;/li&gt;
&lt;li&gt;See the &lt;a href="https://docs.mapbox.com/api/maps/static-images/" rel="noopener noreferrer"&gt;API reference documentation&lt;/a&gt; for detailed breakdowns of each parameter.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>mapping</category>
      <category>gis</category>
      <category>geojson</category>
    </item>
    <item>
      <title>Gradio X Mapbox</title>
      <dc:creator>Chris Whong</dc:creator>
      <pubDate>Thu, 29 Jan 2026 16:25:53 +0000</pubDate>
      <link>https://forem.com/mapbox/gradio-x-mapbox-982</link>
      <guid>https://forem.com/mapbox/gradio-x-mapbox-982</guid>
      <description>&lt;p&gt;A developer on &lt;a href="https://www.reddit.com/r/mapbox/" rel="noopener noreferrer"&gt;r/mapbox&lt;/a&gt; asked for if anyone had successfully integrated a &lt;a href="https://docs.mapbox.com/mapbox-gl-js/guides/" rel="noopener noreferrer"&gt;Mapbox GL JS&lt;/a&gt; map with &lt;a href="https://www.gradio.app/" rel="noopener noreferrer"&gt;Gradio&lt;/a&gt;, a python library for creating web interfaces for machine learning models.&lt;/p&gt;

&lt;p&gt;After a quick peek at &lt;a href="https://www.gradio.app/guides/quickstart" rel="noopener noreferrer"&gt;Gradio's docs&lt;/a&gt;, I quickly found the sections on &lt;a href="https://www.gradio.app/guides/custom-CSS-and-JS" rel="noopener noreferrer"&gt;adding custom CSS and JS&lt;/a&gt;. With a bit of help from AI, I learned the best way to get data out of python world and into JavaScript world in a Gradio app (there are several ways, but the one I used involves stashing it in a hidden output textarea)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4r6mkkktmn5bos5moooe.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4r6mkkktmn5bos5moooe.webp" alt=" " width="800" height="525"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I built a demo to show a simple implementation of Gradio X Mapbox:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Start with a pandas dataframe of U.S. cities with their longitude/latitude coordinates.&lt;/li&gt;
&lt;li&gt;Convert the city points to GeoJSON in python, dropping the GeoJSON in a hidden output.&lt;/li&gt;
&lt;li&gt;On map load, pick up the GeoJSON and add circles to the map representing the cities using a &lt;code&gt;geojson&lt;/code&gt; source and a &lt;code&gt;fill&lt;/code&gt; layer.&lt;/li&gt;
&lt;li&gt;Add a button to trigger some processing in python. In this case, it's the calculation of a convex hull around the point data.&lt;/li&gt;
&lt;li&gt;Convert the convex hull to GeoJSON and stash it in another hidden output.&lt;/li&gt;
&lt;li&gt;Render the convex hull polygon on the on the map using a &lt;code&gt;geojson&lt;/code&gt; source and a &lt;code&gt;fill&lt;/code&gt; layer.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are a few considerations for loading Mapbox GL JS:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Load Mapbox GL JS and its css via CDN links using the &lt;code&gt;head&lt;/code&gt; argument of &lt;code&gt;demo.launch&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Create a map container div using &lt;code&gt;gr.HTML()&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Before instantiating the map, poll for the existence of the map container (it may not exist when the JavaScript code first runs). In the example below, there is a timeout and subsequent checks for setting up the map.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The full code snippet is below if you want to give it a try. You'll need a Mapbox access token to get it working, so &lt;a href="https://account.mapbox.com/auth/signup/" rel="noopener noreferrer"&gt;sign up here&lt;/a&gt; if you don't already have a Mapbox account.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;gradio&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;gr&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;pandas&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;pd&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;numpy&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;np&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;json&lt;/span&gt;
&lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="nx"&gt;shapely&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;geometry&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;MultiPoint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;mapping&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Point&lt;/span&gt;


&lt;span class="err"&gt;#&lt;/span&gt; &lt;span class="nx"&gt;cities&lt;/span&gt; &lt;span class="nx"&gt;DataFrame&lt;/span&gt;
&lt;span class="nx"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DataFrame&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;city&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;New York&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Los Angeles&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Chicago&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Houston&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Phoenix&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Philadelphia&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;San Antonio&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;San Diego&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Dallas&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;San Jose&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;lat&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="mf"&gt;40.7128&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="mf"&gt;34.0522&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="mf"&gt;41.8781&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="mf"&gt;29.7604&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="mf"&gt;33.4484&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="mf"&gt;39.9526&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="mf"&gt;29.4241&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="mf"&gt;32.7157&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="mf"&gt;32.7767&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="mf"&gt;37.3382&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;lon&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;74.0060&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;118.2437&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;87.6298&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;95.3698&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;112.0740&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;75.1652&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;98.4936&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;117.1611&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;96.7970&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;121.8863&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;


&lt;span class="err"&gt;#&lt;/span&gt; &lt;span class="nx"&gt;compute&lt;/span&gt; &lt;span class="nx"&gt;convex&lt;/span&gt; &lt;span class="nx"&gt;hull&lt;/span&gt; &lt;span class="nx"&gt;and&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;GeoJSON&lt;/span&gt;
&lt;span class="nx"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;compute_convex_hull&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nx"&gt;points&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;Point&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;lon&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;lat&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;row&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;iterrows&lt;/span&gt;&lt;span class="p"&gt;()]&lt;/span&gt;
    &lt;span class="nx"&gt;multipoint&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MultiPoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;points&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;hull&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;multipoint&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;convex_hull&lt;/span&gt;
    &lt;span class="nx"&gt;hull_geojson&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;mapping&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hull&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;features&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Feature&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;geometry&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;mapping&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pt&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;properties&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;city&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;city&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]},&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nx"&gt;pt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;zip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;points&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;iterrows&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="nx"&gt;geojson&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;FeatureCollection&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;features&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Feature&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;geometry&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;hull_geojson&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;properties&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;convex_hull&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;features&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;geojson&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="err"&gt;#&lt;/span&gt; &lt;span class="nx"&gt;convert&lt;/span&gt; &lt;span class="nx"&gt;points&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="nx"&gt;GeoJSON&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nx"&gt;initial&lt;/span&gt; &lt;span class="nx"&gt;display&lt;/span&gt;
&lt;span class="nx"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_points_geojson&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="dl"&gt;"""&lt;/span&gt;&lt;span class="s2"&gt;Convert dataframe to GeoJSON for initial point display&lt;/span&gt;&lt;span class="dl"&gt;"""&lt;/span&gt;
    &lt;span class="nx"&gt;features&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Feature&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;geometry&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Point&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;coordinates&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;lon&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;lat&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]]},&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;properties&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;city&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;city&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]},&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;row&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;iterrows&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;FeatureCollection&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;features&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;features&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;


&lt;span class="err"&gt;#&lt;/span&gt; &lt;span class="nx"&gt;Gradio&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;two&lt;/span&gt; &lt;span class="nx"&gt;columns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;left&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="nx"&gt;table&lt;/span&gt; &lt;span class="nx"&gt;and&lt;/span&gt; &lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;right&lt;/span&gt; &lt;span class="nx"&gt;map&lt;/span&gt; &lt;span class="nx"&gt;display&lt;/span&gt;
&lt;span class="kd"&gt;with&lt;/span&gt; &lt;span class="nx"&gt;gr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Blocks&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;demo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="kd"&gt;with&lt;/span&gt; &lt;span class="nx"&gt;gr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Row&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="kd"&gt;with&lt;/span&gt; &lt;span class="nx"&gt;gr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
            &lt;span class="nx"&gt;gr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Markdown&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="dl"&gt;"""&lt;/span&gt;&lt;span class="s2"&gt;
                # US Cities Convex Hull Demo
                This demo computes the convex hull of a set of US cities and outputs the result as GeoJSON for display on a [Mapbox GL JS](https://docs.mapbox.com/mapbox-gl-js/) map.

                GeoJSON strings are assembled in python and stored in hidden Gradio outputs for easy access in the Javascript map code.
                &lt;/span&gt;&lt;span class="dl"&gt;"""&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="nx"&gt;gr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Dataframe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;df&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;City Locations&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="nx"&gt;btn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;gr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Compute Convex Hull GeoJSON&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


        &lt;span class="err"&gt;#&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt; &lt;span class="nx"&gt;includes&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;map&lt;/span&gt; &lt;span class="nx"&gt;container&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Mapbox&lt;/span&gt; &lt;span class="nx"&gt;GL&lt;/span&gt; &lt;span class="nx"&gt;JS&lt;/span&gt; &lt;span class="nx"&gt;will&lt;/span&gt; &lt;span class="nx"&gt;add&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="nx"&gt;map&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt; &lt;span class="nx"&gt;div&lt;/span&gt;
        &lt;span class="kd"&gt;with&lt;/span&gt; &lt;span class="nx"&gt;gr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
            &lt;span class="nx"&gt;gr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;HTML&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="dl"&gt;"""&lt;/span&gt;&lt;span class="s2"&gt;
                &amp;lt;div&amp;gt;
                    &amp;lt;h3&amp;gt;Mapbox GL JS Visualization&amp;lt;/h3&amp;gt;
                    &amp;lt;div id='map-container' style='width: 100%; height: 450px; background-color: lightgray;'&amp;gt;&amp;lt;/div&amp;gt;
                    &amp;lt;div&amp;gt;© Mapbox &amp;amp;nbsp; © OpenStreetMap&amp;lt;/div&amp;gt;
                &amp;lt;/div&amp;gt;
                &lt;/span&gt;&lt;span class="dl"&gt;"""&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;


    &lt;span class="err"&gt;#&lt;/span&gt; &lt;span class="nx"&gt;empty&lt;/span&gt; &lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="nx"&gt;boxes&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="nx"&gt;hold&lt;/span&gt; &lt;span class="nx"&gt;GeoJSON&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;
    &lt;span class="nx"&gt;geojson_output&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;gr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;visible&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;initial_points&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;gr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;get_points_geojson&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;df&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;visible&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


    &lt;span class="nx"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;handle_click&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;compute_convex_hull&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;df&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


    &lt;span class="err"&gt;#&lt;/span&gt; &lt;span class="nx"&gt;on&lt;/span&gt; &lt;span class="nx"&gt;click&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;compute&lt;/span&gt; &lt;span class="nx"&gt;convex&lt;/span&gt; &lt;span class="nx"&gt;hull&lt;/span&gt; &lt;span class="nx"&gt;and&lt;/span&gt; &lt;span class="nx"&gt;update&lt;/span&gt; &lt;span class="nx"&gt;map&lt;/span&gt;
    &lt;span class="nx"&gt;btn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nx"&gt;handle_click&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;outputs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;geojson_output&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nx"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;inputs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;geojson_output&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;outputs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;js&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"""&lt;/span&gt;&lt;span class="s2"&gt;
        (data) =&amp;gt; {
            console.log(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="nx"&gt;Received&lt;/span&gt; &lt;span class="nx"&gt;GeoJSON&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;, data);
            if (window.map) {
                // Remove existing hull layer if it exists
                if (window.map.getLayer('convex-hull')) {
                    window.map.removeLayer('convex-hull');
                }
                if (window.map.getSource('hull')) {
                    window.map.removeSource('hull');
                }


                // Add hull
                window.map.addSource('hull', {
                    type: 'geojson',
                    data: data,
                });


                window.map.addLayer({
                    id: 'convex-hull',
                    type: 'fill',
                    source: 'hull',
                    paint: {
                        'fill-color': '#088',
                        'fill-opacity': 0.3,
                        'fill-emissive-strength': 1,
                    },
                });


                window.map.addLayer({
                    id: 'convex-hull-outline',
                    type: 'line',
                    source: 'hull',
                    paint: {
                        'line-color': '#FFF',
                        'line-width': 2,
                        'line-emissive-strength': 1,
                    },
                });
            }
        }
        &lt;/span&gt;&lt;span class="dl"&gt;"""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;


    &lt;span class="err"&gt;#&lt;/span&gt; &lt;span class="nx"&gt;on&lt;/span&gt; &lt;span class="nx"&gt;initial&lt;/span&gt; &lt;span class="nx"&gt;load&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;initialize&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;then&lt;/span&gt; &lt;span class="nx"&gt;add&lt;/span&gt; &lt;span class="nx"&gt;points&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="nx"&gt;GeoJSON&lt;/span&gt; &lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="nx"&gt;and&lt;/span&gt; &lt;span class="nx"&gt;circle&lt;/span&gt; &lt;span class="nx"&gt;layer&lt;/span&gt;
    &lt;span class="err"&gt;#&lt;/span&gt; &lt;span class="nf"&gt;initMap&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="nx"&gt;contains&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="nx"&gt;retry&lt;/span&gt; &lt;span class="nx"&gt;mechanism&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="nx"&gt;wait&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;map&lt;/span&gt; &lt;span class="nx"&gt;container&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="nx"&gt;be&lt;/span&gt; &lt;span class="nx"&gt;available&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="err"&gt;#&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;Gradio&lt;/span&gt; &lt;span class="nx"&gt;may&lt;/span&gt; &lt;span class="nx"&gt;take&lt;/span&gt; &lt;span class="nx"&gt;some&lt;/span&gt; &lt;span class="nx"&gt;time&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="nx"&gt;render&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;HTML&lt;/span&gt; &lt;span class="nx"&gt;and&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;map&lt;/span&gt; &lt;span class="nx"&gt;container&lt;/span&gt; &lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;may&lt;/span&gt; &lt;span class="nx"&gt;not&lt;/span&gt; &lt;span class="nx"&gt;be&lt;/span&gt; &lt;span class="nx"&gt;present&lt;/span&gt; &lt;span class="nx"&gt;immediately&lt;/span&gt;
    &lt;span class="nx"&gt;demo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nx"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;inputs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;initial_points&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;outputs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;js&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"""&lt;/span&gt;&lt;span class="s2"&gt;
        (pointsData) =&amp;gt; {
            function initMap() {
                const container = document.getElementById('map-container');
                if (!container) {
                    setTimeout(initMap, 100);
                    return;
                }


                mapboxgl.accessToken = 'YOUR_MAPBOX_ACCESS_TOKEN';


                window.map = new mapboxgl.Map({
                    container: 'map-container',
                    style: 'mapbox://styles/mapbox/standard',
                    config: {
                        basemap: {
                            theme: 'monochrome',
                            lightPreset: 'night',
                        },
                    },
                    center: [-98.92906, 40.25617],
                    zoom: 2.5,
                    projection: 'mercator',
                    attributionControl: false,
                });


                // Add points after map loads
                window.map.on('load', function () {
                    window.map.addSource('cities', {
                        type: 'geojson',
                        data: pointsData,
                    });


                    window.map.addLayer({
                        id: 'city-points',
                        type: 'circle',
                        source: 'cities',
                        paint: {
                            'circle-radius': 6,
                            'circle-color': '#B42222',
                            'circle-emissive-strength': 1,
                        },
                    });
                });
            }


            initMap();
        }
        &lt;/span&gt;&lt;span class="dl"&gt;"""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="err"&gt;#&lt;/span&gt; &lt;span class="nx"&gt;load&lt;/span&gt; &lt;span class="nx"&gt;mapbox&lt;/span&gt; &lt;span class="nx"&gt;gl&lt;/span&gt; &lt;span class="nx"&gt;js&lt;/span&gt; &lt;span class="nx"&gt;and&lt;/span&gt; &lt;span class="nx"&gt;its&lt;/span&gt; &lt;span class="nx"&gt;css&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;head&lt;/span&gt;
&lt;span class="nx"&gt;head&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"""&lt;/span&gt;&lt;span class="s2"&gt;
&amp;lt;link href=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="nx"&gt;https&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="c1"&gt;//api.mapbox.com/mapbox-gl-js/v3.18.1/mapbox-gl.css" rel="stylesheet"&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;script&lt;/span&gt; &lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://api.mapbox.com/mapbox-gl-js/v3.18.1/mapbox-gl.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/script&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;span class="dl"&gt;"""&lt;/span&gt;&lt;span class="s2"&gt;


# custom css to position the Mapbox logo, as Gradio css interferes with default positioning
# note that attributionControl is set to false in the map options because Gradio CSS also interferes with the attribution control
# map attribution is required and was manually added in the HTML above
custom_css = &lt;/span&gt;&lt;span class="dl"&gt;"""&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mapboxgl&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;ctrl&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;bottom&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mapboxgl&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;ctrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mapboxgl&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;ctrl&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mapboxgl&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;ctrl&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="nx"&gt;px&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="nx"&gt;px&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;important&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="dl"&gt;"""&lt;/span&gt;&lt;span class="s2"&gt;


demo.launch(head=head, css=custom_css)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>mapbox</category>
      <category>python</category>
      <category>javascript</category>
      <category>datascience</category>
    </item>
    <item>
      <title>Build a ChatGPT App with Mapbox</title>
      <dc:creator>Chris Tufts</dc:creator>
      <pubDate>Fri, 09 Jan 2026 19:40:05 +0000</pubDate>
      <link>https://forem.com/mapbox/build-a-chatgpt-app-with-mapbox-dca</link>
      <guid>https://forem.com/mapbox/build-a-chatgpt-app-with-mapbox-dca</guid>
      <description>&lt;p&gt;ChatGPT Apps are a new way to build interactive experiences within the ChatGPT interface. In this tutorial, we'll explore and run a Points of Interest (POI) search app powered by Mapbox APIs that lets users find places like coffee shops, restaurants, and gas stations near any location using natural language. The focus of this tutorial is on understanding how to build a &lt;strong&gt;ChatGPT App&lt;/strong&gt; using the &lt;strong&gt;ChatGPT Apps SDK&lt;/strong&gt; and the &lt;strong&gt;Model Context Protocol (MCP)&lt;/strong&gt;. The app is loosely based on the &lt;a href="https://docs.mapbox.com/help/tutorials/building-a-store-locator/" rel="noopener noreferrer"&gt;Mapbox &lt;em&gt;Build a store locator using Mapbox GL JS&lt;/em&gt; tutorial&lt;/a&gt;, but adapted for the ChatGPT UI. For the remainder of the post I'll refer to ChatGPT apps as &lt;code&gt;apps&lt;/code&gt; for brevity. &lt;/p&gt;

&lt;h2&gt;
  
  
  How ChatGPT Apps Work
&lt;/h2&gt;

&lt;p&gt;From a user perspective, utilizing an app is identical to the traditional chat workflow: select any tools you want to use, type a prompt into the chat interface, and you get a response. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwczzqf5ivblvosahm5l8.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwczzqf5ivblvosahm5l8.gif" alt="ChatGPT Mapbox App UI Flow" width="720" height="657"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The workflow under the hood starts in ChatGPT, utilizes your app (an MCP server), and uses the results to render a widget in the chat UI. The process is shown in the following diagram.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────────────────────────────────────────────────────────────┐
│                         ChatGPT                                 │
│        User: "Find coffee shops near Times Square"              |
│                           │                                     │
│                           ▼                                     │
│              Calls find_nearby_places tool                      │
└───────────────────────────┬─────────────────────────────────────┘
                            │
                            ▼
┌─────────────────────────────────────────────────────────────────┐
│                    Your MCP Server                              │
│  • Geocode "Times Square" → coordinates (Geocoding API)         │
│  • Search for "coffee" near coordinates (Search Box API)        │
│  • Return structuredContent with place data                     │
└───────────────────────────┬─────────────────────────────────────┘
                            │
                            ▼
┌─────────────────────────────────────────────────────────────────┐
│                         ChatGPT                                 │
│  • Receives structuredContent from tool response                │
│  • Sets window.openai.toolOutput = structuredContent            │
│  • Loads your widget HTML (from MCP resource) in an iframe      │
└───────────────────────────┬─────────────────────────────────────┘
                            │
                            ▼
┌─────────────────────────────────────────────────────────────────┐
│              Your Widget (iframe in ChatGPT UI)                 │
│  • Your HTML/CSS/JS from widget.html                            │
│  • Reads data from window.openai.toolOutput                     │
│  • Renders Mapbox GL JS map with markers                        │
└─────────────────────────────────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Key Concepts
&lt;/h3&gt;

&lt;p&gt;If you are new to MCP's and ChatGPT apps, the following table shows some new key concepts to keep an eye out for as we go through the example. The table is meant for quick reference as more detail will be provided  as we progress through the tutorial. &lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Concept&lt;/th&gt;
&lt;th&gt;What It Is&lt;/th&gt;
&lt;th&gt;In Our App&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;a href="https://developers.openai.com/apps-sdk/concepts/mcp-server" rel="noopener noreferrer"&gt;MCP&lt;/a&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Model Context Protocol— the spec for connecting an LLM with external tools and resources&lt;/td&gt;
&lt;td&gt;The App is an MCP server&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Resource&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;An HTML template ChatGPT can render&lt;/td&gt;
&lt;td&gt;Our map widget (&lt;code&gt;widget.html&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Tool&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;External tools our MCP exposes to ChatGPT&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;find_nearby_places&lt;/code&gt; tool&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;text/html+skybridge&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;MIME type that tells ChatGPT "this is a widget"&lt;/td&gt;
&lt;td&gt;Set on our resource&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://developers.openai.com/apps-sdk/build/chatgpt-ui/" rel="noopener noreferrer"&gt;&lt;strong&gt;&lt;code&gt;window.openai.toolOutput&lt;/code&gt;&lt;/strong&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;How tool results reach your widget&lt;/td&gt;
&lt;td&gt;Contains our POI/place data&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Learn more&lt;/strong&gt;: See the &lt;a href="https://developers.openai.com/apps-sdk" rel="noopener noreferrer"&gt;ChatGPT Apps SDK documentation&lt;/a&gt; for detailed guides on MCP servers, widgets, tools, and more.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Project Setup
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Mapbox account&lt;/strong&gt; with access token (&lt;a href="https://account.mapbox.com/" rel="noopener noreferrer"&gt;get one here&lt;/a&gt;). For additional info on tokens see the &lt;a href="https://docs.mapbox.com/help/dive-deeper/access-tokens/" rel="noopener noreferrer"&gt;deep dive&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Node.js 18+&lt;/strong&gt; installed&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ChatGPT Plus or Team&lt;/strong&gt; account with developer mode enabled&lt;/li&gt;
&lt;li&gt;(Optional): Download &lt;a href="https://ngrok.com/download" rel="noopener noreferrer"&gt;ngrok&lt;/a&gt; for local development. &lt;a href="https://developers.openai.com/apps-sdk/deploy/" rel="noopener noreferrer"&gt;ChatGPT requires the MCP be hosted behind an HTTPS&lt;/a&gt; endpoint, ngrok is a simple way to do this. &lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  1. Clone the repository
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/mapbox/location-ai-tutorials.git
&lt;span class="nb"&gt;cd &lt;/span&gt;location-ai-tutorials/chatgpt-app-tutorial
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Install dependencies
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Package&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@modelcontextprotocol/sdk&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;MCP protocol implementation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;zod&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Schema validation (required by MCP)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;dotenv&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Load environment variables&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  3. Configure environment
&lt;/h3&gt;

&lt;p&gt;Copy the example environment file and add your Mapbox token:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cp&lt;/span&gt; .env.example .env
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Edit &lt;code&gt;.env&lt;/code&gt; and add your token:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;MAPBOX_ACCESS_TOKEN=pk.your_token_here
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Project structure
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;chatgpt-app-tutorial/
├── server.js           # MCP server
├── public/
│   └── widget.html     # Map widget
├── package.json
├── .env.example
└── .env                # Your local config (not tracked)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Understanding the MCP Server
&lt;/h2&gt;

&lt;p&gt;Let's walk through &lt;code&gt;server.js&lt;/code&gt; to understand how the MCP server works.&lt;/p&gt;

&lt;h3&gt;
  
  
  Server setup
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dotenv/config&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;McpServer&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@modelcontextprotocol/sdk/server/mcp.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;StreamableHTTPServerTransport&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@modelcontextprotocol/sdk/server/streamableHttp.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createServer&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;node:http&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;readFileSync&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;node:fs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;zod&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Load widget HTML and inject Mapbox token&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;widgetHtml&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;readFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;public/widget.html&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;utf8&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;{{MAPBOX_ACCESS_TOKEN}}&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;MAPBOX_ACCESS_TOKEN&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;createPoiSearchServer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;server&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;McpServer&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;poi-search&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1.0.0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="c1"&gt;// Resources are added here&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Register the widget resource
&lt;/h3&gt;

&lt;p&gt;Resources are HTML templates that ChatGPT can render. The key is the &lt;code&gt;text/html+skybridge&lt;/code&gt; MIME type which tells ChatGPT it's a widget, not just HTML text.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;registerResource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;places-widget&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;           &lt;span class="c1"&gt;// Name (for reference)&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ui://widget/places.html&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// URI (links tool output to this widget)&lt;/span&gt;
  &lt;span class="p"&gt;{},&lt;/span&gt;
  &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
      &lt;span class="na"&gt;uri&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ui://widget/places.html&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;mimeType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text/html+skybridge&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// This makes it a widget!&lt;/span&gt;
      &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;widgetHtml&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;_meta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;openai/widgetPrefersBorder&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}]&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Register the find_nearby_places tool
&lt;/h3&gt;

&lt;p&gt;Tools are functions ChatGPT can call. The &lt;code&gt;_meta["openai/outputTemplate"]&lt;/code&gt; links this tool to our widget. When ChatGPT calls the tool, the widget renders with the returned data.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;registerTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;find_nearby_places&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Find Nearby Places&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Use this when the user wants to find places like coffee shops, restaurants, gas stations, grocery stores, or other businesses near a specific location. Always requires both a type of place and a location.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;inputSchema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;category&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Type of place to search for (e.g., 'coffee', 'restaurant', 'grocery', 'pharmacy', 'gas station', 'hotel', 'bank')&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="na"&gt;location&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Location to search near (e.g., 'Times Square NYC', 'downtown Seattle', '1600 Pennsylvania Ave')&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;_meta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;openai/outputTemplate&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ui://widget/places.html&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// Link to widget&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;openai/toolInvocation/invoking&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Searching for nearby places...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;openai/toolInvocation/invoked&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Places found&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;category&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;location&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Implementation coming next&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Add Mapbox API helpers
&lt;/h3&gt;

&lt;p&gt;The following functions call &lt;a href="https://docs.mapbox.com/api/guides/" rel="noopener noreferrer"&gt;Mapbox APIs&lt;/a&gt; to convert text locations to coordinates and search for places.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Convert text location to coordinates&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;geocodeLocation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;locationText&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://api.mapbox.com/search/geocode/v6/forward&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;searchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;q&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;locationText&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;searchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;access_token&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;searchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;limit&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Geocoding failed: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;features&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;features&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Could not find location: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;locationText&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;. Try a more specific address or city name.`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;feature&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;features&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;longitude&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;latitude&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;feature&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;geometry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;coordinates&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;placeName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;feature&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;full_address&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;feature&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;locationText&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;longitude&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;latitude&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;placeName&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Search for places by category&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;searchCategory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;categoryId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;longitude&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;latitude&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`https://api.mapbox.com/search/searchbox/v1/category/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;encodeURIComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;categoryId&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;searchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;access_token&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;searchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;proximity&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;longitude&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;latitude&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;searchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;limit&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;10&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Category search failed: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Add category mapping and response transformer
&lt;/h3&gt;

&lt;p&gt;These helper functions normalize user input to Mapbox category IDs and transform the API response to our widget's expected format:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Category mappings - maps common user terms to Mapbox category IDs&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;CATEGORY_MAPPINGS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;coffee&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;coffee&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;coffee shop&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;coffee&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;restaurant&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;restaurant&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;grocery&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;grocery&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;gas station&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;gas_station&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;pharmacy&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;pharmacy&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hotel&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hotel&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;bank&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;bank&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="c1"&gt;// ... add more as needed&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;mapCategoryToId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userCategory&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;normalized&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;userCategory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;CATEGORY_MAPPINGS&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;normalized&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;normalized&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Transform Mapbox response to widget-compatible GeoJSON&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;transformToWidgetGeoJSON&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mapboxResponse&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;categoryName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;FeatureCollection&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;features&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mapboxResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;features&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;[]).&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;feature&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Feature&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;geometry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;feature&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;geometry&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;feature&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Unknown&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;feature&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;address&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;feature&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;full_address&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;city&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;feature&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;place&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;feature&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;region&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;region_code&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;postalCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;feature&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;postcode&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;phoneFormatted&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;feature&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;phone&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;category&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;categoryName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;mapbox_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;feature&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mapbox_id&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}))&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Implement the tool
&lt;/h3&gt;

&lt;p&gt;Now wire up the tool to use these helper functions and return data in the format our widget expects:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;category&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;location&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;accessToken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;MAPBOX_ACCESS_TOKEN&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Error: Mapbox access token not configured.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}],&lt;/span&gt;
      &lt;span class="na"&gt;isError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Step 1: Geocode the location&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;longitude&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;latitude&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;placeName&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;geocodeLocation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Step 2: Map category to Mapbox ID&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;categoryId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;mapCategoryToId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;category&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Step 3: Search for places&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;searchResults&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;searchCategory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;categoryId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;longitude&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;latitude&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Step 4: Transform to widget format&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;places&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;transformToWidgetGeoJSON&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;searchResults&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;category&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Step 5: Calculate map center and zoom&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;center&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;longitude&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;latitude&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;zoom&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;places&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;features&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="mi"&gt;13&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;structuredContent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;places&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;places&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;center&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;center&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;zoom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;zoom&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;searchLocation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;placeName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;searchCategory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;category&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;places&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;features&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
          &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="s2"&gt;`Found &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;places&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;features&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;category&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; places near &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;placeName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.`&lt;/span&gt;
          &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`No &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;category&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; places found near &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;placeName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;. Try a different location or category.`&lt;/span&gt;
      &lt;span class="p"&gt;}]&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Search failed: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt; &lt;span class="p"&gt;}],&lt;/span&gt;
      &lt;span class="na"&gt;isError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Set up the HTTP server
&lt;/h3&gt;

&lt;p&gt;ChatGPT connects to your server via HTTP. The &lt;code&gt;/mcp&lt;/code&gt; endpoint handles the MCP protocol.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;PORT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PORT&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;8787&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;httpServer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// CORS headers (required for ChatGPT)&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;method&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;OPTIONS&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writeHead&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;204&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Access-Control-Allow-Origin&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;*&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Access-Control-Allow-Methods&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST, GET, OPTIONS, DELETE&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Access-Control-Allow-Headers&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type, mcp-session-id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;end&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// MCP endpoint&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`http://localhost`&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;pathname&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/mcp&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Access-Control-Allow-Origin&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;*&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;server&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createPoiSearchServer&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;transport&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;StreamableHTTPServerTransport&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;sessionIdGenerator&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// Stateless mode&lt;/span&gt;
      &lt;span class="na"&gt;enableJsonResponse&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;close&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;transport&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;transport&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;transport&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;handleRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writeHead&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;end&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Not Found&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;httpServer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;PORT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`MCP Server running at http://localhost:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;PORT&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/mcp`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Understanding the Widget
&lt;/h2&gt;

&lt;p&gt;When a tool returns &lt;code&gt;structuredContent&lt;/code&gt;, ChatGPT can render a custom UI embedded directly in the chat interface known as a &lt;strong&gt;widget&lt;/strong&gt;. This is what transforms our app from plain text responses into an interactive map experience.&lt;/p&gt;

&lt;p&gt;The widget is loaded in an iframe within ChatGPT. It receives data from the tool via &lt;code&gt;window.openai.toolOutput&lt;/code&gt; and can render anything a web page can: maps, charts, forms, or any interactive UI. In our case, the widget (&lt;code&gt;public/widget.html&lt;/code&gt;) displays a Mapbox map with place markers and a clickable sidebar.&lt;/p&gt;

&lt;p&gt;Since the widget is just HTML/CSS/JavaScript, you have full control over the user experience without requiring any special framework.&lt;/p&gt;

&lt;h3&gt;
  
  
  Updating the Widget's Data
&lt;/h3&gt;

&lt;p&gt;The &lt;strong&gt;data arrives via &lt;code&gt;window.openai.toolOutput&lt;/code&gt;&lt;/strong&gt; and it might not be available immediately when your script runs. We set up an event listener to ensure the widget can updated once the data is received from the API's.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Traditional app&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;places&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/places&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

&lt;span class="c1"&gt;// ChatGPT App - data comes from tool output&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;places&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;openai&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;toolOutput&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;places&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// But it might arrive later, so also listen for updates&lt;/span&gt;
&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;openai:set_globals&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;places&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;detail&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;globals&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;toolOutput&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;places&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="c1"&gt;// Re-render with new data&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Widget structure
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://api.mapbox.com/mapbox-gl-js/v3.14.0/mapbox-gl.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"https://api.mapbox.com/mapbox-gl-js/v3.14.0/mapbox-gl.css"&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"stylesheet"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;style&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;/* Sidebar + map layout */&lt;/span&gt;
    &lt;span class="nc"&gt;.container&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100vh&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nc"&gt;.sidebar&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;280px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;overflow-y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;auto&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nc"&gt;.sidebar.hidden&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nc"&gt;.map&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="c"&gt;/* Place cards, markers, etc. */&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/style&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"container"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;&amp;lt;!-- Sidebar starts hidden until results arrive --&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"sidebar hidden"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;h2&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"place-count"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Places nearby: 0&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"listings"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"map"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"map"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
    &lt;span class="c1"&gt;// Get data from ChatGPT tool output&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;places&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;openai&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;toolOutput&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;places&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;FeatureCollection&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;features&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;mapCenter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;openai&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;toolOutput&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;center&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;77.03915&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;38.90025&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;mapZoom&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;openai&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;toolOutput&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;zoom&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Listen for data updates&lt;/span&gt;
    &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;openai:set_globals&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;detail&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;globals&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;toolOutput&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;output&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;places&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;places&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;output&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;places&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nx"&gt;mapCenter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;output&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;center&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="nx"&gt;mapCenter&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nx"&gt;mapZoom&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;output&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;zoom&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="nx"&gt;mapZoom&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="c1"&gt;// Re-render everything&lt;/span&gt;
        &lt;span class="nf"&gt;clearMarkers&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="nf"&gt;addMarkers&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="nf"&gt;buildLocationList&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="c1"&gt;// Show sidebar now that we have data&lt;/span&gt;
        &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.sidebar&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hidden&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resize&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;flyTo&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;center&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;mapCenter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;zoom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;mapZoom&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="c1"&gt;// Standard Mapbox GL JS code&lt;/span&gt;
    &lt;span class="nx"&gt;mapboxgl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;accessToken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;{{MAPBOX_ACCESS_TOKEN}}&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;map&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;mapboxgl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;container&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;map&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;center&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;mapCenter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;zoom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;mapZoom&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;basemap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;faded&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="c1"&gt;// Add markers, build listings, handle clicks...&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Testing
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Start the server
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;POI Category Search MCP Server
==============================
Server running at: http://localhost:8787
MCP endpoint:      http://localhost:8787/mcp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Test with MCP Inspector
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx @modelcontextprotocol/inspector@latest http://localhost:8787/mcp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see the tool &lt;code&gt;find_nearby_places&lt;/code&gt; and the resource &lt;code&gt;places-widget&lt;/code&gt; in the inspector. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe1h8w2toqzjp6lxe7tko.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe1h8w2toqzjp6lxe7tko.png" alt="MCP Resources in MCP Inspector" width="800" height="298"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fooc5zdnnqorlc6sww9as.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fooc5zdnnqorlc6sww9as.png" alt="MCP Tools in MCP Inspector" width="800" height="297"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This lets you call tools and see responses without ChatGPT.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Expose with ngrok
&lt;/h3&gt;

&lt;p&gt;ChatGPT needs a public URL to connect:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ngrok http 8787
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Copy the &lt;code&gt;https://&lt;/code&gt; URL.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Add to ChatGPT and Test Your App
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Go to ChatGPT → Settings → Apps &amp;amp; Connectors → Advanced settings&lt;/li&gt;
&lt;li&gt;Enable developer mode&lt;/li&gt;
&lt;li&gt;Add a connector with your ngrok URL + &lt;code&gt;/mcp&lt;/code&gt; (e.g., &lt;code&gt;https://abc123.ngrok.io/mcp&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Open a new chat, click the connector icon, add your connector&lt;/li&gt;
&lt;li&gt;Try: "Find coffee shops near Times Square"&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You've now successfully built and integrated your first ChatGPT App!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwclyca1qdag21wa8hcda.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwclyca1qdag21wa8hcda.gif" alt="App &amp;amp; Response for " width="604" height="594"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Additional Resources
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Mapbox
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/mapbox/location-ai-tutorials.git" rel="noopener noreferrer"&gt;Mapbox Location AI Tutorials repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.mapbox.com/api/guides/mcp-server/" rel="noopener noreferrer"&gt;Mapbox MCP Server&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.mapbox.com/help/tutorials/building-a-store-locator/" rel="noopener noreferrer"&gt;Mapbox Store Locator Tutorial&lt;/a&gt; - The original tutorial this builds upon&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.mapbox.com/mapbox-gl-js/guides/" rel="noopener noreferrer"&gt;Mapbox GL JS Documentation&lt;/a&gt; - Full guide to the mapping library&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.mapbox.com/api/search/geocoding/" rel="noopener noreferrer"&gt;Mapbox Geocoding API&lt;/a&gt; - Convert addresses to coordinates&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.mapbox.com/api/search/search-box/" rel="noopener noreferrer"&gt;Mapbox Search Box API&lt;/a&gt; - Search for places by category&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://account.mapbox.com/" rel="noopener noreferrer"&gt;Mapbox Access Tokens&lt;/a&gt; - Get your API token&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  OpenAI / ChatGPT Apps
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://developers.openai.com/apps-sdk" rel="noopener noreferrer"&gt;ChatGPT Apps SDK&lt;/a&gt; - Complete documentation for building ChatGPT Apps&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.npmjs.com/package/@modelcontextprotocol/inspector" rel="noopener noreferrer"&gt;MCP Inspector&lt;/a&gt; - Test your MCP server locally&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developers.openai.com/apps-sdk/deploy/connect-chatgpt/" rel="noopener noreferrer"&gt;Connecting Your App to ChatGPT&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>chatgpt</category>
      <category>mcp</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
