| path | docs/api/Search_API.mdx | ||||
|---|---|---|---|---|---|
| title | Search API | ||||
| description | Complete API reference for full-text search across collections in SveltyCMS. | ||||
| order | 9 | ||||
| icon | mdi:magnify | ||||
| author | admin | ||||
| created | 2025-10-05 | ||||
| updated | 2025-10-05 | ||||
| tags |
|
The Search API provides powerful full-text search capabilities across collections. It supports advanced filtering, sorting, pagination, and permission-aware results.
Base Path: /api/search
All search endpoints require authentication:
Cookie: session=your-session-idPermissions Required: Authenticated user (results filtered by permissions)
Searches across all or specified collections.
GET /api/searchQuery Parameters:
q(string, required) - Search query textcollections(string, optional) - Comma-separated collection names to searchpage(number, optional) - Page number (1-based) - default:1limit(number, optional) - Results per page (1-100) - default:25sortField(string, optional) - Field to sort by - default:updatedAtsortDirection(string, optional) - Sort direction (asc,desc) - default:descfilter(string, optional) - JSON-encoded filter criteriastatus(string, optional) - Filter by status (published,draft,archived)
Headers:
Cookie: session=your-session-idPermissions Required: Authenticated user
Success (200):
{
"success": true,
"results": [
{
"_id": "entry123",
"collection": "posts",
"title": "Introduction to SveltyCMS",
"excerpt": "Learn how to use SveltyCMS...",
"author": "user123",
"status": "published",
"createdAt": "2025-10-01T10:00:00Z",
"updatedAt": "2025-10-05T14:30:00Z",
"score": 0.95
},
{
"_id": "entry124",
"collection": "pages",
"title": "About SveltyCMS",
"excerpt": "SveltyCMS is a headless CMS...",
"author": "user456",
"status": "published",
"createdAt": "2025-09-15T08:00:00Z",
"updatedAt": "2025-10-03T12:00:00Z",
"score": 0.87
}
],
"pagination": {
"page": 1,
"limit": 25,
"total": 42,
"totalPages": 2,
"hasNextPage": true,
"hasPreviousPage": false
},
"meta": {
"query": "SveltyCMS",
"collections": ["posts", "pages"],
"processingTime": "45ms"
}
}Empty Results (200):
{
"success": true,
"results": [],
"pagination": {
"page": 1,
"limit": 25,
"total": 0,
"totalPages": 0
},
"meta": {
"query": "nonexistent",
"collections": ["posts"],
"processingTime": "12ms"
}
}Error Responses:
// 401 Unauthorized
{
"success": false,
"message": "Unauthorized"
}
// 400 Bad Request - Invalid filter
{
"success": false,
"message": "Invalid filter parameter"
}Searches using full-text indexes for better performance.
GET /api/index/searchQuery Parameters:
q(string, required) - Search querycollection(string, required) - Collection to searchfields(string, optional) - Comma-separated fields to searchlimit(number, optional) - Max results - default:25
Headers:
Cookie: session=your-session-idPermissions Required: Authenticated user
Success (200):
{
"success": true,
"results": [
{
"_id": "entry123",
"title": "Search Result Title",
"content": "Content with matching query...",
"score": 0.95,
"highlights": {
"title": "Search <mark>Result</mark> Title",
"content": "Content with matching <mark>query</mark>..."
}
}
],
"total": 1,
"collection": "posts"
}Searches across text fields with relevance scoring:
GET /api/search?q=svelte+cms+headlessScoring Factors:
- Term frequency
- Field weighting (title > content)
- Document freshness
- Exact vs partial matches
Search specific fields only:
GET /api/search?q=author:john&collections=postsCombine search with filters:
GET /api/search?q=cms&filter={"status":"published","author":"user123"}Filter by date ranges:
GET /api/search?q=news&filter={"createdAt":{"$gte":"2025-01-01","$lte":"2025-12-31"}}- AND - All terms must match:
svelte AND cms - OR - Any term must match:
svelte OR cms - NOT - Exclude terms:
cms NOT wordpress
- Asterisk (*) - Multiple characters:
svelte*matches "svelte", "sveltejs", "sveltekit" - Question (?) - Single character:
c?smatches "cms", "css"
- Quotes - Exact phrase:
"headless cms"matches exact phrase
- Colon (:) - Field-specific:
author:johnsearches author field
Search results are automatically filtered by user permissions:
- See all results regardless of status
- Access to all collections
- Only see
publishedcontent - Only collections they have
readpermission for - Cannot see others' draft content
- Results automatically scoped to user's tenant
- Cross-tenant searches blocked
The Search API uses database-agnostic query methods:
// Uses QueryBuilder abstraction
const queryBuilder = new QueryBuilder(dbAdapter);
// Search across collections
const results = await queryBuilder
.collection(collectionName)
.search(searchQuery)
.filter(baseFilter)
.sort(sortField, sortDirection)
.paginate(page, limit)
.execute();
// No direct database queriesSearch Methods:
- MongoDB - $text index and $regex queries
- PostgreSQL - Full-text search with tsvector
- MySQL - FULLTEXT indexes with MATCH AGAINST
All abstracted through the adapter interface.
When multi-tenant mode is enabled:
- Search automatically scoped to tenant
- Collections filtered by tenant
- Results include tenant context
Tenant-Scoped Search:
const baseFilter = MULTI_TENANT ? { tenantId } : {};
const results = await search(query, baseFilter);Create text indexes for search fields:
// MongoDB
db.collection('posts').createIndex({
title: 'text',
content: 'text',
excerpt: 'text'
});
// PostgreSQL
CREATE INDEX posts_search_idx ON posts
USING GIN (to_tsvector('english', title || ' ' || content));- Popular searches cached for 5 minutes
- Results cached by query hash
- Invalidated on content updates
- Limit results to prevent performance issues
- Use cursor-based pagination for large datasets
- Default limit: 25, max: 100
Track search queries for analytics:
{
"query": "svelte cms",
"userId": "user123",
"timestamp": "2025-10-05T14:30:00Z",
"resultsCount": 42,
"clickedResult": "entry123"
}// Basic search
const response = await fetch('/api/search?q=svelte&collections=posts', {
credentials: 'include'
});
const { results, pagination } = await response.json();
// Advanced search with filters
const advancedResponse = await fetch(
'/api/search?q=cms&filter=' +
encodeURIComponent(
JSON.stringify({
status: 'published',
author: 'user123'
})
) +
'&sortField=createdAt&sortDirection=desc',
{ credentials: 'include' }
);
// Paginated search
for (let page = 1; page <= totalPages; page++) {
const pageResponse = await fetch(`/api/search?q=news&page=${page}&limit=10`, { credentials: 'include' });
const data = await pageResponse.json();
console.log(`Page ${page}:`, data.results.length, 'results');
}# Simple search
curl -X GET "https://cms.example.com/api/search?q=svelte" \
-H "Cookie: session=$SESSION"
# Search specific collections
curl -X GET "https://cms.example.com/api/search?q=cms&collections=posts,pages" \
-H "Cookie: session=$SESSION"
# Advanced search with filters
curl -X GET 'https://cms.example.com/api/search?q=news&filter={"status":"published"}' \
-H "Cookie: session=$SESSION"<script>
let query = '';
let results = [];
async function search() {
const response = await fetch(`/api/search?q=${encodeURIComponent(query)}`);
const data = await response.json();
results = data.results;
}
function debounce(fn, delay) {
let timeout;
return (...args) => {
clearTimeout(timeout);
timeout = setTimeout(() => fn(...args), delay);
};
}
const debouncedSearch = debounce(search, 300);
</script>
<input type="search" bind:value={query} on:input={debouncedSearch} placeholder="Search..." />
{#each results as result}
<div class="result">
<h3>{result.title}</h3>
<p>{result.excerpt}</p>
</div>
{/each}For implementation details, see:
src/routes/api/search/+server.ts- Main search endpointsrc/routes/api/index/search/+server.ts- Index searchsrc/utils/QueryBuilder.ts- Query builder utilitysrc/content/ContentManager.ts- Content manager