All skills
mapbox avatar

/mapbox-search-integration

@65bab68 official
by mapboxmapbox/mapbox-agent-skills80 stars
17

Complete workflow for implementing Mapbox search in applications - from discovery questions to production-ready integration with best practices

Use this Skill: https://skilld.dev/gh/mapbox/mapbox-agent-skills/mapbox-search-integration

This session only. Nothing lands on disk.

referencesbest-practices.md

≈1.7k tokens on demand. Your agent reads this file only when SKILL.md points to it.

Best Practices: "The Good Parts"

1. Debouncing (CRITICAL for Autocomplete)

Note: Debouncing is only a concern if you are calling the API directly. Mapbox Search JS and the Search SDKs handle debouncing automatically.

Problem: Every keystroke = API call = expensive + slow

Solution: Wait until user stops typing (for direct API integration)

let debounceTimeout;

function debouncedSearch(query) {
  clearTimeout(debounceTimeout);

  debounceTimeout = setTimeout(() => {
    performSearch(query);
  }, 300); // 300ms is optimal for most use cases
}

Why 300ms?

  • Fast enough to feel responsive
  • Slow enough to avoid spam
  • Industry standard (Google uses ~300ms)

2. Session Token Management

Note: Session tokens are only a concern if you are calling the API directly. Mapbox Search JS and the Search SDKs for iOS/Android handle session tokens automatically.

Problem: Search Box API charges per session, not per request

What's a session?

  • Starts with first suggest request
  • Ends with retrieve request
  • Use same token for all requests in session

Implementation (direct API calls only):

class SearchSession {
  constructor() {
    this.token = this.generateToken();
  }

  generateToken() {
    return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
  }

  async suggest(query) {
    // Use this.token for all suggest requests
    return fetch(`...?session_token=${this.token}`);
  }

  async retrieve(id) {
    const result = await fetch(`...?session_token=${this.token}`);
    // Session ends - generate new token
    this.token = this.generateToken();
    return result;
  }
}

Cost impact:

  • ✅ Correct: 1 session = unlimited suggests + 1 retrieve = 1 charge
  • ❌ Wrong: No session token = each request charged separately

3. Geographic Filtering

Always set location context when possible:

// GOOD: Specific country
{
  country: 'US';
}

// GOOD: Proximity to user
{
  proximity: [-122.4194, 37.7749];
}

// GOOD: Bounding box for service area
{
  bbox: [-122.5, 37.7, -122.3, 37.9];
}

// BAD: No geographic context
{
} // Returns global results, slower, less relevant

Tip: Use the Location Helper tool to easily calculate bounding boxes for your service area.

Why it matters:

  • ✅ Better result relevance
  • ✅ Faster response times
  • ✅ Lower ambiguity
  • ✅ Better user experience

4. Error Handling

Handle all failure cases:

async function performSearch(query) {
  try {
    const response = await fetch(searchUrl);

    // Check HTTP status
    if (!response.ok) {
      if (response.status === 429) {
        // Rate limited
        showError('Too many requests. Please wait a moment.');
        return [];
      } else if (response.status === 401) {
        // Invalid token
        showError('Search is unavailable. Please check configuration.');
        return [];
      } else {
        // Other error
        showError('Search failed. Please try again.');
        return [];
      }
    }

    const data = await response.json();

    // Check for results
    if (!data.suggestions || data.suggestions.length === 0) {
      showMessage('No results found. Try a different search.');
      return [];
    }

    return data.suggestions;
  } catch (error) {
    // Network error
    console.error('Search error:', error);
    showError('Network error. Please check your connection.');
    return [];
  }
}

5. Result Display UX

Show enough context for disambiguation:

<div class="search-result">
  <div class="result-name">Starbucks</div>
  <div class="result-address">123 Main St, San Francisco, CA</div>
  <div class="result-type">Coffee Shop</div>
</div>

Not just:

<div>Starbucks</div>
<!-- Which Starbucks? -->

6. Loading States

Always show loading feedback:

function performSearch(query) {
  showLoadingSpinner();

  fetch(searchUrl)
    .then((response) => response.json())
    .then((data) => {
      hideLoadingSpinner();
      displayResults(data.suggestions);
    })
    .catch((error) => {
      hideLoadingSpinner();
      showError('Search failed');
    });
}

7. Accessibility

Make search keyboard-navigable:

<input type="search" role="combobox" aria-autocomplete="list" aria-controls="search-results" aria-expanded="false" />

<ul id="search-results" role="listbox">
  <li role="option" tabindex="0">Result 1</li>
  <li role="option" tabindex="0">Result 2</li>
</ul>

Keyboard support:

  • ⬆️⬇️ Arrow keys: Navigate results
  • Enter: Select result
  • Escape: Close results

8. Mobile Optimizations

iOS/Android specific considerations:

// iOS: Adjust for keyboard
NotificationCenter.default.addObserver(
    forName: UIResponder.keyboardWillShowNotification,
    object: nil,
    queue: .main
) { notification in
    // Adjust view for keyboard
}

// Handle tap outside to dismiss
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(dismissKeyboard))
view.addGestureRecognizer(tapGesture)

Make touch targets large enough:

  • Minimum: 44x44pt (iOS) / 48x48dp (Android)
  • Ensure adequate spacing between results

9. Caching (For High-Volume Apps)

Cache recent/popular searches:

class SearchCache {
  constructor(maxSize = 50) {
    this.cache = new Map();
    this.maxSize = maxSize;
  }

  get(query) {
    const key = query.toLowerCase();
    return this.cache.get(key);
  }

  set(query, results) {
    const key = query.toLowerCase();

    // LRU eviction
    if (this.cache.size >= this.maxSize) {
      const firstKey = this.cache.keys().next().value;
      this.cache.delete(firstKey);
    }

    this.cache.set(key, {
      results,
      timestamp: Date.now()
    });
  }

  isValid(entry, maxAgeMs = 5 * 60 * 1000) {
    return entry && Date.now() - entry.timestamp < maxAgeMs;
  }
}

// Usage
const cache = new SearchCache();

async function search(query) {
  const cached = cache.get(query);
  if (cache.isValid(cached)) {
    return cached.results;
  }

  const results = await performAPISearch(query);
  cache.set(query, results);
  return results;
}

10. Token Security

CRITICAL: Scope tokens properly:

// Create token with only search scopes
// In Mapbox dashboard or via API:
{
  "scopes": [
    "search:read",
    "styles:read",  // Only if showing map
    "fonts:read"    // Only if showing map
  ],
  "allowedUrls": [
    "https://yourdomain.com/*"
  ]
}

Never:

  • ❌ Use secret tokens (sk.*) in client-side code
  • ❌ Give tokens more scopes than needed
  • ❌ Skip URL restrictions on public tokens

See mapbox-token-security skill for details.

Source: SKILL.md on GitHub

No alerts15d5 checks · Risk SAFE
  • Gen Agent Trust Hub15d

    The mapbox-search-integration skill is a professional implementation guide for Mapbox search services. It adheres to security best practices, uses official Mapbox SDKs and APIs, and provides clear guidance on secure credential handling.

  • Socket15d

    No alerts

  • Snyk15d

    Risk: LOW · No issues

  • Runlayer6mo

    1/1 file flagged

  • ZeroLeaks5mo

    Score: 93/100 · 2 sections analyzed

Signed by skilld at 65bab68. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 6 hours ago.

Activeupdated 6 months ago
  • API
  • React
  • mapbox
  • search
  • geocoding
  • location
  • web
  • ios
  • android
  • autocomplete

README badge

README badge for mapbox/mapbox-agent-skills/mapbox-search-integration

Guides implementation of Mapbox search through discovery questions, product selection, and production-ready integration patterns. Covers Search Box API for interactive address and POI search, Geocoding API for batch geocoding, and platform-specific SDKs for web, React, iOS, and Android.

Generated from the current SKILL.md.

Should I use the Search Box API or Geocoding API?
Use Search Box API for all interactive/user-facing search, including address search, autocomplete, and POI search. Use Geocoding API only for batch server-side geocoding or maintaining an existing Geocoding API integration.
What's the difference between Search JS React, Search JS Web, and Search JS Core?
Search JS React is a React library with built-in UI (easiest for React apps). Search JS Web is a Web Components library with UI for vanilla JS. Search JS Core is a JavaScript wrapper for the API where you build your own UI.
Do I need to implement debouncing and session tokens?
Yes. Debouncing (300ms recommended) reduces API calls during typing, and session tokens are required for proper billing on every suggest/retrieve request in interactive flows.
Which platform SDK should I use?
Use Mapbox Search JS for web apps (with React/Web/Core variants), Search SDK for iOS for native iOS apps, and Search SDK for Android for native Android apps. Avoid direct API calls in favor of SDKs to handle debouncing and session management automatically.
What geographic filtering options are available?
You can use the country parameter for single/multiple countries, bbox parameter for bounding box constraints, and proximity parameter for location-based filtering.

Generated from the current SKILL.md. These answers refresh after source changes.