frogbot.search(), REST, or GraphQL, or search several collections in one call with frogbot.searchMany(). Ranking runs in the database.
The Postgres and Vercel Postgres, SQLite, and MongoDB adapters implement search, and every option on this page works the same way on each. The Cloudflare D1 adapter implements lexical search; D1 has no native vector search, so an index with vector fails setup with reason engine-gap. Adapter pages list engine-specific details such as supported metrics and languages. On other adapters, any configured search index fails database initialization with SearchCapabilityError and reason not-implemented; leave search off those collections and use vector fields for storage only.
Configure indexes
Each key undersearch names an index and selects the fields it ranks:
dimensions belongs to the vector field, not the index. When an index includes both components, supplying both query inputs selects hybrid mode with configured reciprocal-rank fusion (rrf). FrogBot does not calculate embeddings; your application supplies stored vectors and query vectors. The adapter creates any native indexes as part of the database schema.
Index options
An index with both
lexical and vector accepts a hybrid object:
Query
The operation acceptscollection, a named index, and query (SearchOptions). The query inputs select the mode:
Search options
Search enforces access and requires
req unless you explicitly set overrideAccess: true. index is currently typed as a string; index names are checked against the collection at runtime. The vector must match the indexed field’s dimensions and contain finite numbers. A text query must be non-empty, and limit defaults to 10 (positive integer required). where, select, depth (defaults to 0), candidates, draft, locale, and fallbackLocale are options, but which filters and modes can run depends on the adapter and selected index.
The database ranks only documents the caller can read: collection read access and your where filter are applied before ranking. FrogBot then reads the ranked documents in one ordinary collection read, so collection and field read hooks run on each result as they do for any other read. A ranked document that this read does not return, for example because it was deleted or is no longer readable, is left out; hits can therefore be shorter than limit. Field-level read access on a ranked or filtered field stops the query with SearchFilterUnsupportedError while access is enforced, because scores computed over a hidden value would reveal it.
The result is { mode, ranking, hits }: mode is lexical, vector, or hybrid; ranking contains method, higherIsBetter, and approximate; each hit has { doc, score }, in ranked order. Hybrid results add component details, described below. Scores are ordering values, not probabilities; scores from different methods are not directly comparable, and neither are scores from different collections. An empty hits array is a successful no-match result. Results hold only the top hits: there are no totals, pages, or cursors. Ranking and hybrid fusion run in the database, with no JavaScript similarity or fusion fallback.
Candidates
candidates sets how many top matches a ranked component considers before limit is applied. It has one meaning on every adapter and applies to vector and hybrid queries:
- Approximate vector search asks the nearest-neighbour index for this many neighbours. A larger value finds more true neighbours, and leaves more hits after your filters on adapters whose index cannot filter, at the cost of a slower query.
- Hybrid search takes this many top matches from the lexical and vector components separately, then fuses them. A document outside both candidate lists cannot appear in the results, so a larger value can surface documents that rank moderately in both components.
candidates, then the index’s defaultCandidates, then 100, and is never lower than limit:
candidates must be a positive integer.
Hybrid components
Each hybrid hit also hascomponents, the rank and score the document received from each component before fusion. A component the document did not match is null:
ranking.components describes each one with method, higherIsBetter, and approximate, so you can, for example, drop hits whose vector similarity is below a threshold. Lexical and vector results do not include components.
Search several collections
frogbot.searchMany() runs one query against several collections and returns one ranked list per collection. Each entry names a collection and one of its indexes, plus the options that depend on that collection’s fields:
results follows the order of collections. Each list holds up to limit hits from its collection, in the same order and with the same scores as a frogbot.search() of that collection alone. A search box can show the lists as sections, such as “Articles” and “FAQs”. FrogBot doesn’t order hits from different collections against each other, because their scores aren’t comparable. When you need one list, merge the results with rerank.
Search many options
These options apply to every collection in the call:Collection entries
Each entry incollections accepts:
Any other key on an entry, such as its own
query or limit, fails with SearchValidationError.
Search many results
The result is{ results }, with one item per entry:
Each item is typed by its collection: in the example above,
articles.hits[0].doc is an article and faqs.hits[0].doc is a FAQ. When you loop over results, checking item.collection narrows the document type.
Failures
The call succeeds or fails as a whole. Before running any query, FrogBot checks the list and every entry: an empty list, an entry that isn’t an object or lackscollection or index, an unknown entry key, a repeated collection, an index that isn’t configured, an index without the query’s mode, a vector of the wrong length, or an invalid option fails with SearchValidationError. locale: 'all' fails with SearchFilterUnsupportedError, also with status 400.
FrogBot then searches the collections one after another, in list order, on the same request, so the call also works inside a database transaction. If any collection fails, the whole call fails and returns no results. A collection the user can’t read fails the call with 403, as a single search of it would, rather than being left out. Errors about one entry’s index, query, options, or filters name its collection and index, for example Search index 'answers' in collection 'faqs' requires a vector of 768 finite numbers.
Scores across collections
Within one collection, a hit ranked higher is a better match. Across collections the scores don’t line up:- Text scores are relative to each collection. On SQLite, D1, and MongoDB, BM25 weighs each word by how rare it is in that collection’s records, so the same word scores differently in 40 FAQs than in 8,000 articles. On every adapter, the score also depends on which fields and language the index ranks.
- Vector scores depend on each index’s metric. Cosine and dot product are similarities where higher is better; Euclidean is a distance where lower is better. Two vector fields with the same dimensions can still hold embeddings from different models, and FrogBot doesn’t record which model produced a vector, so it can’t tell whether two similarities are comparable.
- Hybrid scores come from rank positions. Reciprocal rank fusion scores a document by where it ranked within its own collection, so comparing fused scores across collections compares positions, not how well each document matched.
Merge results with rerank
To get one list, for example to choose passages for an AI answer, merge the lists and order them withfrogbot.rerank(). It scores each text against the query with your rerank model, so the scores are comparable regardless of which collection a hit came from:
ranking is sorted by relevance, and originalIndex points back into documents. Reranking calls a model on every search, which adds cost and delay, and needs a configured rerank model.
REST API
Every collection with a search index gets aPOST /api/{collection-slug}/search endpoint. The JSON body accepts the same options as frogbot.search() except collection, overrideAccess, and req: the endpoint always searches as the requesting user with access enforced.
{ mode, ranking, hits } object that frogbot.search() returns. See the REST API for authentication.
POST /api/frogbot/search runs frogbot.searchMany(). It exists when at least one collection has a search index. The body accepts collections, query, limit, draft, locale, and fallbackLocale, and each entry accepts the same keys as in searchMany(); any other key fails with 400. It also searches as the requesting user with access enforced, and a collection that user can’t read fails the whole request with 403.
{ results } object that frogbot.searchMany() returns. Both endpoints report errors with the statuses in Setup and errors.
GraphQL
Every searchable collection with GraphQL queries enabled also gets asearch query named after its plural GraphQL name, such as searchArticles. Setting the collection’s graphQL option to false or graphQL.disableQueries to true omits it.
components { lexical { rank score } vector { rank score } }, and ranking exposes components { lexical { method higherIsBetter approximate } vector { ... } }. Both are null for lexical and vector queries.
The query accepts index, text, vector, where, limit, candidates, draft, and, when localization is enabled, locale and fallbackLocale. doc uses the collection’s GraphQL type. Like the REST endpoint, the query always enforces the requesting user’s access. See the GraphQL API.
GraphQL has no query that searches several collections. Call each collection’s search query in one request instead, using aliases to name the results and to call the same query more than once:
Setup and errors
FrogBot checks every configured index when the database initializes, including when you create or run migrations. The check is silent when a configured index is compatible. On an adapter without search, any configured index fails initialization withSearchCapabilityError (reason: 'not-implemented'), naming the collection, index, and mode. An adapter also stops setup when the engine cannot run an option (engine-gap) or a database prerequisite is missing; see the Postgres and SQLite prerequisites and the D1 limits.
The core also exports SearchValidationError, SearchFilterUnsupportedError, and SearchReadinessError for invalid inputs, unsupported filters or visibility rules, and invalid or unready ranked results. REST and GraphQL report them with these HTTP statuses:
A caller without collection read access receives
403. SearchCapabilityError carries a reason: not-implemented, engine-gap, missing-prerequisite, permission-denied, or setup-failed. See the database status for adapter support.
Adapter integration
Database adapters add search through asearch object on the adapter. capabilities declares which modes an index supports. buildSchema receives every collection’s normalized indexes while the database initializes, before tables and indexes are created, so native search indexes are part of schema push and migrations. readiness can reject a query while an index is still building, and search returns ranked { id, score } rows for FrogBot to read back. The Postgres, SQLite, MongoDB, and D1 adapters implement this contract. search receives the resolved candidates count for vector and hybrid queries.
You can optionally call the existing frogbot.rerank() on readable hits, as in Merge results with rerank, or compose retrieval and answer generation in your own application. There is no automatic embedding or built-in RAG preset for this collection search API.