Search

Use full-text search to find records across text fields in any collection.

Full-text search lets you find records by matching a search term across all text-type fields in a collection. Use the search query parameter or the SDK's search option.

#How search works

When you pass a search term, EmuView generates LIKE '%term%' clauses for every searchable field in the collection. The clauses are combined with OR, so a match on any field returns the record.

Searchable field types: text, email, url, slug, richtext, markdown, enum.

Fields of other types (number, boolean, json, relation, file, geo) are not included in search queries.

#Steps

#1. Create a collection with searchable fields

Searchable fields are identified by their type — no extra configuration is needed. If your collection has text, email, or richtext fields, they are searchable automatically.

// This collection has three searchable fields: title (text), description (richtext), status (enum)
await fetch('https://your-api.example.com/api/v1/collections', {
	method: 'POST',
	headers: {
		Authorization: 'Bearer sk-your-api-key',
		'Content-Type': 'application/json'
	},
	body: JSON.stringify({
		name: 'products',
		storageType: 'd1',
		fields: [
			{ name: 'title', type: 'text', required: true, indexed: true },
			{ name: 'price', type: 'decimal', required: true },
			{ name: 'description', type: 'richtext' },
			{ name: 'status', type: 'enum', enumValues: ['draft', 'published', 'archived'] }
		]
	})
});

#2. Search for records

#HTTP
GET /api/v1/collections/products/records?search=widget
Authorization: Bearer sk-your-api-key
#SDK
const results = await sdk.collection('products').list({
	search: 'widget'
});

console.log(`Found ${results.total} records matching "widget"`);

#3. Combine search with filters

Search and filters work together with AND logic. The record must match the search term AND satisfy all filter conditions:

#HTTP
GET /api/v1/collections/products/records?search=widget&filter={"status":{"_eq":"published"}}
Authorization: Bearer sk-your-api-key
#SDK
const results = await sdk.collection('products').list({
	search: 'widget',
	filter: { status: { _eq: 'published' } },
	sortBy: '-created_at',
	limit: 20
});

This query returns records where any text field contains "widget" AND the status is "published".

#4. Combine search with sorting and pagination

const results = await sdk.collection('products').list({
	search: 'premium',
	filter: { price: { _gte: 10 } },
	sortBy: '-created_at',
	page: 1,
	limit: 25
});

#Full-text and semantic search (search indexes)

The search parameter above is a simple LIKE scan — fine for admin views and small collections, but it reads every row. For real search features (a public search page, relevance ranking, typo tolerance, multi-collection search), build a search index:

  • POST /api/v1/search-indexes creates a named index compiled from one or more source collections into a dedicated FTS5 artifact; GET /api/v1/search-indexes/:name/query?q=… queries it with BM25 relevance ranking. Manage and rebuild via POST /api/v1/search-indexes/:name/rebuild. The SDK covers the full lifecycle: sdk.search.create(config), .update(), .rebuild(name), .status(name), .delete(name), and .query(name, { q, semantic: true }).
  • Semantic search is built in: set "semanticSearch": true (optionally embeddingModel) on the index config and pass semantic=true on queries — results are re-ranked by vector similarity fused with the FTS ranking. This requires the gateway's Workers AI (AI) binding, which the standard deploy templates include; on an instance without it, semantic=true queries return a typed 503 semantic_unavailable error (lexical queries keep working), and GET /health lists AI under missing.optional. See Enabling semantic search for the full setup.
  • Access control is a post-filter over collection policy. Every hit on a returned page is re-authorized against the source collection's row-level item_filter for the calling user (anonymous callers use the public role's filter), so per-row visibility must be encoded in the collection's policy — app-side visibility logic is not replicated by the index. total and facets are aggregates over the matched set, which is never authorized row by row, so they come back as null unless the caller may read every row of every collection the index serves — refused rather than approximated (ADR 0025). Use hasMore to page when total is null.
  • Indexes are not auto-rebuilt when source data changes heavily (bulk imports, truncate, collection delete) — they are marked stale and keep serving the compiled data until you rebuild. Check is_stale/stale_reason on the index list; the dashboard surfaces stale indexes with a one-click Rebuild on Settings → Databases.
  • Filtering an index uses its own grammar, not the one above. The filter={"status":{"_eq":"published"}} syntax on this page belongs to the collection records API. A search index is filtered with filter: { status: 'published' } (scalar = equality, object = range) on fields carrying the filter role — see Filtering results in the SDK search guide.

#What you learned

  • Search matches across all text-type fields using LIKE '%term%'
  • Searchable types: text, email, url, slug, richtext, markdown, enum
  • Search combines with filters using AND logic
  • No additional configuration is needed — searchable fields are identified by type
  • For ranked, typo-tolerant, or semantic search, create a search index (/api/v1/search-indexes)

#Next steps