contacts: defineTable({
firmId: v.id("firms"),
name: v.string(),
email: v.string(),
phone: v.string(),
searchText: v.string(),
}).searchIndex("search_contacts", {
searchField: "searchText",
filterFields: ["firmId"],
});
```[[Discord multi-field search](<https://discord.com/channels/1019350475847499849/1385358143437017178>)]
- **Single combined field for title+content**:
```ts
report: defineTable({
title: v.optional(v.string()),
content: v.string(),
title_content_lowercase: v.string(),
}).searchIndex("search_title_content", {
searchField: "title_content_lowercase",
});
Then query with .withSearchIndex("search_title_content", q => q.search("title_content_lowercase", term)). [TS filtering & searchIndex]
If you need exact matches on some fields (e.g. zip, city) and fuzzy on others, you typically:
- Use a search index for the fuzzy part (on a combined field).
- Apply exact filters either:
- As
filterFields on the search index (if they’re simple equality), or
- As post‑filters in TypeScript.
4. Tags / array filters
For array membership (e.g. tags), the recommended scalable pattern is a join table:
posts: defineTable({
body: v.string(),
tags: v.array(v.string()),
}),
tagged_posts: defineTable({
tag: v.string(),
post: v.id("posts"),
}).index("by_tag", ["tag"]);
Then query tagged_posts by tag and join back to posts. [Tags join table]
This gives you efficient “has tag X” filters that can be combined with other indexed filters on the main table.
5. Streams and “max rows read” concerns