Files
wiki/public/raw/docs-search.md

8.7 KiB

Search


Type: page Date: 2026-01-08

Keyword search matches exact words using Convex full-text search. Results update instantly as you type.

For meaning-based search that finds conceptually similar content, see Semantic Search.


Keyboard shortcuts

Press Cmd+K (Mac) or Ctrl+K (Windows/Linux) to open search. Click the magnifying glass icon works too.

Key Action
Cmd+K / Ctrl+K Open/close search
Tab Switch between Keyword and Semantic modes
Navigate results
Enter Select result
Esc Close modal

How keyword search works

┌─────────────────────────────────────────────────────────────────────────┐
│                      KEYWORD SEARCH FLOW                                │
└─────────────────────────────────────────────────────────────────────────┘

  ┌──────────┐    ┌─────────────┐    ┌──────────────────┐
  │  Cmd+K   │───▶│ SearchModal │───▶│  Convex Query    │
  └──────────┘    └─────────────┘    └────────┬─────────┘
                                              │
                       ┌──────────────────────┴──────────────────────┐
                       ▼                                             ▼
              ┌────────────────┐                           ┌────────────────┐
              │  search_title  │                           │ search_content │
              │    (index)     │                           │    (index)     │
              └───────┬────────┘                           └───────┬────────┘
                      │                                            │
                      └──────────────────┬─────────────────────────┘
                                         ▼
                              ┌─────────────────────┐
                              │ Dedupe + Rank       │
                              │ (title matches first)│
                              └──────────┬──────────┘
                                         ▼
                              ┌─────────────────────┐
                              │  Search Results     │
                              │  (max 15)           │
                              └──────────┬──────────┘
                                         ▼
                              ┌─────────────────────┐
                              │ Navigate + ?q=term  │
                              └──────────┬──────────┘
                                         ▼
                              ┌─────────────────────┐
                              │ Highlight matches   │
                              │ + scroll to first   │
                              └─────────────────────┘
  1. User presses Cmd+K to open SearchModal
  2. SearchModal sends reactive query to Convex as user types
  3. Convex searches both title and content indexes in parallel
  4. Results are deduplicated (same post can match both indexes)
  5. Results ranked with title matches first, limited to 15
  6. User selects result, navigates with ?q=searchterm param
  7. Destination page highlights all matches and scrolls to first

Keyword search is the default because it's:

  • Instant - Results return in milliseconds, no API calls
  • Free - No external services, no per-query costs
  • Reactive - Updates in real-time as you type
  • Highlightable - Matches exact words, so results can be highlighted on the destination page

Use keyword search when you know the exact terms, code snippets, or commands you're looking for.

How search indexes work

Search indexes are defined in convex/schema.ts on the posts and pages tables:

posts: defineTable({
  title: v.string(),
  content: v.string(),
  published: v.boolean(),
  // ... other fields
})
  .searchIndex("search_content", {
    searchField: "content",
    filterFields: ["published"],
  })
  .searchIndex("search_title", {
    searchField: "title",
    filterFields: ["published"],
  }),

Each table has two search indexes:

  • search_title - Searches the title field
  • search_content - Searches the full markdown content

The filterFields: ["published"] allows filtering to only published content without a separate query.

Search query implementation

The search query in convex/search.ts searches both titles and content, then deduplicates and ranks results:

// Search posts by title
const postsByTitle = await ctx.db
  .query("posts")
  .withSearchIndex("search_title", (q) =>
    q.search("title", args.query).eq("published", true)
  )
  .take(10);

// Search posts by content
const postsByContent = await ctx.db
  .query("posts")
  .withSearchIndex("search_content", (q) =>
    q.search("content", args.query).eq("published", true)
  )
  .take(10);

Key features:

  • Dual search - Searches both title and content indexes
  • Filter by published - Only returns published content
  • Deduplication - Removes duplicates when a post matches both title and content
  • Ranking - Title matches sort before content-only matches
  • Snippets - Generates context snippets around the search term
  • Unlisted filtering - Excludes posts with unlisted: true

Frontend search modal

The SearchModal component (src/components/SearchModal.tsx) provides the UI:

// Reactive search query - updates as you type
const results = useQuery(
  api.search.search,
  searchQuery.trim() ? { query: searchQuery } : "skip"
);

The "skip" parameter tells Convex to skip the query when the search field is empty, avoiding unnecessary database calls.

Search result highlighting

When you click a search result, the app navigates to the page with a ?q= parameter. The useSearchHighlighting hook (src/hooks/useSearchHighlighting.ts) then:

  1. Waits for page content to load
  2. Finds all occurrences of the search term
  3. Wraps matches in <mark> tags with highlight styling
  4. Scrolls to the first match
  5. Highlights pulse, then fade after 4 seconds
  6. Press Esc to clear highlights manually

Files involved

File Purpose
convex/schema.ts Search index definitions
convex/search.ts Search query with deduplication and snippets
src/components/SearchModal.tsx Search UI with keyboard navigation
src/components/Layout.tsx Keyboard shortcut handler (Cmd+K)
src/hooks/useSearchHighlighting.ts Result highlighting and scroll-to-match

Adding search to new tables

To add search to a new table:

  1. Add a search index in convex/schema.ts:
myTable: defineTable({
  title: v.string(),
  body: v.string(),
  status: v.string(),
})
  .searchIndex("search_body", {
    searchField: "body",
    filterFields: ["status"],
  }),
  1. Create a search query in a Convex function:
const results = await ctx.db
  .query("myTable")
  .withSearchIndex("search_body", (q) =>
    q.search("body", searchTerm).eq("status", "published")
  )
  .take(10);
  1. Run npx convex dev to deploy the new index

Limitations

  • Search indexes only work on string fields
  • One searchField per index (create multiple indexes for multiple fields)
  • Filter fields support equality only (not ranges or inequalities)
  • Results are ranked by relevance, not by date or other fields
  • Maximum 10 results per .take() call (paginate for more)

Resources