<?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>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>
    <item>
      <title>Introducing the Mapbox MCP Server: Location Intelligence for AI Agents</title>
      <dc:creator>Matthew Podwysocki</dc:creator>
      <pubDate>Tue, 16 Dec 2025 14:43:34 +0000</pubDate>
      <link>https://forem.com/mapbox/introducing-the-mapbox-mcp-server-location-intelligence-for-ai-agents-4bia</link>
      <guid>https://forem.com/mapbox/introducing-the-mapbox-mcp-server-location-intelligence-for-ai-agents-4bia</guid>
      <description>&lt;p&gt;AI agents are getting smarter every day as they can write code, analyze data, and answer complex questions. But when it comes to understanding &lt;em&gt;where&lt;/em&gt; things are, most agents hit a wall. They can tell you about restaurants in San Francisco, but they can't geocode an address, calculate a route, or determine if two locations are within walking distance.&lt;/p&gt;

&lt;p&gt;That's where the &lt;strong&gt;Mapbox Model Context Protocol (MCP) Server&lt;/strong&gt; comes in. It gives AI agents access to Mapbox's powerful location services through a simple, standardized interface. No custom API integration required, just connect your agent to the MCP server, and it can instantly geocode addresses, plan routes, analyze travel times, and create maps.&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%2Fdnliakuyq4f0ive7f518.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%2Fdnliakuyq4f0ive7f518.gif" alt="DC Tour Assistant Demo" width="600" height="307"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Example: An AI agent built with MCP can naturally handle location queries and display results visually&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  What is the Model Context Protocol (MCP)?
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://modelcontextprotocol.io/" rel="noopener noreferrer"&gt;Model Context Protocol&lt;/a&gt; is an open standard created by Anthropic for connecting AI models to external tools and data sources. Think of it as USB for AI agents which is to say a universal connector that works across different frameworks and platforms.&lt;/p&gt;

&lt;p&gt;Instead of writing custom code to integrate with each API, you:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Connect your agent to an MCP server&lt;/li&gt;
&lt;li&gt;The server exposes its tools in a standardized format&lt;/li&gt;
&lt;li&gt;Your agent can discover and use those tools automatically&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;MCP is supported by popular frameworks like CrewAI, LangGraph, Pydantic AI, and Mastra, as well as by AI assistants like GitHub Copilot and Claude Desktop.&lt;/p&gt;
&lt;h2&gt;
  
  
  What Does the Mapbox MCP Server Provide?
&lt;/h2&gt;

&lt;p&gt;The Mapbox MCP Server exposes nine powerful geospatial tools that give your agents comprehensive location intelligence.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Mapbox also offers the &lt;a href="https://github.com/mapbox/mcp-devkit-server" rel="noopener noreferrer"&gt;DevKit MCP Server&lt;/a&gt; (hosted at &lt;code&gt;https://mcp-devkit.mapbox.com/mcp&lt;/code&gt;), which is designed to help developers &lt;em&gt;build&lt;/em&gt; Mapbox applications by providing code examples, documentation search, and development assistance. This post focuses on the main MCP server for location intelligence. We'll cover the DevKit in a future post.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;
  
  
  Core Tools
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;th&gt;Example Use&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;search_and_geocode_tool&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Convert addresses/places to coordinates&lt;/td&gt;
&lt;td&gt;"Find coordinates for '1600 Pennsylvania Ave, Washington DC'"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;reverse_geocoding_tool&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Convert coordinates to addresses&lt;/td&gt;
&lt;td&gt;"What's the address at 38.8977, -77.0365?"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;directions_tool&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Calculate routes between points&lt;/td&gt;
&lt;td&gt;"Driving directions from Boston to New York with traffic"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;matrix_tool&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Travel times between multiple points&lt;/td&gt;
&lt;td&gt;"Calculate delivery times from 3 warehouses to 10 addresses"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;isochrone_tool&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Areas reachable within time/distance&lt;/td&gt;
&lt;td&gt;"Show everywhere I can drive in 30 minutes from downtown"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;static_image_tool&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Generate map images&lt;/td&gt;
&lt;td&gt;"Create a map showing my delivery route"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;category_search_tool&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Find POIs by category&lt;/td&gt;
&lt;td&gt;"Find all museums near Central Park"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;resource_reader_tool&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Access reference data&lt;/td&gt;
&lt;td&gt;"Get list of available POI categories"&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;h3&gt;
  
  
  What Makes These Tools Special
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Unified Search &amp;amp; Geocoding&lt;/strong&gt;: The &lt;code&gt;search_and_geocode_tool&lt;/code&gt; consolidates forward geocoding and POI search into one powerful tool. It handles everything from "123 Main St" to "Starbucks near Times Square" to "Eiffel Tower."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Traffic-Aware Routing&lt;/strong&gt;: The &lt;code&gt;directions_tool&lt;/code&gt; supports real-time traffic data, alternative routes, and can optimize for different transportation modes (driving, walking, cycling).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Multi-Point Optimization&lt;/strong&gt;: The &lt;code&gt;matrix_tool&lt;/code&gt; efficiently calculates travel times between dozens of locations which is essential for logistics, delivery planning, and service area analysis.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reachability Analysis&lt;/strong&gt;: The &lt;code&gt;isochrone_tool&lt;/code&gt; generates polygons showing areas reachable within specific time or distance thresholds which is perfect for analyzing coverage areas, commute zones, or delivery ranges.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Visual Output&lt;/strong&gt;: The &lt;code&gt;static_image_tool&lt;/code&gt; creates customizable map images with markers, routes, and overlays which is great for reports, notifications, or sharing with users.&lt;/p&gt;
&lt;h2&gt;
  
  
  Getting Started: Setup Options
&lt;/h2&gt;

&lt;p&gt;You have two ways to use the Mapbox MCP Server:&lt;/p&gt;
&lt;h3&gt;
  
  
  Option 1: Hosted Server (Easiest to Get Started)
&lt;/h3&gt;

&lt;p&gt;Mapbox provides a hosted MCP endpoint at &lt;strong&gt;&lt;code&gt;https://mcp.mapbox.com/mcp&lt;/code&gt;&lt;/strong&gt; that's always available with no installation required!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Authentication:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Interactive clients&lt;/strong&gt; (Claude Desktop, VS Code): Uses OAuth flow with browser-based login&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Programmatic access&lt;/strong&gt;: Uses Bearer token authentication with your Mapbox access token&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Setup:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For interactive tools like Claude Desktop, VS Code, or Cursor, add this to your MCP settings (typically in &lt;code&gt;settings.json&lt;/code&gt; or similar config file):&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;"mcpServers"&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;"mapbox"&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;"https://mcp.mapbox.com/mcp"&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;&lt;strong&gt;For Claude Desktop specifically&lt;/strong&gt;, the config file is located at:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;macOS&lt;/strong&gt;: &lt;code&gt;~/Library/Application Support/Claude/claude_desktop_config.json&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Windows&lt;/strong&gt;: &lt;code&gt;%APPDATA%\Claude\claude_desktop_config.json&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The first time you connect, a browser window will open asking you to log in to your Mapbox account and authorize access via OAuth.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For programmatic access&lt;/strong&gt; (like in agent frameworks), you'll need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A Mapbox account (free tier available) - &lt;a href="https://account.mapbox.com/auth/signup/" rel="noopener noreferrer"&gt;Sign up here&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Your Mapbox access token from &lt;a href="https://account.mapbox.com/access-tokens/" rel="noopener noreferrer"&gt;your account dashboard&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then use Bearer token authentication in your code (see framework-specific tutorials for examples).&lt;/p&gt;

&lt;p&gt;Check the &lt;a href="https://github.com/mapbox/mcp-server/blob/main/docs/hosted-mcp-guide.md" rel="noopener noreferrer"&gt;Hosted MCP Server Guide&lt;/a&gt; for detailed setup instructions for different clients.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;No local setup or dependencies required&lt;/li&gt;
&lt;li&gt;Managed infrastructure by Mapbox&lt;/li&gt;
&lt;li&gt;Always available&lt;/li&gt;
&lt;li&gt;Automatic updates&lt;/li&gt;
&lt;li&gt;Perfect for getting started quickly&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;Additional network hop (your machine → hosted MCP → Mapbox APIs)&lt;/li&gt;
&lt;li&gt;Requires stable internet connection&lt;/li&gt;
&lt;li&gt;Less control over configuration&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Option 2: Local Server (For Development &amp;amp; Customization)
&lt;/h3&gt;

&lt;p&gt;Run the MCP server as a local process on your machine. This gives you full control and is ideal for development, debugging, or customization.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Prerequisites:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Node.js LTS+ (for npx method) OR Python 3.10+ with uv (for uvx method)&lt;/li&gt;
&lt;li&gt;A Mapbox account (free tier available) - &lt;a href="https://account.mapbox.com/auth/signup/" rel="noopener noreferrer"&gt;Sign up here&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Your Mapbox access token from &lt;a href="https://account.mapbox.com/access-tokens/" rel="noopener noreferrer"&gt;your account dashboard&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Setup:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For interactive tools like Claude Desktop, VS Code, or Cursor, add this to your MCP settings:&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;"mcpServers"&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;"mapbox"&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;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&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;"-y"&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/mcp-server"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"env"&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;"MAPBOX_ACCESS_TOKEN"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"your_token_here"&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;&lt;strong&gt;For Claude Desktop specifically&lt;/strong&gt;, the config file is located at:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;macOS&lt;/strong&gt;: &lt;code&gt;~/Library/Application Support/Claude/claude_desktop_config.json&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Windows&lt;/strong&gt;: &lt;code&gt;%APPDATA%\Claude\claude_desktop_config.json&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;For command-line testing&lt;/strong&gt;, you can run the server directly:&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="c"&gt;# Set your access token&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;MAPBOX_ACCESS_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"your_token_here"&lt;/span&gt;

&lt;span class="c"&gt;# Run with npx (recommended)&lt;/span&gt;
npx &lt;span class="nt"&gt;-y&lt;/span&gt; @mapbox/mcp-server

&lt;span class="c"&gt;# OR run with uvx (Python users)&lt;/span&gt;
pip &lt;span class="nb"&gt;install &lt;/span&gt;uv
uvx mapbox-mcp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Test it with MCP Inspector:&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 @modelcontextprotocol/inspector npx &lt;span class="nt"&gt;-y&lt;/span&gt; @mapbox/mcp-server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This opens a web interface where you can explore available tools and test them interactively.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Full control over the server process&lt;/li&gt;
&lt;li&gt;Lower latency (direct connection to Mapbox APIs)&lt;/li&gt;
&lt;li&gt;Works with any Mapbox token&lt;/li&gt;
&lt;li&gt;Can customize or extend if needed&lt;/li&gt;
&lt;li&gt;Better for development and debugging&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;Requires Node.js or Python tooling&lt;/li&gt;
&lt;li&gt;Server process management&lt;/li&gt;
&lt;li&gt;Must ensure dependencies are installed&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Example Use Cases and Prompts
&lt;/h2&gt;

&lt;p&gt;Once connected, your agents can handle natural language requests like these:&lt;/p&gt;

&lt;h3&gt;
  
  
  Location Discovery
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;"Find coffee shops within walking distance of the Empire State Building"&lt;/li&gt;
&lt;li&gt;"Show me gas stations along the route from Boston to New York"&lt;/li&gt;
&lt;li&gt;"What restaurants are near Times Square?"&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Navigation &amp;amp; Travel
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;"Get driving directions from LAX to Hollywood with current traffic"&lt;/li&gt;
&lt;li&gt;"How long would it take to walk from Central Park to Times Square?"&lt;/li&gt;
&lt;li&gt;"Calculate travel time from my hotel (Four Seasons) to JFK Airport by taxi"&lt;/li&gt;
&lt;li&gt;"What's the fastest route visiting Salesforce Tower, Twitter HQ, and Uber headquarters?"&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Visualization &amp;amp; Maps
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;"Create a map image showing the route from Golden Gate Bridge to Fisherman's Wharf"&lt;/li&gt;
&lt;li&gt;"Show me a satellite view of Manhattan with key landmarks marked"&lt;/li&gt;
&lt;li&gt;"Generate a map highlighting all Starbucks locations within a mile of downtown Seattle"&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Analysis &amp;amp; Planning
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;"Show me areas reachable within 30 minutes of downtown Portland by car"&lt;/li&gt;
&lt;li&gt;"Calculate a travel time matrix between 3 hotel locations and the convention center"&lt;/li&gt;
&lt;li&gt;"Find the optimal route visiting 3 tourist attractions in San Francisco"&lt;/li&gt;
&lt;li&gt;"I'm staying at the Fairmont. Show me everywhere I can reach by walking in 15 minutes"&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Behind the Scenes
&lt;/h3&gt;

&lt;p&gt;When you ask: &lt;em&gt;"How long does it take to drive from the Space Needle to Pike Place Market?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The agent automatically:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Uses &lt;code&gt;search_and_geocode_tool&lt;/code&gt; to find Space Needle coordinates&lt;/li&gt;
&lt;li&gt;Uses &lt;code&gt;search_and_geocode_tool&lt;/code&gt; to find Pike Place Market coordinates&lt;/li&gt;
&lt;li&gt;Calls &lt;code&gt;directions_tool&lt;/code&gt; with driving profile&lt;/li&gt;
&lt;li&gt;Returns: "約 8 minutes, 1.2 miles"&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;No manual API calls, no coordinate formatting, no error handling as the agent figures it all out.&lt;/p&gt;

&lt;h2&gt;
  
  
  Supported Frameworks and Tools
&lt;/h2&gt;

&lt;p&gt;The Mapbox MCP Server works with any MCP-compatible client, including:&lt;/p&gt;

&lt;h3&gt;
  
  
  AI Agent Frameworks
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;CrewAI&lt;/strong&gt; (Python) - Multi-agent collaboration&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LangGraph&lt;/strong&gt; (Python) - Stateful agent workflows&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pydantic AI&lt;/strong&gt; (Python) - Type-safe agent development&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mastra&lt;/strong&gt; (TypeScript) - Production AI applications&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Smolagents&lt;/strong&gt; (Python) - Lightweight agents from Hugging Face&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Development Tools
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Claude Desktop&lt;/strong&gt; - Anthropic's AI assistant&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ChatGPT&lt;/strong&gt; - OpenAI's desktop application&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gemini CLI&lt;/strong&gt; - Google's command-line tool&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;VS Code&lt;/strong&gt; - Via MCP extensions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cursor&lt;/strong&gt; - AI-powered IDE&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Goose&lt;/strong&gt; - Terminal-based AI assistant&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Integration Approaches
&lt;/h3&gt;

&lt;p&gt;Each framework has slightly different integration patterns, but they all follow the same basic flow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Configure the MCP server connection&lt;/li&gt;
&lt;li&gt;Add the configuration to your agent&lt;/li&gt;
&lt;li&gt;The agent automatically discovers available tools&lt;/li&gt;
&lt;li&gt;Use natural language to invoke tools&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We'll be publishing detailed tutorials for each major framework. Stay tuned for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;CrewAI&lt;/strong&gt;: Building multi-agent location-aware systems&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pydantic AI&lt;/strong&gt;: Type-safe location intelligence&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Smolagents&lt;/strong&gt;: Lightweight location-enabled agents&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mastra&lt;/strong&gt;: Production TypeScript agents with Mapbox&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Key Advantages of Using MCP
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;No Custom API Integration&lt;/strong&gt;: Traditional approach requires writing geocoding logic, formatting coordinates, handling errors, chaining API calls. With MCP, you just describe what you want in natural language.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Automatic Tool Discovery&lt;/strong&gt;: The MCP server exposes its tools in a standardized format. Your agent discovers available tools automatically and no manual configuration of each tool.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Framework Agnostic&lt;/strong&gt;: Write once, use everywhere. The same Mapbox MCP server works with CrewAI, LangGraph, GitHub Copilot, Claude Desktop, and any other MCP-compatible client.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Natural Language Interface&lt;/strong&gt;: Agents interpret your task descriptions and choose the right tools. Say "plan a route visiting three restaurants" and the agent figures out it needs to geocode, then calculate directions, then optimize the order.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Intelligent Tool Chaining&lt;/strong&gt;: Agents understand dependencies between tools. They know addresses must be geocoded before calculating routes, and they handle this automatically.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-World Applications
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Travel &amp;amp; Hospitality
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Hotel concierge bots that provide directions and local recommendations&lt;/li&gt;
&lt;li&gt;Trip planning assistants that optimize multi-stop itineraries&lt;/li&gt;
&lt;li&gt;Event coordinators calculating travel times for attendees&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Logistics &amp;amp; Delivery
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Route optimization for delivery fleets&lt;/li&gt;
&lt;li&gt;Service area analysis for expansion planning&lt;/li&gt;
&lt;li&gt;Warehouse location analysis based on customer distribution&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Real Estate
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Commute time calculators for property listings&lt;/li&gt;
&lt;li&gt;School district and amenity proximity analysis&lt;/li&gt;
&lt;li&gt;Neighborhood boundary visualization&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Field Services
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Technician dispatch optimization&lt;/li&gt;
&lt;li&gt;Service area coverage analysis&lt;/li&gt;
&lt;li&gt;Emergency response time calculations&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Location-Based Marketing
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Store locators with accurate directions&lt;/li&gt;
&lt;li&gt;Coverage area mapping for service offerings&lt;/li&gt;
&lt;li&gt;Competitive location analysis&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Getting Help and Resources
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Documentation
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/mapbox/mcp-server" rel="noopener noreferrer"&gt;Mapbox MCP Server GitHub&lt;/a&gt; - Source code and documentation&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/mapbox/mcp-server/blob/main/docs/hosted-mcp-guide.md" rel="noopener noreferrer"&gt;Hosted MCP Server Guide&lt;/a&gt; - Setup for different clients&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://modelcontextprotocol.io/" rel="noopener noreferrer"&gt;Model Context Protocol Spec&lt;/a&gt; - MCP standard documentation&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.mapbox.com/api/" rel="noopener noreferrer"&gt;Mapbox API Docs&lt;/a&gt; - Underlying API reference&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Support
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Email&lt;/strong&gt;: &lt;a href="mailto:mcp-feedback@mapbox.com"&gt;mcp-feedback@mapbox.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Issues&lt;/strong&gt;: &lt;a href="https://github.com/mapbox/mcp-server/issues" rel="noopener noreferrer"&gt;GitHub Issues&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Community&lt;/strong&gt;: Join discussions in the MCP community&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Pricing and Rate Limits
&lt;/h2&gt;

&lt;p&gt;The Mapbox MCP Server uses your Mapbox account and access token. Pricing follows standard Mapbox API rates:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Geocoding&lt;/strong&gt;: 100,000 requests/month free&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Directions&lt;/strong&gt;: 5,000 requests/month free&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Matrix&lt;/strong&gt;: Counts as multiple requests (origins × destinations)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Static Maps&lt;/strong&gt;: 50,000 requests/month free&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The free tier is generous for development and small projects. For production use, check &lt;a href="https://www.mapbox.com/pricing/" rel="noopener noreferrer"&gt;Mapbox pricing&lt;/a&gt; for details.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next Steps
&lt;/h2&gt;

&lt;p&gt;Ready to add location intelligence to your AI agents?&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Sign up for Mapbox&lt;/strong&gt;: Get your free access token at &lt;a href="https://account.mapbox.com/auth/signup/" rel="noopener noreferrer"&gt;mapbox.com/signup&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Try the MCP Inspector&lt;/strong&gt;: Test the server and explore available tools&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pick a framework&lt;/strong&gt;: Start with CrewAI, LangGraph, or your preferred agent framework&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build something&lt;/strong&gt;: Check out our framework-specific tutorials (coming soon!)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In our next post, we'll dive deep into building a multi-agent travel planning system with CrewAI and Mapbox MCP, complete with working code you can run and customize.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Also coming soon:&lt;/strong&gt; A guide to the &lt;a href="https://github.com/mapbox/mcp-devkit-server" rel="noopener noreferrer"&gt;Mapbox DevKit MCP Server&lt;/a&gt;, which helps AI assistants write Mapbox code by providing documentation, examples, and API guidance which is perfect for developers building Mapbox applications.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Have questions or want to share what you're building? Reach out to &lt;a href="mailto:mcp-feedback@mapbox.com"&gt;mcp-feedback@mapbox.com&lt;/a&gt; or open an issue on &lt;a href="https://github.com/mapbox/mcp-server/issues" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>tutorial</category>
      <category>python</category>
    </item>
    <item>
      <title>Build quickly with npm create @mapbox/web-app</title>
      <dc:creator>Andrew Sepic</dc:creator>
      <pubDate>Fri, 12 Dec 2025 15:45:44 +0000</pubDate>
      <link>https://forem.com/mapbox/build-quickly-with-npm-create-mapboxweb-app-d98</link>
      <guid>https://forem.com/mapbox/build-quickly-with-npm-create-mapboxweb-app-d98</guid>
      <description>&lt;p&gt;The Mapbox Developer Relations team recently released a new developer tool to help web developers quickly scaffold &lt;a href="https://docs.mapbox.com/mapbox-gl-js/" rel="noopener noreferrer"&gt;Mapbox GL JS&lt;/a&gt; into their favorite frontend frameworks.  You can now go from zero to hero with a map in your app in about 10 seconds. 🚀&lt;/p&gt;

&lt;p&gt;To get started, ensure you have &lt;a href="https://npmjs.org" rel="noopener noreferrer"&gt;&lt;code&gt;npm&lt;/code&gt;&lt;/a&gt; installed and run the command in your terminal.&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="nv"&gt;$ &lt;/span&gt;npm create @mapbox/web-app 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(Requires &lt;a href="https://nodejs.org" rel="noopener noreferrer"&gt;Node.js&lt;/a&gt; &lt;code&gt;v20+&lt;/code&gt;.)&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this is helpful
&lt;/h2&gt;

&lt;p&gt;If you work with &lt;a href="https://docs.mapbox.com/mapbox-gl-js/" rel="noopener noreferrer"&gt;Mapbox GL JS&lt;/a&gt; in modern JavaScript frameworks, you'll know that spinning up a map in your app can take more time and effort than you'd like.  This tool abstracts away project configuration, dependency management and lets developers get a simple map running very quickly.  This allows you to test ideas &amp;amp; features fast, without having to clone a local repo or project as a starting point.  &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%2Fff5vto5jehy9xhb9y1o0.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%2Fff5vto5jehy9xhb9y1o0.png" alt=" " width="800" height="443"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;npm create @mapbox/web-app&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;npm create @mapbox/web-app&lt;/code&gt; package is a CLI tool that uses &lt;a href="https://vite.dev" rel="noopener noreferrer"&gt;Vite&lt;/a&gt; templates to quickly build a project into the following frameworks: &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Vanilla JS (with &amp;amp; without a bundler)&lt;/li&gt;
&lt;li&gt;React&lt;/li&gt;
&lt;li&gt;Vue&lt;/li&gt;
&lt;li&gt;Svelte&lt;/li&gt;
&lt;li&gt;Angular&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You can also also add interactive search to your map by adding &lt;a href="https://docs.mapbox.com/mapbox-search-js" rel="noopener noreferrer"&gt;Mapbox Search JS&lt;/a&gt; via the install prompts.  &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The CLI tooling asks 4 questions:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Which framework do you want to use? &lt;/li&gt;
&lt;li&gt;Choose a project name..&lt;/li&gt;
&lt;li&gt;Access Token - Insert your public Mapbox access token and the tooling will pass this into the appropriate local &lt;code&gt;.env&lt;/code&gt; file. Get yours from your &lt;a href="https://console.mapbox.com/" rel="noopener noreferrer"&gt;developer console&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;Add Search JS (or not)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;After answering those questions you'll have a full-screen Mapbox GL JS map loading in the browser within seconds, ready for you to customize. 🔥&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%2Ffthgzgg4xpuwfofyato6.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%2Ffthgzgg4xpuwfofyato6.png" alt=" " width="800" height="525"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  Next Steps
&lt;/h2&gt;

&lt;p&gt;Now that you have a full screen map loaded, where do you want to go next?  If you are a new developer using Mapbox, you can expand this simple application by following one of these tutorials: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.mapbox.com/help/tutorials/use-mapbox-gl-js-with-react/" rel="noopener noreferrer"&gt;Use Mapbox GL JS in a React app&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.mapbox.com/help/tutorials/use-mapbox-gl-js-with-svelte/" rel="noopener noreferrer"&gt;Use Mapbox GL JS in a Svelte app&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.mapbox.com/help/tutorials/use-mapbox-gl-js-with-vue/" rel="noopener noreferrer"&gt;Use Mapbox GL JS in a Vue app&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.mapbox.com/help/tutorials/use-mapbox-gl-js-with-angular/" rel="noopener noreferrer"&gt;Use Mapbox GL JS in an Angular app&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The tutorials above demonstrate state management best practices for these frameworks, how to track &amp;amp; display map state into the application UI and how to trigger map events from the UI. &lt;/p&gt;

&lt;p&gt;If you are an experienced developer and are familiar with Mapbox products, you can browse our &lt;a href="https://docs.mapbox.com/help/tutorials/" rel="noopener noreferrer"&gt;tutorials&lt;/a&gt; for inspiration or search them for your use case.  &lt;/p&gt;

&lt;h2&gt;
  
  
  More Information on Github
&lt;/h2&gt;

&lt;p&gt;You can find more information about this CLI tool and report issues on &lt;a href="https://github.com/mapbox/create-web-app" rel="noopener noreferrer"&gt;Github&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>tooling</category>
      <category>npm</category>
      <category>javascript</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
