---
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 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"** → Focus on address geocoding, consider Search Box API or Geocoding API
- **"Points of interest / businesses"** → POI search, use Search Box API with category search
- **"Both addresses and POIs"** → Search Box API (unified search)
- **"Specific types of places"** (restaurants, hotels, etc.) → Category search or filtered POI search
- **"Custom locations"** (user-created places) → May need custom data + search integration
### Question 2: What's the geographic scope?
**Ask:** "Where will users be searching?"
**Common answers and implications:**
- **"Single country"** (e.g., "only USA") → Use `country` parameter, better results, lower cost
- **"Specific region"** → Use `bbox` parameter for bounding box constraint
- **"Global"** → No country restriction, but may need language parameter
- **"Multiple specific countries"** → Use `country` array 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 `autocomplete: true`, 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
- **"Vue / Angular / Other framework"** → Mapbox Search JS Core or Web, or direct API calls
### 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:
### Recommended: Search Box API (Modern, Unified)
**Use when:**
- User needs both addresses AND POIs
- Building a modern web/mobile app
- Want autocomplete functionality
- Need session-based pricing
- Want the simplest integration
**Advantages:**
- ✅ Unified search (addresses + POIs)
- ✅ Session-based pricing (cheaper for autocomplete)
- ✅ Modern API design
- ✅ Built-in autocomplete support
- ✅ Better POI coverage
**Products:**
- **Search Box API** (REST) - Direct API integration
- **Mapbox Search JS** (SDK) - 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 SDK for iOS** - Native iOS integration
- **Search SDK for Android** - Native Android integration
### Geocoding API
**Use when:**
- Only need address geocoding (no POIs)
- Existing integration to maintain
- Need permanent geocoding (not search)
- Batch geocoding jobs
**Note:** Prefer Search Box API unless the user specifically says they only want address geocoding.
## Integration Patterns by Platform
**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.
### Web: Mapbox Search JS (Recommended)
#### Option 1: Search JS React (Easiest - React apps with UI)
**When to use:** React application, want autocomplete UI component, fastest implementation
**Installation:**
```bash
npm install @mapbox/search-js-react
```
**Complete implementation:**
```jsx
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 (
);
}
```
#### Option 2: Search JS Web (Web Components with UI)
**When to use:** Vanilla JavaScript, Web Components, or any framework, want autocomplete UI
**Complete implementation:**
```html
```
**Key implementation notes:**
- ✅ Set `country` if single-country search (better results, lower cost)
- ✅ Set `types` based on what users search for
- ✅ Use `proximity` to bias results to user location
- ✅ Handle `retrieve` event 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:**
```bash
npm install @mapbox/search-js-core
```
**Complete implementation:**
```javascript
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 => `
${suggestion.name}
${suggestion.place_formatted}
`).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:**
```javascript
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 => `
${result.name}
${result.place_formatted || ''}
`).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
### React Integration Pattern
**Best Practice:** Use Search JS React for easiest implementation, or Search JS Core for custom UI.
#### Option 1: Search JS React (Recommended - Easiest)
```javascript
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 (