Search Block

Full-text search engine for modern applications
The Search Block provides lightning-fast full-text search capabilities for your application. Index your data, perform instant searches with typo tolerance, and deliver relevant results with faceting, filtering, and analytics—all through a simple REST API.
Features
- Full-Text Search - Lightning-fast queries across all indexed content
- Typo Tolerance - Fuzzy matching handles misspellings automatically
- Faceted Filters - Dynamic faceting for result refinement
- Auto-Suggestions - Real-time search suggestions as users type
- Synonyms & Stemming - Language-aware matching with custom synonyms
- Search Analytics - Track popular queries and optimize relevance
- Custom Ranking - Configure ranking rules and boosting
- Multi-Index - Federated search across multiple indexes
- Highlighting - Show matched terms in context
- Geo Search - Location-aware search and sorting
API Endpoint
| Service | URL |
|---|---|
| Search | https://search.api.us.23blocks.com |
Environment Routing: Use the same URL for all environments. Your API key determines the environment:
pk_test_*/sk_test_*→ Routes to Stagingpk_live_*/sk_live_*→ Routes to Production
Quick Start
npm install @23blocks/sdk
import { create23BlocksClient } from '@23blocks/sdk';
const client = create23BlocksClient({
urls: { search: 'https://search.api.us.23blocks.com' },
apiKey: 'your-api-key', // Use pk_test_* for staging, pk_live_* for production
});
// Create an index
await client.search.createIndex('products');
// Index documents
await client.search.indexDocuments('products', [
{ id: '1', name: 'Wireless Headphones', category: 'electronics', price: 299 },
{ id: '2', name: 'Bluetooth Speaker', category: 'electronics', price: 149 },
{ id: '3', name: 'Running Shoes', category: 'sports', price: 129 }
]);
// Search with typo tolerance
const results = await client.search.query('products', {
query: 'wirless headphnes', // typos handled!
filters: { category: 'electronics' },
facets: ['category', 'brand']
});
// Get suggestions
const suggestions = await client.search.suggest('products', {
query: 'wire',
limit: 5
});
Authentication
All API requests require authentication using your API credentials.
Required Headers
X-App-Id: your-app-id
X-Api-Key: your-api-key
Content-Type: application/json
Indexes API
Manage your search indexes.
Create Index
Create a new search index.
POST /search/v1/indexes
Request Body:
{
"data": {
"type": "indexes",
"attributes": {
"uid": "products",
"primary_key": "id",
"settings": {
"searchable_attributes": ["name", "description", "category"],
"filterable_attributes": ["category", "brand", "price"],
"sortable_attributes": ["price", "created_at", "popularity"],
"ranking_rules": [
"words",
"typo",
"proximity",
"attribute",
"sort",
"exactness"
],
"stop_words": ["the", "a", "an"],
"distinct_attribute": null,
"typo_tolerance": {
"enabled": true,
"min_word_size_for_typos": {
"one_typo": 5,
"two_typos": 9
}
}
}
}
}
}
Response:
{
"data": {
"type": "indexes",
"id": "idx_a1b2c3d4",
"attributes": {
"uid": "products",
"primary_key": "id",
"created_at": "2025-01-15T10:30:00Z",
"updated_at": "2025-01-15T10:30:00Z",
"number_of_documents": 0,
"is_indexing": false
}
}
}
List Indexes
Retrieve all indexes.
GET /search/v1/indexes
Response:
{
"data": [
{
"type": "indexes",
"id": "idx_a1b2c3d4",
"attributes": {
"uid": "products",
"primary_key": "id",
"number_of_documents": 15420,
"is_indexing": false
}
},
{
"type": "indexes",
"id": "idx_e5f6g7h8",
"attributes": {
"uid": "articles",
"primary_key": "id",
"number_of_documents": 3250,
"is_indexing": false
}
}
]
}
Get Index
Retrieve a specific index.
GET /search/v1/indexes/:index_uid
Response:
{
"data": {
"type": "indexes",
"id": "idx_a1b2c3d4",
"attributes": {
"uid": "products",
"primary_key": "id",
"created_at": "2025-01-15T10:30:00Z",
"updated_at": "2025-01-15T12:45:00Z",
"number_of_documents": 15420,
"is_indexing": false,
"settings": {
"searchable_attributes": ["name", "description", "category"],
"filterable_attributes": ["category", "brand", "price"],
"sortable_attributes": ["price", "created_at", "popularity"]
}
}
}
}
Update Index Settings
Update index configuration.
PATCH /search/v1/indexes/:index_uid/settings
Request Body:
{
"data": {
"type": "index_settings",
"attributes": {
"searchable_attributes": ["name", "description", "category", "tags"],
"filterable_attributes": ["category", "brand", "price", "in_stock"],
"synonyms": {
"phone": ["smartphone", "mobile", "cell phone"],
"laptop": ["notebook", "computer"]
}
}
}
}
Delete Index
Delete an index and all its documents.
DELETE /search/v1/indexes/:index_uid
Documents API
Manage documents within an index.
Index Documents
Add or update documents.
POST /search/v1/indexes/:index_uid/documents
Request Body:
{
"data": {
"type": "documents",
"attributes": {
"documents": [
{
"id": "prod_001",
"name": "Wireless Noise-Cancelling Headphones",
"description": "Premium over-ear headphones with active noise cancellation",
"category": "electronics",
"brand": "AudioMax",
"price": 299.99,
"in_stock": true,
"rating": 4.7,
"tags": ["wireless", "noise-cancelling", "premium"]
},
{
"id": "prod_002",
"name": "Portable Bluetooth Speaker",
"description": "Waterproof speaker with 360-degree sound",
"category": "electronics",
"brand": "SoundWave",
"price": 149.99,
"in_stock": true,
"rating": 4.5,
"tags": ["bluetooth", "portable", "waterproof"]
}
]
}
}
}
Response:
{
"data": {
"type": "indexing_tasks",
"id": "task_xyz123",
"attributes": {
"status": "enqueued",
"documents_received": 2,
"enqueued_at": "2025-01-15T14:30:00Z"
}
}
}
Get Document
Retrieve a specific document.
GET /search/v1/indexes/:index_uid/documents/:document_id
Response:
{
"data": {
"type": "documents",
"id": "prod_001",
"attributes": {
"name": "Wireless Noise-Cancelling Headphones",
"description": "Premium over-ear headphones with active noise cancellation",
"category": "electronics",
"brand": "AudioMax",
"price": 299.99,
"in_stock": true,
"rating": 4.7,
"tags": ["wireless", "noise-cancelling", "premium"]
}
}
}
List Documents
Browse documents in an index.
GET /search/v1/indexes/:index_uid/documents?offset=0&limit=20
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
| offset | integer | Number of documents to skip |
| limit | integer | Number of documents to return (max 1000) |
| fields | string | Comma-separated list of fields to return |
| filter | string | Filter expression |
Delete Documents
Delete specific documents.
DELETE /search/v1/indexes/:index_uid/documents
Request Body:
{
"data": {
"type": "document_deletions",
"attributes": {
"document_ids": ["prod_001", "prod_002", "prod_003"]
}
}
}
Delete Documents by Filter
Delete documents matching a filter.
POST /search/v1/indexes/:index_uid/documents/delete-by-filter
Request Body:
{
"data": {
"type": "document_deletions",
"attributes": {
"filter": "category = 'deprecated' AND updated_at < '2024-01-01'"
}
}
}
Search API
Perform searches across your indexed data.
Basic Search
POST /search/v1/indexes/:index_uid/search
Request Body:
{
"data": {
"type": "search_queries",
"attributes": {
"query": "wireless headphones",
"limit": 20,
"offset": 0
}
}
}
Response:
{
"data": {
"type": "search_results",
"attributes": {
"hits": [
{
"id": "prod_001",
"name": "Wireless Noise-Cancelling Headphones",
"description": "Premium over-ear headphones with active noise cancellation",
"category": "electronics",
"price": 299.99,
"_formatted": {
"name": "<em>Wireless</em> Noise-Cancelling <em>Headphones</em>",
"description": "Premium over-ear <em>headphones</em> with active noise cancellation"
},
"_matchesPosition": {
"name": [{ "start": 0, "length": 8 }, { "start": 31, "length": 10 }]
}
}
],
"offset": 0,
"limit": 20,
"estimated_total_hits": 128,
"processing_time_ms": 12,
"query": "wireless headphones"
}
}
}
Search with Filters
Apply filters to narrow results.
{
"data": {
"type": "search_queries",
"attributes": {
"query": "headphones",
"filter": "category = 'electronics' AND price < 300 AND in_stock = true",
"limit": 20
}
}
}
Filter Syntax:
| Operator | Description | Example |
|---|---|---|
= | Equals | category = 'electronics' |
!= | Not equals | brand != 'Generic' |
> | Greater than | price > 100 |
>= | Greater or equal | rating >= 4.0 |
< | Less than | price < 500 |
<= | Less or equal | stock <= 10 |
IN | In array | category IN ['electronics', 'audio'] |
NOT IN | Not in array | status NOT IN ['deleted', 'hidden'] |
EXISTS | Field exists | discount EXISTS |
NOT EXISTS | Field doesn't exist | archived NOT EXISTS |
AND | Logical and | price < 100 AND in_stock = true |
OR | Logical or | category = 'audio' OR category = 'video' |
Search with Facets
Get facet counts for result refinement.
{
"data": {
"type": "search_queries",
"attributes": {
"query": "headphones",
"facets": ["category", "brand", "price"],
"limit": 20
}
}
}
Response:
{
"data": {
"type": "search_results",
"attributes": {
"hits": [],
"facet_distribution": {
"category": {
"electronics": 95,
"audio": 28,
"gaming": 12
},
"brand": {
"AudioMax": 42,
"SoundWave": 35,
"TechPro": 28
}
},
"facet_stats": {
"price": {
"min": 29.99,
"max": 599.99,
"avg": 189.50,
"sum": 25678.50
}
}
}
}
}
Search with Sorting
Sort results by specific attributes.
{
"data": {
"type": "search_queries",
"attributes": {
"query": "headphones",
"sort": ["price:asc", "rating:desc"],
"limit": 20
}
}
}
Search with Highlighting
Configure result highlighting.
{
"data": {
"type": "search_queries",
"attributes": {
"query": "wireless",
"attributes_to_highlight": ["name", "description"],
"highlight_pre_tag": "<mark>",
"highlight_post_tag": "</mark>",
"limit": 20
}
}
}
Geo Search
Search with geographic constraints.
{
"data": {
"type": "search_queries",
"attributes": {
"query": "coffee shop",
"filter": "_geoRadius(40.7128, -74.0060, 5000)",
"sort": ["_geoPoint(40.7128, -74.0060):asc"],
"limit": 20
}
}
}
Multi-Index Search
Search across multiple indexes.
POST /search/v1/multi-search
Request Body:
{
"data": {
"type": "multi_search_queries",
"attributes": {
"queries": [
{
"index_uid": "products",
"query": "headphones",
"limit": 5
},
{
"index_uid": "articles",
"query": "headphones review",
"limit": 3
}
]
}
}
}
Suggestions API
Provide search suggestions as users type.
Get Suggestions
POST /search/v1/indexes/:index_uid/suggest
Request Body:
{
"data": {
"type": "suggestion_queries",
"attributes": {
"query": "wire",
"limit": 5,
"highlight": true
}
}
}
Response:
{
"data": {
"type": "suggestions",
"attributes": {
"suggestions": [
{
"suggestion": "wireless headphones",
"highlighted": "<em>wire</em>less headphones",
"frequency": 1250
},
{
"suggestion": "wireless speaker",
"highlighted": "<em>wire</em>less speaker",
"frequency": 890
},
{
"suggestion": "wireless charger",
"highlighted": "<em>wire</em>less charger",
"frequency": 654
},
{
"suggestion": "wireless mouse",
"highlighted": "<em>wire</em>less mouse",
"frequency": 421
},
{
"suggestion": "wireless keyboard",
"highlighted": "<em>wire</em>less keyboard",
"frequency": 387
}
],
"processing_time_ms": 3
}
}
}
Configure Suggestions
Configure suggestion behavior.
PATCH /search/v1/indexes/:index_uid/settings/suggestions
Request Body:
{
"data": {
"type": "suggestion_settings",
"attributes": {
"enabled": true,
"source_attributes": ["name", "category", "tags"],
"min_query_length": 2,
"max_suggestions": 10,
"include_frequency": true
}
}
}
Synonyms API
Configure synonyms for better search matching.
Set Synonyms
PUT /search/v1/indexes/:index_uid/settings/synonyms
Request Body:
{
"data": {
"type": "synonyms",
"attributes": {
"synonyms": {
"phone": ["smartphone", "mobile", "cell phone", "cellphone"],
"laptop": ["notebook", "computer", "pc"],
"tv": ["television", "smart tv", "flat screen"],
"headphones": ["earphones", "earbuds", "headset"]
},
"one_way_synonyms": {
"iphone": ["phone", "smartphone"],
"macbook": ["laptop", "notebook"]
}
}
}
}
Get Synonyms
GET /search/v1/indexes/:index_uid/settings/synonyms
Response:
{
"data": {
"type": "synonyms",
"attributes": {
"synonyms": {
"phone": ["smartphone", "mobile", "cell phone", "cellphone"],
"laptop": ["notebook", "computer", "pc"]
},
"one_way_synonyms": {
"iphone": ["phone", "smartphone"]
}
}
}
}
Stop Words API
Configure stop words to ignore in searches.
Set Stop Words
PUT /search/v1/indexes/:index_uid/settings/stop-words
Request Body:
{
"data": {
"type": "stop_words",
"attributes": {
"stop_words": ["the", "a", "an", "and", "or", "but", "is", "are", "was", "were"]
}
}
}
Ranking Rules API
Configure how results are ranked.
Set Ranking Rules
PUT /search/v1/indexes/:index_uid/settings/ranking-rules
Request Body:
{
"data": {
"type": "ranking_rules",
"attributes": {
"ranking_rules": [
"words",
"typo",
"proximity",
"attribute",
"sort",
"exactness",
"popularity:desc",
"rating:desc"
]
}
}
}
Built-in Ranking Rules:
| Rule | Description |
|---|---|
| words | Matches with more query words rank higher |
| typo | Matches with fewer typos rank higher |
| proximity | Matches with words closer together rank higher |
| attribute | Matches in more important attributes rank higher |
| sort | Applies explicit sorting from search request |
| exactness | Exact matches rank higher than partial matches |
Analytics API
Track search analytics and user behavior.
Get Popular Searches
GET /search/v1/indexes/:index_uid/analytics/popular-searches
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
| period | string | Time period: 24h, 7d, 30d, 90d |
| limit | integer | Number of results (default: 10) |
Response:
{
"data": {
"type": "analytics",
"attributes": {
"popular_searches": [
{
"query": "wireless headphones",
"count": 12450,
"unique_users": 8920,
"avg_click_position": 1.3
},
{
"query": "bluetooth speaker",
"count": 8730,
"unique_users": 6540,
"avg_click_position": 1.8
}
],
"period": "7d",
"total_searches": 156780
}
}
}
Get No-Result Searches
Find queries with no results.
GET /search/v1/indexes/:index_uid/analytics/no-result-searches
Response:
{
"data": {
"type": "analytics",
"attributes": {
"no_result_searches": [
{
"query": "solar charger",
"count": 234,
"last_searched_at": "2025-01-15T14:30:00Z"
},
{
"query": "wireless earbuds case",
"count": 189,
"last_searched_at": "2025-01-15T14:25:00Z"
}
],
"period": "7d",
"total_no_result_searches": 1250
}
}
}
Get Click Analytics
Track which results users click.
GET /search/v1/indexes/:index_uid/analytics/clicks
Response:
{
"data": {
"type": "analytics",
"attributes": {
"click_data": [
{
"document_id": "prod_001",
"clicks": 5420,
"impressions": 18500,
"ctr": 0.293,
"avg_position": 1.2
}
],
"period": "7d",
"total_clicks": 45600,
"total_impressions": 156780,
"avg_ctr": 0.29
}
}
}
Track Search Event
Log search analytics events.
POST /search/v1/analytics/events
Request Body:
{
"data": {
"type": "search_events",
"attributes": {
"event_type": "click",
"index_uid": "products",
"query": "wireless headphones",
"document_id": "prod_001",
"position": 1,
"user_id": "user_abc123",
"session_id": "sess_xyz789"
}
}
}
Tasks API
Monitor indexing tasks.
Get Task Status
GET /search/v1/tasks/:task_id
Response:
{
"data": {
"type": "tasks",
"id": "task_xyz123",
"attributes": {
"status": "succeeded",
"task_type": "document_addition",
"index_uid": "products",
"details": {
"documents_received": 250,
"documents_indexed": 250
},
"enqueued_at": "2025-01-15T14:30:00Z",
"started_at": "2025-01-15T14:30:01Z",
"finished_at": "2025-01-15T14:30:03Z",
"duration_ms": 2340
}
}
}
List Tasks
GET /search/v1/tasks?status=succeeded&index_uid=products&limit=20
Task Statuses:
| Status | Description |
|---|---|
| enqueued | Task is waiting to be processed |
| processing | Task is currently being processed |
| succeeded | Task completed successfully |
| failed | Task failed with an error |
| canceled | Task was canceled |
Webhooks
Configure webhooks for search events.
Register Webhook
POST /search/v1/webhooks
Request Body:
{
"data": {
"type": "webhooks",
"attributes": {
"url": "https://your-app.com/webhooks/search",
"events": [
"indexing.completed",
"indexing.failed",
"index.created",
"index.deleted"
],
"secret": "your-webhook-secret"
}
}
}
Webhook Events
| Event | Description |
|---|---|
| indexing.completed | Batch indexing completed successfully |
| indexing.failed | Batch indexing failed |
| index.created | New index created |
| index.deleted | Index was deleted |
| settings.updated | Index settings changed |
Webhook Payload Example
{
"event": "indexing.completed",
"occurred_at": "2025-01-15T14:30:03Z",
"data": {
"task_id": "task_xyz123",
"index_uid": "products",
"documents_indexed": 250,
"duration_ms": 2340
}
}
Error Handling
Error Response Format
{
"errors": [
{
"status": "400",
"code": "invalid_filter_syntax",
"title": "Invalid Filter Syntax",
"detail": "The filter 'price <> 100' contains an invalid operator. Use '!=' instead of '<>'.",
"source": {
"parameter": "filter"
}
}
]
}
Common Error Codes
| Code | Status | Description |
|---|---|---|
| index_not_found | 404 | The specified index does not exist |
| document_not_found | 404 | The specified document does not exist |
| invalid_filter_syntax | 400 | Filter syntax is invalid |
| invalid_sort_attribute | 400 | Sort attribute is not sortable |
| attribute_not_filterable | 400 | Attribute is not configured as filterable |
| max_documents_exceeded | 400 | Document batch exceeds maximum size |
| index_already_exists | 409 | Index with this UID already exists |
| rate_limit_exceeded | 429 | Too many requests |
SDK Examples
React Search Component
import { useState, useEffect, useCallback } from 'react';
import { useDebounce } from 'use-debounce';
import { create23BlocksClient } from '@23blocks/sdk';
const client = create23BlocksClient({
urls: { search: 'https://search.api.us.23blocks.com' },
appId: 'your-app-id',
});
function ProductSearch() {
const [query, setQuery] = useState('');
const [debouncedQuery] = useDebounce(query, 300);
const [results, setResults] = useState([]);
const [suggestions, setSuggestions] = useState([]);
const [facets, setFacets] = useState({});
const [filters, setFilters] = useState({});
const [loading, setLoading] = useState(false);
// Fetch suggestions as user types
useEffect(() => {
if (debouncedQuery.length < 2) {
setSuggestions([]);
return;
}
client.search.suggest('products', {
query: debouncedQuery,
limit: 5
}).then(data => {
setSuggestions(data.suggestions);
});
}, [debouncedQuery]);
// Perform search
const performSearch = useCallback(async (searchQuery) => {
if (!searchQuery) {
setResults([]);
setFacets({});
return;
}
setLoading(true);
try {
const response = await client.search.query('products', {
query: searchQuery,
filter: buildFilterString(filters),
facets: ['category', 'brand'],
limit: 20,
attributesToHighlight: ['name', 'description']
});
setResults(response.hits);
setFacets(response.facetDistribution);
// Track search analytics
await client.search.trackEvent({
eventType: 'search',
indexUid: 'products',
query: searchQuery,
resultsCount: response.estimatedTotalHits
});
} finally {
setLoading(false);
}
}, [filters]);
// Handle result click
const handleResultClick = async (hit, position) => {
await client.search.trackEvent({
eventType: 'click',
indexUid: 'products',
query,
documentId: hit.id,
position
});
};
return (
<div className="search-container">
{/* Search Input */}
<div className="search-input-wrapper">
<input
type="text"
value={query}
onChange={(e) => setQuery(e.target.value)}
onKeyDown={(e) => e.key === 'Enter' && performSearch(query)}
placeholder="Search products..."
className="search-input"
/>
{/* Suggestions Dropdown */}
{suggestions.length > 0 && (
<ul className="suggestions-dropdown">
{suggestions.map((suggestion, index) => (
<li
key={index}
onClick={() => {
setQuery(suggestion.suggestion);
performSearch(suggestion.suggestion);
setSuggestions([]);
}}
dangerouslySetInnerHTML={{ __html: suggestion.highlighted }}
/>
))}
</ul>
)}
</div>
<div className="search-layout">
{/* Facets Sidebar */}
<aside className="facets-sidebar">
{Object.entries(facets).map(([facetName, values]) => (
<div key={facetName} className="facet-group">
<h3>{facetName}</h3>
{Object.entries(values).map(([value, count]) => (
<label key={value}>
<input
type="checkbox"
checked={filters[facetName]?.includes(value)}
onChange={() => toggleFilter(facetName, value)}
/>
{value} ({count})
</label>
))}
</div>
))}
</aside>
{/* Results */}
<main className="search-results">
{loading ? (
<div className="loading">Searching...</div>
) : (
<ul>
{results.map((hit, index) => (
<li
key={hit.id}
onClick={() => handleResultClick(hit, index + 1)}
className="result-item"
>
<h3 dangerouslySetInnerHTML={{
__html: hit._formatted?.name || hit.name
}} />
<p dangerouslySetInnerHTML={{
__html: hit._formatted?.description || hit.description
}} />
<span className="price">${hit.price}</span>
</li>
))}
</ul>
)}
</main>
</div>
</div>
);
}
Vue 3 Composable
<script setup>
import { ref, watch, computed } from 'vue';
import { useDebounceFn } from '@vueuse/core';
import { create23BlocksClient } from '@23blocks/sdk';
const client = create23BlocksClient({
urls: { search: 'https://search.api.us.23blocks.com' },
appId: 'your-app-id',
});
const query = ref('');
const results = ref([]);
const suggestions = ref([]);
const facets = ref({});
const selectedFilters = ref({});
const loading = ref(false);
// Debounced suggestion fetch
const fetchSuggestions = useDebounceFn(async (searchQuery) => {
if (searchQuery.length < 2) {
suggestions.value = [];
return;
}
const data = await client.search.suggest('products', {
query: searchQuery,
limit: 5
});
suggestions.value = data.suggestions;
}, 300);
watch(query, (newQuery) => {
fetchSuggestions(newQuery);
});
// Build filter string from selected filters
const filterString = computed(() => {
const parts = [];
for (const [key, values] of Object.entries(selectedFilters.value)) {
if (values.length > 0) {
parts.push(`${key} IN [${values.map(v => `'${v}'`).join(', ')}]`);
}
}
return parts.join(' AND ');
});
// Perform search
async function search() {
if (!query.value) return;
loading.value = true;
try {
const response = await client.search.query('products', {
query: query.value,
filter: filterString.value || undefined,
facets: ['category', 'brand'],
limit: 20
});
results.value = response.hits;
facets.value = response.facetDistribution;
} finally {
loading.value = false;
}
}
// Toggle facet filter
function toggleFilter(facetName, value) {
if (!selectedFilters.value[facetName]) {
selectedFilters.value[facetName] = [];
}
const index = selectedFilters.value[facetName].indexOf(value);
if (index === -1) {
selectedFilters.value[facetName].push(value);
} else {
selectedFilters.value[facetName].splice(index, 1);
}
search();
}
</script>
<template>
<div class="search-page">
<div class="search-box">
<input
v-model="query"
@keyup.enter="search"
placeholder="Search products..."
/>
<ul v-if="suggestions.length" class="suggestions">
<li
v-for="(suggestion, index) in suggestions"
:key="index"
@click="query = suggestion.suggestion; search(); suggestions = []"
v-html="suggestion.highlighted"
/>
</ul>
</div>
<div class="content">
<aside class="filters">
<div v-for="(values, facetName) in facets" :key="facetName">
<h4>{{ facetName }}</h4>
<label v-for="(count, value) in values" :key="value">
<input
type="checkbox"
:checked="selectedFilters[facetName]?.includes(value)"
@change="toggleFilter(facetName, value)"
/>
{{ value }} ({{ count }})
</label>
</div>
</aside>
<main class="results">
<div v-if="loading">Loading...</div>
<div v-else-if="results.length === 0">No results found</div>
<ul v-else>
<li v-for="hit in results" :key="hit.id">
<h3 v-html="hit._formatted?.name || hit.name" />
<p v-html="hit._formatted?.description || hit.description" />
<span class="price">${{ hit.price }}</span>
</li>
</ul>
</main>
</div>
</div>
</template>
Rate Limits
| Plan | Searches/second | Documents/index | Indexes |
|---|---|---|---|
| Free | 10 | 10,000 | 3 |
| Starter | 50 | 100,000 | 10 |
| Growth | 200 | 1,000,000 | 50 |
| Enterprise | Custom | Unlimited | Unlimited |
Best Practices
Indexing
- Batch documents - Index in batches of 100-1000 documents for optimal performance
- Use primary keys - Always define a primary key for document identification
- Configure searchable attributes - Only index fields that need to be searchable
- Set filterable attributes - Pre-configure attributes you'll filter on
Searching
- Use suggestions - Implement auto-complete for better UX
- Apply filters wisely - Filters are fast, use them for category navigation
- Track analytics - Log searches and clicks to improve relevance
- Handle typos - Typo tolerance is enabled by default
Performance
- Debounce queries - Wait 200-300ms between keystrokes before searching
- Limit results - Request only needed fields and reasonable result counts
- Cache static facets - Cache facet distributions that don't change often
- Use pagination - Implement infinite scroll or pagination for large result sets
Related Blocks
- Content Block - Headless CMS for searchable content
- Products Block - Product catalog with search integration
- AI Block - Semantic search and recommendations