Skip to content

Full Text Search

This doc is about full-text search capabilities. For vector similarity search and semantic search, see the Vector Embeddings documentation.

Full-Text Search allows you to search text content efficiently.

Declare the full-text index with the collection schema: the fts block lists the indexed fields and vski apply creates it via a migration (see IaC).

vski/default/collections.yaml
version: 1
database: default
collections:
articles:
type: base
fields:
- {name: title, type: text, required: true}
- {name: content, type: text, required: true}
- {name: tags, type: json}
fts:
fields: [title, content] # full-text index over these fields
Terminal window
vski apply # creates the collection and its FTS index

Queries run through the Python SDK (pip install vski-sdk); the examples below assume an authenticated client is in scope (see Collections).

# Search for articles containing "react"
results = await client.collection("articles").search("react", snippet=True)
for item in results["items"]:
print(item["title"])
# "React Tutorial"
# "Building React Apps"

VSKI supports FTS5 query syntax:

# Search for exact phrase
results = await client.collection("articles").search('"react hooks"')
# Search for articles containing react OR vue
results = await client.collection("articles").search("react OR vue")
# Search for articles containing both react AND hooks
results = await client.collection("articles").search("react hooks")
# Search for articles containing "react" but not "angular"
results = await client.collection("articles").search("react NOT angular")
# Search for words starting with "react"
results = await client.collection("articles").search("react*")
# Search for words within 5 words of each other
results = await client.collection("articles").search(
'"react hooks" NEAR/5 "components"'
)

The search endpoint runs the FTS query; structured filters belong to get_list(). Use the ~ substring operator to combine a text match with regular conditions in a single query:

# Full-text query
results = await client.collection("articles").search("react tutorial", per_page=20)
# Text match combined with regular filters
page = await client.collection("articles").get_list(
1, 20, filter='published = true AND title ~ "react tutorial"'
)
results = await client.collection("articles").search(
"react",
per_page=10, # limit results (page size)
expand="author", # expand relations
)
# which fields are searched is fixed by fts.fields in the collection YAML

Results come back in FTS relevance order (the raw rank score is not part of the response):

results = await client.collection("articles").search("react")
for item in results["items"]:
print(item["title"])
results = await client.collection("articles").search("react")
# Rank by another field once the results are back
ranked = sorted(
results["items"], key=lambda item: item.get("views", 0), reverse=True
)
# Or sort in the database without a full-text query
most_viewed = await client.collection("articles").get_list(1, 20, sort="-views")

Implement autocomplete suggestions:

async def get_suggestions(query: str):
if len(query) < 2:
return []
results = await client.collection("articles").search(f"{query}*", per_page=5)
return [item["title"] for item in results["items"]]

Track search queries for analytics:

from datetime import datetime, timezone
async def log_search(query: str, results_count: int, user_id: str | None = None):
await client.collection("search_logs").create({
"query": query,
"userId": user_id,
"timestamp": datetime.now(timezone.utc).isoformat(),
"resultsCount": results_count,
})
# Usage
results = await client.collection("articles").search(query)
await log_search(query, results["totalItems"], current_user_id)

Create appropriate indexes for search:

collections:
products:
type: base
fields:
- {name: name, type: text, required: true}
- {name: description, type: text}
- {name: category, type: text}
indexes:
- {name: products_category, sql: "CREATE INDEX products_category ON products(category)"}
fts:
fields: [name, description]
Terminal window
vski apply # FTS over name/description plus a regular index on category

Cache search results:

search_cache: dict = {}
async def cached_search(query: str):
if query in search_cache:
return search_cache[query]
results = await client.collection("articles").search(query)
search_cache[query] = results
return results

Debounce search input:

import asyncio
pending: asyncio.Task | None = None
async def run_search(delay: float, query: str):
await asyncio.sleep(delay)
results = await client.collection("articles").search(query)
display_results(results["items"])
def debounce_search(query: str, delay: float = 0.3):
global pending
if pending and not pending.done():
pending.cancel()
pending = asyncio.create_task(run_search(delay, query))
# Search products with filters
async def search_products(
query: str,
category: str | None = None,
min_price: float | None = None,
):
conditions = [f'name ~ "{query}"', "published = true"]
if category is not None:
conditions.append(f'category = "{category}"')
if min_price is not None:
conditions.append(f"price >= {min_price}")
return await client.collection("products").get_list(
1, 20,
filter=" AND ".join(conditions),
sort="-popularity",
)
# FAQ search with relevance ranking
async def search_faq(query: str):
return await client.collection("faqs").search(query, per_page=5)
MethodEndpointDescription
GET/api/collections/:name/records/searchFull-text search
GET/api/collections/:name/recordsList with search parameter
  1. Use appropriate indexing - Create FTS indexes for searchable fields
  2. Optimize query syntax - Use specific queries instead of broad ones
  3. Implement pagination - Always use limits with search results
  4. Cache popular searches - Reduce server load
  5. Monitor search performance - Track slow queries
  6. Normalize text - Consistent text formatting improves search