
Mapbox Search Integration
- 1.3k installs
- 71 repo stars
- Updated August 4, 2026
- mapbox/mapbox-agent-skills
mapbox-search-integration provides documented workflows for Complete workflow for implementing Mapbox search in applications - from discovery questions to production-ready integration with best practices
About
The mapbox-search-integration skill complete workflow for implementing Mapbox search in applications - from discovery questions to production-ready integration with best practices # Mapbox Search Integration Skill Expert guidance for implementing Mapbox search functionality in applications. Covers the complete workflow from asking the right discovery questions, selecting the appropriate search product, to implementing production-ready integrations following best practices from the Mapbox search team. ## Use This Skill When User says things like: - "I need to add search to my map" - "I need a search bar for my mapping app" - "How do I implement location search?" - "I want users to search for places/addresses" - "I need geocoding in my application" **This skill complements `mapbox-search-patterns`:** - `mapbox-search-patterns` = Tool and parameter selection - `mapbox-search-integration` = Complete implementation workflow ## Discovery Phase: Ask the Right Questions Before jumping into code, ask these questions to understand requirements: ### Question 1: What are users searching for? **Ask:** "What do you want users to search for?" **Common answers and implications:** - **"Addresses"*.
- "I need to add search to my map"
- "I need a search bar for my mapping app"
- "How do I implement location search?"
- "I want users to search for places/addresses"
- "I need geocoding in my application"
Mapbox Search Integration by the numbers
- 1,302 all-time installs (skills.sh)
- +40 installs in the week ending Aug 5, 2026 (Skillselion tracking)
- Ranked #344 of 2,203 Security skills by installs in the Skillselion catalog
- Security screen: LOW risk (skills.sh audit)
- Data as of Aug 5, 2026 (Skillselion catalog sync)
mapbox-search-integration capabilities & compatibility
- Capabilities
- "i need to add search to my map" · "i need a search bar for my mapping app" · "how do i implement location search?" · "i want users to search for places/addresses" · "i need geocoding in my application"
- Use cases
- documentation
What mapbox-search-integration says it does
# Mapbox Search Integration Skill Expert guidance for implementing Mapbox search functionality in applications.
Covers the complete workflow from asking the right discovery questions, selecting the appropriate search product, to implementing production-ready integrations following best practices from the Mapbox
npx skills add https://github.com/mapbox/mapbox-agent-skills --skill mapbox-search-integrationAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 1.3k |
|---|---|
| repo stars | ★ 71 |
| Security audit | 3 / 3 scanners passed |
| Last updated | August 4, 2026 |
| Repository | mapbox/mapbox-agent-skills ↗ |
How do I use mapbox-search-integration for the task described in its SKILL.md triggers?
Complete workflow for implementing Mapbox search in applications - from discovery questions to production-ready integration with best practices
Who is it for?
Teams invoking mapbox-search-integration when the user request matches documented triggers and prerequisites.
Skip if: Skip when cached docs are missing, the request is a negative trigger, or another sibling skill owns the workflow.
When should I use this skill?
Complete workflow for implementing Mapbox search in applications - from discovery questions to production-ready integration with best practices
What you get
Step-by-step guidance grounded in mapbox-search-integration documentation and reference files.
- searchbox react integration
- flyto navigation handler
Files
Mapbox Search Integration Skill
Expert guidance for implementing Mapbox search functionality in applications. Covers the complete workflow from asking the right discovery questions, selecting the appropriate search product, to implementing production-ready integrations following best practices from the Mapbox search team.
Use This Skill When
User says things like:
- "I need to add search to my map"
- "I need a search bar for my mapping app"
- "How do I implement location search?"
- "I want users to search for places/addresses"
- "I need geocoding in my application"
This skill complements `mapbox-search-patterns`:
mapbox-search-patterns= Tool and parameter selectionmapbox-search-integration= Complete implementation workflow
Discovery Phase: Ask the Right Questions
Before jumping into code, ask these questions to understand requirements:
Question 1: What are users searching for?
Ask: "What do you want users to search for?"
Common answers and implications:
- "Addresses" → Use Search Box API (the default for interactive address search, including geocoding). Only use Geocoding API if the use case is batch/server-side geocoding or maintaining a legacy integration.
- "Points of interest / businesses" → POI search, use Search Box API with category search
- "Both addresses and POIs" → Search Box API
- "Specific types of POIs" (restaurants, hotels, etc.) → Search Box API
- "Countries, cities, postcodes or neighborhoods" → Search Box API for interactive search; Geocoding API only for batch/server-side geocoding
- "Custom locations" (user-created places) → May need custom data + search integration
Follow-up if not stated initially: "Are your users searching for points of interest data? Restaurants, stores, categories of businesses?"
Implications:
- "Yes, POIs are included" → Use the Search Box API
- "No, the user does not need POI search" → Still default to Search Box API for interactive/autocomplete use cases. Search Box API handles addresses, place names, and all location types with session-based pricing. Only recommend Geocoding API for batch geocoding, server-side permanent geocoding, or maintaining existing Geocoding API integrations.
Question 2: What's the geographic scope?
Ask: "Where will users be searching?"
Common answers and implications:
- "Single country" (e.g., "only USA") → Use
countryparameter, better results, lower cost - "Specific region" → Use
bboxparameter for bounding box constraint - "Global" → No country restriction, but may need language parameter
- "Multiple specific countries" → Use
countryarray parameter
Follow-up: "Do you need to limit results to a specific area?" (delivery zone, service area, etc.)
Question 3: What's the search interaction pattern?
Ask: "How will users interact with search?"
Common answers and implications:
- "Search-as-you-type / autocomplete" → Use Search Box API with
auto_complete: trueand session-based pricing (most cost-efficient for autocomplete). Implement debouncing. - "Search button / final query" → Can use either API, no autocomplete needed
- "Both" (autocomplete + refine) → Two-stage search, autocomplete then detailed results
- "Voice input" → Consider speech-to-text integration, handle longer queries
Question 4: What platform?
Ask: "What platform is this for?"
Common answers and implications:
- "Web application" → Mapbox Search JS (easiest), or direct API calls for advanced cases
- "iOS app" → Search SDK for iOS (recommended), or direct API integration for advanced cases
- "Android app" → Search SDK for Android (recommended), or direct API integration for advanced cases
- "Multiple platforms" → Platform-specific SDKs (recommended), or direct API approach for consistency
- "React app" → Mapbox Search JS React (easiest with UI), or Search JS Core for custom UI. Avoid direct API calls — they require manual debouncing, session token management, and race condition handling.
- "Vue / Angular / Other framework" → Mapbox Search JS Core or Web. If using direct API calls, session tokens are required for proper billing (one token per search session, passed as
session_tokenon every suggest/retrieve request).
Question 5: How will results be used?
Ask: "What happens when a user selects a result?"
Common answers and implications:
- "Fly to location on map" → Need coordinates, map integration
- "Show details / info" → Need to retrieve and display result properties
- "Fill form fields" → Need to parse address components
- "Start navigation" → Need coordinates, integrate with directions
- "Multiple selection" → Need to handle selection state, possibly show markers
Question 6: Expected usage volume?
Ask: "How many searches do you expect per month?"
Implications:
- Low volume (< 10k) → Free tier sufficient, simple implementation
- Medium volume (10k-100k) → Consider caching, optimize API calls
- High volume (> 100k) → Implement debouncing, caching, batch operations, monitor costs
Product Selection Decision Tree
Based on discovery answers, recommend the right product:
Key principle: Search Box API is the default choice for virtually all interactive search use cases, including address search, geocoding, autocomplete, and POI search. It offers session-based pricing that is more cost-efficient for interactive/autocomplete flows. Only recommend Geocoding API for the narrow cases listed below.
Search Box API (DEFAULT)
Use when (any of these):
- User needs interactive address search or autocomplete (this IS geocoding — Search Box API handles it)
- User needs POI / category search
- User needs any end-user-facing search UI
- User wants session-based pricing (more cost-efficient for autocomplete/interactive use)
- User is building a web, iOS, or Android app with a search bar
Prefer SDKs over direct API calls for web integration:
- Mapbox Search JS (SDK) - Recommended for web integration, with three components:
- Search JS React - Easy search integration via React library with UI
- Search JS Web - Easy search integration via Web Components with UI
- Search JS Core - JavaScript (node or web) wrapper for API, build your own UI
- Search Box API (REST) - Direct API integration, for advanced/custom cases
- Search SDK for iOS - Native iOS integration
- Search SDK for Android - Native Android integration
Geocoding API (SPECIALIZED)
Use ONLY when:
- Batch geocoding large lists of addresses (server-side)
- Permanent/stored geocoding results (server-side, where results are persisted)
- Maintaining an existing Geocoding API integration (migration not justified)
- No interactive/user-facing search needed
Do NOT recommend Geocoding API when:
- The user wants a search bar, autocomplete, or interactive address lookup — use Search Box API instead
- The user says "geocoding" but describes an interactive search flow — use Search Box API instead
Reference Files
Load the relevant reference based on the user's platform and needs:
- Web (Search JS React / Web / Core / Direct API) → Load
references/web-search-js.md - When: User is building a web app (vanilla JS, any framework except React-specific patterns)
- React Integration → Load
references/react-search.md - When: User is building a React app specifically
- iOS → Load
references/ios-search.md - When: User is building an iOS app (Swift/UIKit/SwiftUI)
- Android → Load
references/android-search.md - When: User is building an Android app (Kotlin/Java)
- Node.js → Load
references/nodejs-search.md - When: User needs server-side search (Express, serverless, backend API)
- Best Practices → Load
references/best-practices.md - When: Implementing search for the first time, or optimizing an existing implementation
- Covers: debouncing, session tokens, geographic filtering, error handling, accessibility, caching, token security
- Common Pitfalls → Load
references/pitfalls.md - When: Debugging issues, reviewing code, or during code review
- Covers: no debouncing, missing session tokens, no geo context, poor mobile UX, race conditions
- Framework Hooks → Load
references/framework-hooks.md - When: Building custom hooks (React) or composables (Vue) around Search JS Core
- Testing and Monitoring → Load
references/testing-monitoring.md - When: Writing tests or setting up production monitoring/analytics
Checklist: Production-Ready Search
Before launching, verify:
Configuration:
- [ ] Token properly scoped (search:read only)
- [ ] URL restrictions configured
- [ ] Geographic filtering set (country, proximity, or bbox)
- [ ] Types parameter set based on use case
- [ ] Language parameter set if needed
Implementation:
- [ ] Debouncing implemented (300ms recommended)
- [ ] Session tokens used correctly
- [ ] Error handling for all failure cases
- [ ] Loading states shown
- [ ] Empty results handled gracefully
- [ ] Race conditions prevented
UX:
- [ ] Touch targets at least 44pt/48dp
- [ ] Results show enough context (name + address)
- [ ] Keyboard navigation works
- [ ] Accessibility attributes set
- [ ] Mobile keyboard handled properly
Performance:
- [ ] Caching implemented (if high volume)
- [ ] Request timeout set
- [ ] Minimal data fetched
- [ ] Bundle size optimized
Testing:
- [ ] Unit tests for core logic
- [ ] Integration tests with real API
- [ ] Tested on slow networks
- [ ] Tested with various query types
- [ ] Mobile device testing
Monitoring:
- [ ] Analytics tracking set up
- [ ] Error logging configured
- [ ] Usage monitoring in place
- [ ] Budget alerts configured
Integration with Other Skills
Works with:
- mapbox-search-patterns: Parameter selection and optimization
- mapbox-web-integration-patterns: Framework-specific patterns
- mapbox-token-security: Token management and security
- mapbox-web-performance-patterns: Optimizing search performance
Resources
- Search Box API Documentation
- Geocoding API Documentation
- Mapbox Search JS
- Search JS React
- Search JS Web
- Search JS Core
- Search SDK for iOS
- Search SDK for Android
- Location Helper Tool - Calculate bounding boxes
Quick Decision Guide
User says: "I need location search"
1. Ask discovery questions (Questions 1-6 above) 2. Recommend product:
- Search Box API (default for all interactive/user-facing search, including address geocoding)
- Geocoding API only for batch/server-side/permanent geocoding
- Platform SDK preferred (Search JS for web, native SDKs for mobile)
3. Implement with:
- ✅ Debouncing
- ✅ Session tokens
- ✅ Geographic filtering
- ✅ Error handling
- ✅ Good UX
4. Test thoroughly 5. Monitor in production
Remember: The best search implementation asks the right questions first, then builds exactly what the user needs - no more, no less.
{
"skill_name": "mapbox-search-integration",
"evals": [
{
"id": 1,
"prompt": "I'm building a React app with a Mapbox map. I want users to type in a search box, see autocomplete suggestions for both addresses and businesses, and click a result to fly the map to that location. What's the recommended implementation?",
"expectations": [
"Recommends Search Box API (not Geocoding API) — users need both addresses and businesses/POIs",
"Recommends Search JS React (`@mapbox/search-js-react`) as the easiest option for React apps with a built-in UI",
"Shows the SearchBox component with an onRetrieve callback to get coordinates and call map.flyTo()",
"Does NOT recommend direct API calls with fetch() as the primary approach — SDK handles debouncing, session tokens, and UI"
]
},
{
"id": 2,
"prompt": "I want to add an autocomplete search box to my web app but I need a completely custom dropdown UI — my own styling, keyboard navigation, and result rendering. Should I call the Mapbox Search Box API directly with fetch() requests, or is there a better approach?",
"expectations": [
"Recommends Search JS Core (`@mapbox/search-js-core`) over direct API calls for custom UI — it provides the API wrapper without imposing UI",
"Explains that calling the API directly means manually implementing debouncing and session token management",
"Notes that Search JS Core handles debouncing and session tokens automatically",
"If direct API is used anyway, explains that session tokens are required for proper billing and must be generated per search session"
]
},
{
"id": 3,
"prompt": "My app needs to geocode addresses — users type a US address and I get back coordinates. I'm looking at the Mapbox Geocoding API. Should I use that or the Search Box API?",
"expectations": [
"Recommends Search Box API as the default choice for interactive address search",
"Explains Search Box API has session-based pricing which is more cost-efficient for autocomplete/interactive use",
"Notes Geocoding API is appropriate for batch geocoding, maintaining an existing integration, or permanent geocoding (not search)",
"Notes SDKs (Search JS) are preferred over direct API calls for web integration"
]
}
]
}
Android: Search SDK for Android
Option 1: Search SDK with UI (Easiest)
When to use: Android app, want pre-built search UI, fastest implementation
Installation:
// Add to build.gradle
dependencies {
implementation 'com.mapbox.search:mapbox-search-android-ui:2.0.0'
implementation 'com.mapbox.maps:android:11.0.0'
}Complete implementation with built-in UI:
import com.mapbox.search.ui.view.SearchBottomSheetView
import com.mapbox.maps.MapView
class SearchActivity : AppCompatActivity() {
private lateinit var searchView: SearchBottomSheetView
private lateinit var mapView: MapView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_search)
mapView = findViewById(R.id.map_view)
// SearchBottomSheetView provides complete UI automatically
searchView = findViewById(R.id.search_view)
searchView.initializeSearch(
savedInstanceState,
SearchBottomSheetView.Configuration()
)
// Handle result selection
searchView.addOnSearchResultClickListener { searchResult ->
// SDK handled all the search interaction
val coordinate = searchResult.coordinate
mapView.getMapboxMap().flyTo(
CameraOptions.Builder()
.center(Point.fromLngLat(coordinate.longitude, coordinate.latitude))
.zoom(15.0)
.build()
)
searchView.hide()
}
}
}Option 2: Search SDK Core (Custom UI)
When to use: Need custom UI, integrate with SearchView, full control over UX
Complete implementation:
import com.mapbox.search.SearchEngine
import com.mapbox.search.SearchEngineSettings
import com.mapbox.search.SearchOptions
import com.mapbox.maps.MapView
class SearchActivity : AppCompatActivity() {
private lateinit var searchEngine: SearchEngine
private lateinit var mapView: MapView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Initialize Search Engine (SDK handles debouncing and session tokens)
searchEngine = SearchEngine.createSearchEngine(
SearchEngineSettings("YOUR_MAPBOX_TOKEN")
)
setupSearchView()
setupMap()
}
private fun setupSearchView() {
val searchView = findViewById<SearchView>(R.id.search_view)
searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String): Boolean {
performSearch(query)
return true
}
override fun onQueryTextChange(newText: String): Boolean {
if (newText.length >= 2) {
// Search SDK handles debouncing automatically
performSearch(newText)
}
return true
}
})
}
private fun performSearch(query: String) {
val options = SearchOptions(
countries = listOf("US"),
limit = 5
)
searchEngine.search(query, options) { results ->
results.onSuccess { searchResults ->
displayResults(searchResults)
}.onFailure { error ->
Log.e("Search", "Error: $error")
}
}
}
private fun displayResults(results: List<SearchResult>) {
// Display in custom RecyclerView
handleResultSelection(results[0])
}
private fun handleResultSelection(result: SearchResult) {
val coordinate = result.coordinate
mapView.getMapboxMap().flyTo(
CameraOptions.Builder()
.center(Point.fromLngLat(coordinate.longitude, coordinate.latitude))
.zoom(15.0)
.build()
)
}
}Option 3: Direct API Integration (Advanced)
When to use: Very specific requirements, server-side Android backend
Important: Only use if SDK doesn't meet your needs. You must handle debouncing and session tokens manually.
// Direct API calls - see Web direct API example
// Not recommended for Android - use Search SDK insteadBest 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 relevantTip: 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.
Framework-Specific Hooks and Composables
React Best Practices
Best Practice: Use Search JS React or Search JS Core instead of building custom hooks with direct API calls.
Option 1: Use Search JS React (Recommended)
import { SearchBox } from '@mapbox/search-js-react';
// Easiest - just use the SearchBox component
function MyComponent() {
return (
<SearchBox
accessToken="YOUR_TOKEN"
onRetrieve={(result) => {
// Handle result
}}
options={{
country: 'US',
types: 'address,poi'
}}
/>
);
}Option 2: Custom Hook with Search JS Core
import { useState, useCallback, useRef, useEffect } from 'react';
import { SearchSession } from '@mapbox/search-js-core';
// Custom hook using Search JS Core (handles debouncing and session tokens)
function useMapboxSearch(accessToken, options = {}) {
const [results, setResults] = useState([]);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState(null);
// Search JS Core handles session tokens automatically
const searchSessionRef = useRef(null);
useEffect(() => {
searchSessionRef.current = new SearchSession({ accessToken });
}, [accessToken]);
const search = useCallback(
async (query) => {
if (!query || query.length < 2) {
setResults([]);
return;
}
setIsLoading(true);
setError(null);
try {
// Search JS Core handles debouncing and session tokens
const response = await searchSessionRef.current.suggest(query, options);
setResults(response.suggestions || []);
} catch (err) {
setError(err.message);
setResults([]);
} finally {
setIsLoading(false);
}
},
[options]
);
const retrieve = useCallback(async (suggestion) => {
try {
// Search JS Core handles session tokens automatically
const result = await searchSessionRef.current.retrieve(suggestion);
return result.features[0];
} catch (err) {
setError(err.message);
throw err;
}
}, []);
return { results, isLoading, error, search, retrieve };
}Benefits of using Search JS Core:
- ✅ No manual session token management
- ✅ No manual debouncing needed
- ✅ No race condition handling needed (SDK handles it)
- ✅ Cleaner, simpler code
- ✅ Production-ready error handling built-in
Vue Composition API (Using Search JS Core - Recommended)
import { ref, watch } from 'vue';
import { SearchSession } from '@mapbox/search-js-core';
export function useMapboxSearch(accessToken, options = {}) {
const query = ref('');
const results = ref([]);
const isLoading = ref(false);
// Use Search JS Core - handles debouncing and session tokens automatically
const searchSession = new SearchSession({ accessToken });
const performSearch = async (searchQuery) => {
if (!searchQuery || searchQuery.length < 2) {
results.value = [];
return;
}
isLoading.value = true;
try {
// Search JS Core handles debouncing and session tokens
const response = await searchSession.suggest(searchQuery, options);
results.value = response.suggestions || [];
} catch (error) {
console.error('Search error:', error);
results.value = [];
} finally {
isLoading.value = false;
}
};
// Watch query changes (Search JS Core handles debouncing)
watch(query, (newQuery) => {
performSearch(newQuery);
});
const retrieve = async (suggestion) => {
// Search JS Core handles session tokens automatically
const feature = await searchSession.retrieve(suggestion);
return feature;
};
return {
query,
results,
isLoading,
retrieve
};
}Key benefits:
- ✅ Search JS Core handles debouncing automatically (no lodash needed)
- ✅ Session tokens managed automatically (no manual token generation)
- ✅ Simpler code, fewer dependencies
- ✅ Same API works in browser and Node.js
iOS: Search SDK for iOS
Option 1: Search SDK with UI (Easiest)
When to use: iOS app, want pre-built search UI, fastest implementation
Installation:
// Add to Package.swift or SPM
dependencies: [
.package(url: "https://github.com/mapbox/mapbox-search-ios.git", from: "2.0.0")
]Complete implementation with built-in UI:
import MapboxSearch
import MapboxMaps
class SearchViewController: UIViewController {
private var searchController: MapboxSearchController!
private var mapView: MapView!
override func viewDidLoad() {
super.viewDidLoad()
setupMap()
setupSearchWithUI()
}
func setupMap() {
mapView = MapView(frame: view.bounds)
view.addSubview(mapView)
}
func setupSearchWithUI() {
// MapboxSearchController provides complete UI automatically
searchController = MapboxSearchController()
searchController.delegate = self
// Present the search UI
present(searchController, animated: true)
}
}
extension SearchViewController: SearchControllerDelegate {
func searchResultSelected(_ searchResult: SearchResult) {
// SDK handled all the search interaction
// Just respond to selection
mapView.camera.fly(to: CameraOptions(
center: searchResult.coordinate,
zoom: 15
))
let annotation = PointAnnotation(coordinate: searchResult.coordinate)
mapView.annotations.pointAnnotations = [annotation]
dismiss(animated: true)
}
}Option 2: Search SDK Core (Custom UI)
When to use: Need custom UI, integrate with UISearchController, full control over UX
Complete implementation:
import MapboxSearch
import MapboxMaps
class SearchViewController: UIViewController {
private var searchEngine: SearchEngine!
private var mapView: MapView!
override func viewDidLoad() {
super.viewDidLoad()
// Initialize Search Engine (SDK handles debouncing and session tokens)
searchEngine = SearchEngine(accessToken: "YOUR_MAPBOX_TOKEN")
setupSearchBar()
setupMap()
}
func setupSearchBar() {
let searchController = UISearchController(searchResultsController: nil)
searchController.searchResultsUpdater = self
searchController.obscuresBackgroundDuringPresentation = false
navigationItem.searchController = searchController
}
func setupMap() {
mapView = MapView(frame: view.bounds)
view.addSubview(mapView)
}
}
extension SearchViewController: UISearchResultsUpdating {
func updateSearchResults(for searchController: UISearchController) {
guard let query = searchController.searchBar.text, !query.isEmpty else {
return
}
// Search SDK handles debouncing automatically
searchEngine.search(query: query) { [weak self] result in
switch result {
case .success(let results):
self?.displayResults(results)
case .failure(let error):
print("Search error: \(error)")
}
}
}
func displayResults(_ results: [SearchResult]) {
// Display results in custom table view
// When user selects a result:
handleResultSelection(results[0])
}
func handleResultSelection(_ result: SearchResult) {
mapView.camera.fly(to: CameraOptions(
center: result.coordinate,
zoom: 15
))
let annotation = PointAnnotation(coordinate: result.coordinate)
mapView.annotations.pointAnnotations = [annotation]
}
}Option 3: Direct API Integration (Advanced)
When to use: Very specific requirements, server-side iOS backend
Important: Only use if SDK doesn't meet your needs. You must handle debouncing and session tokens manually.
// Direct API calls - see Web direct API example
// Not recommended for iOS - use Search SDK insteadNode.js: Mapbox Search JS Core
Option 1: Search JS Core (Recommended)
When to use: Server-side search, backend API, serverless functions
Installation:
npm install @mapbox/search-js-coreComplete implementation:
import { SearchSession } from '@mapbox/search-js-core';
// Initialize search session (handles session tokens automatically)
const search = new SearchSession({
accessToken: process.env.MAPBOX_TOKEN
});
// Express.js API endpoint example
app.get('/api/search', async (req, res) => {
const { query, proximity, country } = req.query;
try {
// Get suggestions (Search JS Core handles session management)
const response = await search.suggest(query, {
proximity: proximity ? proximity.split(',').map(Number) : undefined,
country: country,
limit: 10
});
res.json(response.suggestions);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// Retrieve full details for a selected result
app.get('/api/search/:id', async (req, res) => {
try {
const result = await search.retrieve(req.params.id);
res.json(result.features[0]);
} catch (error) {
res.status(500).json({ error: error.message });
}
});Key benefits:
- ✅ Search JS Core handles session tokens automatically
- ✅ Perfect for serverless (Vercel, Netlify, AWS Lambda)
- ✅ Same API as browser Search JS Core
- ✅ No manual debouncing needed (handle at API gateway level)
Option 2: Direct API Integration (Advanced)
When to use: Very specific requirements, need features not in Search JS Core
Implementation:
import fetch from 'node-fetch';
async function searchPlaces(query, options = {}) {
const params = new URLSearchParams({
q: query,
access_token: process.env.MAPBOX_TOKEN,
session_token: generateSessionToken(), // You must manage this
...options
});
const response = await fetch(`https://api.mapbox.com/search/searchbox/v1/suggest?${params}`);
return response.json();
}Important: Only use direct API calls if Search JS Core doesn't meet your needs. You'll need to handle session tokens manually.
Common Pitfalls and How to Avoid Them
Pitfall 1: No Debouncing
Problem:
input.addEventListener('input', (e) => {
performSearch(e.target.value); // API call on EVERY keystroke!
});Impact:
- Expensive (hundreds of unnecessary API calls)
- Slow (race conditions, outdated results)
- Rate limiting (429 errors)
Solution: Always debounce (see Best Practices #1)
Pitfall 2: Ignoring Session Tokens
Problem:
// No session token = each request charged separately
fetch('...suggest?q=query&access_token=xxx');Impact:
- Costs 10-100x more than necessary
- Budget blown on redundant charges
Solution: Use session tokens (see Best Practices #2)
Pitfall 3: No Geographic Context
Problem:
// Searching globally for "Paris"
{
q: 'Paris';
} // Paris, France? Paris, Texas? Paris, Kentucky?Impact:
- Confusing results (wrong country)
- Slower responses
- Poor user experience
Solution:
// Much better
{ q: 'Paris', country: 'US', proximity: user_location }Pitfall 4: Poor Mobile UX
Problem:
<!-- Tiny touch targets -->
<div style="height: 20px; padding: 2px;">Search result</div>Impact:
- Frustrating to tap
- Accidental selections
- Bad reviews
Solution:
.search-result {
min-height: 48px; /* Android minimum */
padding: 12px;
margin: 4px 0;
}Pitfall 5: Not Handling Empty Results
Problem:
// Just shows empty container
displayResults([]); // User sees blank space - is it loading? broken?Impact:
- User confusion
- Is it working?
Solution:
if (results.length === 0) {
showMessage('No results found. Try a different search term.');
}Pitfall 6: Blocking on Slow Networks
Problem:
// No timeout = waits forever on slow network
await fetch(searchUrl);Impact:
- Appears frozen
- User frustration
Solution:
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 5000);
fetch(searchUrl, { signal: controller.signal }).finally(() => clearTimeout(timeout));Pitfall 7: Ignoring Result Types
Problem:
// Treating all results the same
displayResult(result.name); // But is it an address? POI? Region?Impact:
- Unclear what was selected
- Wrong zoom level
- Inappropriate markers
Solution:
function handleResult(result) {
const type = result.feature_type;
if (type === 'poi') {
map.flyTo({ center: coords, zoom: 17 }); // Close zoom
addPOIMarker(result);
} else if (type === 'address') {
map.flyTo({ center: coords, zoom: 16 });
addAddressMarker(result);
} else if (type === 'place') {
map.flyTo({ center: coords, zoom: 12 }); // Wider view for city
}
}Pitfall 8: Race Conditions
Problem:
// Fast typing: "san francisco"
// API responses arrive out of order:
// "san f" results arrive AFTER "san francisco" resultsImpact:
- Wrong results displayed
- Confusing UX
Solution:
let searchCounter = 0;
async function performSearch(query) {
const currentSearch = ++searchCounter;
const results = await fetchResults(query);
// Only display if this is still the latest search
if (currentSearch === searchCounter) {
displayResults(results);
}
}React Integration Patterns
Best Practice: Use Search JS React for easiest implementation, or Search JS Core for custom UI.
Option 1: Search JS React (Recommended - Easiest)
import { SearchBox } from '@mapbox/search-js-react';
import mapboxgl from 'mapbox-gl';
import { useState } from 'react';
function MapboxSearchComponent() {
const [map, setMap] = useState(null);
useEffect(() => {
const mapInstance = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mapbox/streets-v12',
center: [-122.4194, 37.7749],
zoom: 12
});
setMap(mapInstance);
}, []);
const handleRetrieve = (result) => {
const [lng, lat] = result.features[0].geometry.coordinates;
map.flyTo({ center: [lng, lat], zoom: 14 });
new mapboxgl.Marker().setLngLat([lng, lat]).addTo(map);
};
return (
<div>
<SearchBox
accessToken="YOUR_MAPBOX_TOKEN"
onRetrieve={handleRetrieve}
placeholder="Search for places"
options={{
country: 'US', // Optional
types: 'address,poi'
}}
/>
<div id="map" style={{ height: '600px' }} />
</div>
);
}Benefits:
- ✅ Complete UI component provided
- ✅ No manual debouncing needed
- ✅ No manual session token management
- ✅ Production-ready out of the box
Option 2: Search JS Core (Custom UI)
import { useState, useEffect } from 'react';
import { SearchSession } from '@mapbox/search-js-core';
import mapboxgl from 'mapbox-gl';
function MapboxSearchComponent({ country, types = 'address,poi' }) {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
const [isLoading, setIsLoading] = useState(false);
// Search JS Core handles debouncing and session tokens automatically
const searchSession = new SearchSession({
accessToken: 'YOUR_MAPBOX_TOKEN'
});
useEffect(() => {
const performSearch = async () => {
if (!query || query.length < 2) {
setResults([]);
return;
}
setIsLoading(true);
try {
const response = await searchSession.suggest(query, {
country,
types,
limit: 5
});
setResults(response.suggestions || []);
} catch (error) {
console.error('Search error:', error);
setResults([]);
} finally {
setIsLoading(false);
}
};
performSearch();
}, [query]);
const handleResultClick = async (suggestion) => {
try {
const result = await searchSession.retrieve(suggestion);
const feature = result.features[0];
// Handle result (fly to location, add marker, etc.)
onResultSelect(feature);
setQuery(feature.properties.name);
setResults([]);
} catch (error) {
console.error('Retrieve error:', error);
}
};
return (
<div className="search-container">
<input
type="text"
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="Search for a place..."
className="search-input"
/>
{isLoading && <div className="loading">Searching...</div>}
{results.length > 0 && (
<div className="search-results">
{results.map((result) => (
<div key={result.mapbox_id} className="search-result" onClick={() => handleResultClick(result)}>
<strong>{result.name}</strong>
{result.place_formatted && <p>{result.place_formatted}</p>}
</div>
))}
</div>
)}
</div>
);
}Benefits:
- ✅ Full control over UI design
- ✅ Search JS Core handles debouncing automatically
- ✅ Search JS Core handles session tokens automatically
- ✅ Cleaner code than direct API calls
Note: For React apps, prefer Search JS React (Option 1) unless you need a completely custom UI design.
Why NOT Direct API Calls (fetch)?
If you bypass Search JS Core and call the Search Box API directly with fetch(), you must handle:
1. Session tokens — Required for proper billing. The Search Box API uses session-based pricing: one session = one suggest flow + one retrieve. You must generate a unique session token per search session and pass it as session_token parameter on every request. Without session tokens, each individual request is billed separately (much more expensive). 2. Debouncing — You must implement your own 300ms debounce to avoid excessive API calls. 3. Race conditions — Later requests may resolve before earlier ones; you need request cancellation or ordering logic.
Search JS Core handles all three automatically — this is why it's recommended over direct fetch() calls even for custom UIs.
Testing and Monitoring
Testing Strategy
Unit Tests
// Mock fetch for testing
global.fetch = jest.fn();
describe('MapboxSearch', () => {
beforeEach(() => {
fetch.mockClear();
});
test('debounces search requests', async () => {
const search = new MapboxSearch('fake-token');
// Rapid-fire searches
search.search('san');
search.search('san f');
search.search('san fr');
search.search('san francisco');
// Wait for debounce
await new Promise((resolve) => setTimeout(resolve, 400));
// Should only make one API call
expect(fetch).toHaveBeenCalledTimes(1);
});
test('handles empty results', async () => {
fetch.mockResolvedValue({
ok: true,
json: async () => ({ suggestions: [] })
});
const search = new MapboxSearch('fake-token');
const results = await search.performSearch('xyz');
expect(results).toEqual([]);
});
test('handles API errors', async () => {
fetch.mockResolvedValue({
ok: false,
status: 429
});
const search = new MapboxSearch('fake-token');
const results = await search.performSearch('test');
expect(results).toEqual([]);
});
});Integration Tests
describe('Search Integration', () => {
test('complete search flow', async () => {
const search = new MapboxSearch(process.env.MAPBOX_TOKEN);
// Perform search
const suggestions = await search.performSearch('San Francisco');
expect(suggestions.length).toBeGreaterThan(0);
// Retrieve first result
const feature = await search.retrieve(suggestions[0].mapbox_id);
expect(feature.geometry.coordinates).toBeDefined();
expect(feature.properties.name).toBe('San Francisco');
});
});Monitoring and Analytics
Track Key Metrics
// Track search usage
function trackSearch(query, resultsCount) {
analytics.track('search_performed', {
query_length: query.length,
results_count: resultsCount,
had_results: resultsCount > 0
});
}
// Track selections
function trackSelection(result, position) {
analytics.track('search_result_selected', {
result_type: result.feature_type,
result_position: position,
had_address: !!result.properties.full_address
});
}
// Track errors
function trackError(errorType, query) {
analytics.track('search_error', {
error_type: errorType,
query_length: query.length
});
}Monitor for Issues
- Zero-result rate (should be < 20%)
- Average response time
- Error rate
- Selection rate (users selecting vs abandoning)
- API usage vs budget
Web: Mapbox Search JS Integration
Important: Always prefer using SDKs (Mapbox Search JS, Search SDK for iOS/Android) over calling APIs directly. SDKs handle debouncing, session tokens, error handling, and provide UI components. Only use direct API calls for advanced use cases.
Option 1: Search JS React (Easiest - React apps with UI)
When to use: React application, want autocomplete UI component, fastest implementation
Installation:
npm install @mapbox/search-js-reactComplete implementation:
import { SearchBox } from '@mapbox/search-js-react';
import mapboxgl from 'mapbox-gl';
function App() {
const [map, setMap] = React.useState(null);
React.useEffect(() => {
mapboxgl.accessToken = 'YOUR_MAPBOX_TOKEN';
const mapInstance = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mapbox/streets-v12',
center: [-122.4194, 37.7749],
zoom: 12
});
setMap(mapInstance);
}, []);
const handleRetrieve = (result) => {
const [lng, lat] = result.features[0].geometry.coordinates;
map.flyTo({ center: [lng, lat], zoom: 14 });
new mapboxgl.Marker().setLngLat([lng, lat]).addTo(map);
};
return (
<div>
<SearchBox accessToken="YOUR_MAPBOX_TOKEN" onRetrieve={handleRetrieve} placeholder="Search for places" />
<div id="map" style={{ height: '600px' }} />
</div>
);
}Option 2: Search JS Web (Web Components with UI)
When to use: Vanilla JavaScript, Web Components, or any framework, want autocomplete UI
Complete implementation:
<!DOCTYPE html>
<html>
<head>
<script src="https://api.mapbox.com/search-js/v1.0.0-beta.18/web.js"></script>
<link href="https://api.mapbox.com/search-js/v1.0.0-beta.18/web.css" rel="stylesheet" />
<script src="https://api.mapbox.com/mapbox-gl-js/v3.0.0/mapbox-gl.js"></script>
<link href="https://api.mapbox.com/mapbox-gl-js/v3.0.0/mapbox-gl.css" rel="stylesheet" />
</head>
<body>
<div id="search"></div>
<div id="map" style="height: 600px;"></div>
<script>
// Initialize map
mapboxgl.accessToken = 'YOUR_MAPBOX_TOKEN';
const map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mapbox/streets-v12',
center: [-122.4194, 37.7749],
zoom: 12
});
// Initialize Search Box
const search = new MapboxSearchBox();
search.accessToken = 'YOUR_MAPBOX_TOKEN';
// CRITICAL: Set options based on discovery
search.options = {
language: 'en',
country: 'US', // If single-country (from Question 2)
proximity: 'ip', // Or specific coordinates
types: 'address,poi' // Based on Question 1
};
search.mapboxgl = mapboxgl;
search.marker = true; // Auto-add marker on result selection
// Handle result selection
search.addEventListener('retrieve', (event) => {
const result = event.detail;
// Fly to result
map.flyTo({
center: result.geometry.coordinates,
zoom: 15,
essential: true
});
// Optional: Show popup with details
new mapboxgl.Popup()
.setLngLat(result.geometry.coordinates)
.setHTML(
`<h3>${result.properties.name}</h3>
<p>${result.properties.full_address || ''}</p>`
)
.addTo(map);
});
// Attach to DOM
document.getElementById('search').appendChild(search);
</script>
</body>
</html>Key implementation notes:
- ✅ Set
countryif single-country search (better results, lower cost) - ✅ Set
typesbased on what users search for - ✅ Use
proximityto bias results to user location - ✅ Handle
retrieveevent for result selection - ✅ Integrate with map (flyTo, markers, popups)
Option 3: Search JS Core (Custom UI)
When to use: Need custom UI design, full control over UX, works in any framework or Node.js
Installation:
npm install @mapbox/search-js-coreComplete implementation:
import { SearchSession } from '@mapbox/search-js-core';
import mapboxgl from 'mapbox-gl';
// Initialize search session
const search = new SearchSession({
accessToken: 'YOUR_MAPBOX_TOKEN'
});
// Initialize map
mapboxgl.accessToken = 'YOUR_MAPBOX_TOKEN';
const map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mapbox/streets-v12',
center: [-122.4194, 37.7749],
zoom: 12
});
// Your custom search input
const searchInput = document.getElementById('search-input');
const resultsContainer = document.getElementById('results');
// Handle user input
searchInput.addEventListener('input', async (e) => {
const query = e.target.value;
if (query.length < 2) {
resultsContainer.innerHTML = '';
return;
}
// Get suggestions (Search JS Core handles debouncing and session tokens)
const response = await search.suggest(query, {
proximity: map.getCenter().toArray(),
country: 'US', // Optional
types: ['address', 'poi']
});
// Render custom results UI
resultsContainer.innerHTML = response.suggestions
.map(
(suggestion) => `
<div class="result-item" data-id="${suggestion.mapbox_id}">
<strong>${suggestion.name}</strong>
<div>${suggestion.place_formatted}</div>
</div>
`
)
.join('');
});
// Handle result selection
resultsContainer.addEventListener('click', async (e) => {
const resultItem = e.target.closest('.result-item');
if (!resultItem) return;
const mapboxId = resultItem.dataset.id;
// Retrieve full details
const result = await search.retrieve(mapboxId);
const feature = result.features[0];
const [lng, lat] = feature.geometry.coordinates;
// Update map
map.flyTo({ center: [lng, lat], zoom: 15 });
new mapboxgl.Marker().setLngLat([lng, lat]).addTo(map);
// Clear search
searchInput.value = feature.properties.name;
resultsContainer.innerHTML = '';
});Key benefits:
- ✅ Full control over UI/UX
- ✅ Search JS Core handles session tokens automatically
- ✅ Works in any framework (React, Vue, Angular, etc.)
- ✅ Can use in Node.js for server-side search
Option 4: Direct API Integration (Advanced - Last Resort)
When to use: Very specific requirements that SDKs don't support, or server-side integration where Search JS Core doesn't fit
Important: Only use direct API calls when SDKs don't meet your needs. You'll need to handle debouncing and session tokens manually.
When to use: Custom UI, framework integration, need full control
Complete implementation with debouncing:
import mapboxgl from 'mapbox-gl';
class MapboxSearch {
constructor(accessToken, options = {}) {
this.accessToken = accessToken;
this.options = {
country: options.country || null, // e.g., 'US'
language: options.language || 'en',
proximity: options.proximity || 'ip',
types: options.types || 'address,poi',
limit: options.limit || 5,
...options
};
this.debounceTimeout = null;
this.sessionToken = this.generateSessionToken();
}
generateSessionToken() {
return `${Date.now()}-${Math.random().toString(36).substring(7)}`;
}
// CRITICAL: Debounce to avoid API spam
async search(query, callback, debounceMs = 300) {
clearTimeout(this.debounceTimeout);
this.debounceTimeout = setTimeout(async () => {
const results = await this.performSearch(query);
callback(results);
}, debounceMs);
}
async performSearch(query) {
if (!query || query.length < 2) return [];
const params = new URLSearchParams({
q: query,
access_token: this.accessToken,
session_token: this.sessionToken,
language: this.options.language,
limit: this.options.limit
});
// Add optional parameters
if (this.options.country) {
params.append('country', this.options.country);
}
if (this.options.types) {
params.append('types', this.options.types);
}
if (this.options.proximity && this.options.proximity !== 'ip') {
params.append('proximity', this.options.proximity);
}
try {
const response = await fetch(`https://api.mapbox.com/search/searchbox/v1/suggest?${params}`);
if (!response.ok) {
throw new Error(`Search API error: ${response.status}`);
}
const data = await response.json();
return data.suggestions || [];
} catch (error) {
console.error('Search error:', error);
return [];
}
}
async retrieve(suggestionId) {
const params = new URLSearchParams({
access_token: this.accessToken,
session_token: this.sessionToken
});
try {
const response = await fetch(`https://api.mapbox.com/search/searchbox/v1/retrieve/${suggestionId}?${params}`);
if (!response.ok) {
throw new Error(`Retrieve API error: ${response.status}`);
}
const data = await response.json();
// Session ends on retrieve - generate new token for next search
this.sessionToken = this.generateSessionToken();
return data.features[0];
} catch (error) {
console.error('Retrieve error:', error);
return null;
}
}
}
// Usage example
const search = new MapboxSearch('YOUR_MAPBOX_TOKEN', {
country: 'US', // Based on discovery Question 2
types: 'poi', // Based on discovery Question 1
proximity: [-122.4194, 37.7749] // Or 'ip' for user location
});
// Attach to input field
const input = document.getElementById('search-input');
const resultsContainer = document.getElementById('search-results');
input.addEventListener('input', (e) => {
const query = e.target.value;
search.search(query, (results) => {
displayResults(results);
});
});
function displayResults(results) {
resultsContainer.innerHTML = results
.map(
(result) => `
<div class="result" data-id="${result.mapbox_id}">
<strong>${result.name}</strong>
<p>${result.place_formatted || ''}</p>
</div>
`
)
.join('');
// Handle result selection
resultsContainer.querySelectorAll('.result').forEach((el) => {
el.addEventListener('click', async () => {
const feature = await search.retrieve(el.dataset.id);
handleResultSelection(feature);
});
});
}
function handleResultSelection(feature) {
const [lng, lat] = feature.geometry.coordinates;
// Fly map to result
map.flyTo({
center: [lng, lat],
zoom: 15
});
// Add marker
new mapboxgl.Marker().setLngLat([lng, lat]).addTo(map);
// Close results
resultsContainer.innerHTML = '';
input.value = feature.properties.name;
}Critical implementation details:
1. ✅ Debouncing: Wait 300ms after user stops typing before API call 2. ✅ Session tokens: Use same token for suggest + retrieve, generate new after 3. ✅ Error handling: Handle API errors gracefully 4. ✅ Parameter optimization: Only send parameters you need 5. ✅ Result display: Show name + formatted address 6. ✅ Selection handling: Retrieve full feature on selection
Related skills
How it compares
Choose mapbox-search-integration over generic map tutorials when autocomplete must cover businesses and addresses with current Search Box APIs.
FAQ
What does mapbox-search-integration do?
Complete workflow for implementing Mapbox search in applications - from discovery questions to production-ready integration with best practices
When should I use mapbox-search-integration?
Complete workflow for implementing Mapbox search in applications - from discovery questions to production-ready integration with best practices
What are common prerequisites?
--- name: mapbox-search-integration description: Complete workflow for implementing Mapbox search in applications - from discovery questions to production-ready integration with best practices --- # Mapbox Search Integra
Is Mapbox Search Integration safe to install?
skills.sh reports 3 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.