#combined search index

8 messages · Page 1 of 1 (latest)

terse berry
#

how can i use index and search index together?

await ctx.db .query("podcasts") .withSearchIndex("by_name", (q) => q.search("name", search ?? "")) .withIndex("by_userId", (q) => q.eq("userId", userId).gte("_creationTime", key), ) .collect();

wintry slateBOT
#

Hi @terse berry. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave:

#

Unfortunately, you cannot combine .withSearchIndex() and .withIndex() in a single query in Convex. A query can only use a single index, and a search index is both an index and an order (by relevance), making it incompatible with a regular index in the same query. [dynamic query builders]

Workarounds

Option 1: Use .withSearchIndex() with a filter field

If userId is a field you want to filter on alongside the search, you can define it as a filterFields in your search index:

// schema.ts
podcasts: defineTable({
  name: v.string(),
  userId: v.id("users"),
}).searchIndex("by_name", {
  searchField: "name",
  filterFields: ["userId"],
})

Then query like this:

await ctx.db
  .query("podcasts")
  .withSearchIndex("by_name", (q) =>
    q.search("name", search ?? "").eq("userId", userId)
  )
  .collect();

This lets you filter by userId within the search index itself. Note that _creationTime range filtering is not supported in search index filter expressions.

Option 2: Conditional query (search vs. index)

If the search term is optional, use two separate queries depending on whether a search term is provided:

const result = search
  ? await ctx.db
      .query("podcasts")
      .withSearchIndex("by_name", (q) =>
        q.search("name", search).eq("userId", userId)
      )
      .collect()
  : await ctx.db
      .query("podcasts")
      .withIndex("by_userId", (q) =>
        q.eq("userId", userId).gte("_creationTime", key)
      )
      .collect();

This is a known pattern in the Convex community — using a search index when a search term exists, and falling back to a regular index otherwise. [search index empty query]

Key limitation

#

The _creationTime range filter (.gte("_creationTime", key)) cannot be used inside a search index. If you need both full-text search and time-range filtering, you'd need to post-filter the search results in TypeScript after collecting them.

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:

#

@terse berry Kapa.ai is still learning and improving, please let me know how I did by reacting below.

terse berry
#

@wintry slate but how do i get the pagination with the aggregat component with the search

wintry slateBOT
#

Let me check the knowledge base for you again. One moment! :books:

#

The knowledge sources do not contain enough information to describe how to combine the Aggregate component with a search index for pagination.

What the knowledge sources do cover is:

  • Aggregate component is useful for offset-based pagination (e.g., skip to page N) and counting, but it works with regular database indexes — not search indexes. [offset-based pagination]

  • Search index pagination uses .paginate(paginationOpts) directly on a .withSearchIndex() query, like:

export const list = query({
  args: { paginationOpts: paginationOptsValidator },
  handler: async (ctx, args) => {
    return await ctx.db
      .query("podcasts")
      .withSearchIndex("by_name", (q) =>
        q.search("name", search ?? "").eq("userId", userId)
      )
      .paginate(args.paginationOpts);
  },
});

Then use usePaginatedQuery on the frontend. [paginated queries]

However, the knowledge sources do not describe a supported pattern for combining the Aggregate component with a search index query. The Aggregate component's offset-based pagination works with regular indexes, and it's unclear from the available sources whether it can be used alongside search indexes.

I'd recommend asking in the Convex Discord for guidance on this specific combination, as it goes beyond what the available documentation covers.

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: