Search by meaning
Semantic search: query a collection with some text and get rows ranked by similarity. With an embeddings model, "excursion" finds the museum trip; without one, it falls back to fuzzy word matching. Vectors live in the same SQLite file as the rows, with no index to build.
const { items } = await sluurp.collection("notes").list({ near: "museum trips" });GET /api/collections/notes/records?near=museum%20tripsEach row gets a _score (1 = identical, lower = less similar). Everything else about list still applies: rules limit which rows are considered, and filter narrows them before ranking:
await sluurp.collection("notes").list({
near: "a pupil who is struggling",
filter: `class = "4B"`,
perPage: 10,
});Fields
By default, fields marked "searchable": true are used (the same ones search= uses). If there are none, text, email and URL fields are used. Override with on:
await sluurp.collection("posts").list({ near: "school trip", on: "title,body" });Embeddings
How vectors are computed depends on the AI settings in the admin UI (Platform → AI).
- Local model file: Sluurp computes embeddings in-process, nothing else to run. Point it at a
.ggufembeddings model such as nomic-embed-text-v1.5 (84 MB); Find models on this machine lists ones already downloaded by LM Studio or Hugging Face. Supports BERT-style models (Nomic, MiniLM, BGE, E5) in F32, F16, Q8_0, Q4_K, Q5_K or Q6_K. Tens of milliseconds per text on a laptop. - Embeddings API: any OpenAI-compatible embeddings endpoint (local Ollama, OpenAI, …). With Ollama:
ollama pull nomic-embed-text, base URLhttp://localhost:11434/v1, modelnomic-embed-text. Text is sent only to that server. - Neither: vectors are built from words and character trigrams. “museum trips” still finds “Museum trip — 4B” and typos are tolerated, but “excursion” won’t match. Nothing is sent anywhere.
Switching models is safe: vectors are stored per model and recomputed lazily.
near and search
search= | near= | |
|---|---|---|
| Matches | rows containing the words | rows about the same thing |
| Order | by sort | most similar first |
| Engine | SQLite full-text index | exact vector comparison |
| Good for | names, codes, exact phrases | questions, topics, “more like this” |
Use search when users know the exact words, near when they know the meaning.
How it works
- Vectors are computed lazily the first time a row is searched, and stored with a hash of the source text; they’re recomputed when the text changes. Writes cost nothing extra.
- Vectors are ordinary rows in the project database (
_vectors_<dimensions>), backed up with everything else, and compared with sqlite-vector (Apache-2.0), compiled into the binary. - Search is exact, not approximate. Every visible row (up to 5,000 after
filter) is compared, so there’s no index to tune and no missed rows. For bigger collections, narrow first withfilter(a class, a term, the last year). - Max 500 rows per page, as with any list.
The social feed
Posts can be searched the same way, across feeds the user can read:
const { items } = await sluurp.social.feed({ near: "lost property" });GET /api/social/feed?near=lost%20propertyA search palette can show both: semantic matches next to keyword matches.