Search8 min read

How to Integrate Typesense with Your Headless CMS

Connect Typesense to your headless CMS so published content becomes typo-tolerant, faceted search results in seconds instead of waiting for manual reindexing.

Published April 29, 2026
01Overview

What is Typesense?

Typesense is an open source search engine built for fast, typo-tolerant search, faceting, filtering, sorting, and geosearch. Teams use it for product catalogs, documentation sites, media libraries, marketplaces, and internal knowledge bases that need low-latency search without running a large search operations team. It sits in the same search category as Algolia, Elasticsearch, and Meilisearch, with hosted Typesense Cloud and self-hosted deployment options.


02The case for integration

Why integrate Typesense with a headless CMS?

Search gets messy when your editing system and search index live separate lives. An editor fixes a product title, adds a category, or unpublishes an article, but the Typesense index still has the old version. The result is familiar: users search for a term that should work, filters show stale values, and someone eventually runs a manual reindex script.,Connecting Typesense to a headless CMS solves that by turning content changes into index updates. When content is published, updated, or deleted, a webhook or server-side function can fetch the exact document shape Typesense needs, map it to a Typesense collection, and call the Typesense Documents API. For a product catalog, that might mean indexing title, slug, price, availability, category names, brand, image URL, and a denormalized popularity score.,With Sanity, structured content in the Content Lake makes this cleaner because Typesense gets typed JSON instead of scraped HTML or page blobs. GROQ selects only the fields needed for the search record, including joined reference data such as author names or category paths. Webhooks can fire on publish events, and Functions can run sync logic without a separate worker, cron job, or queue service. The trade-off is that you still need to design the Typesense collection schema carefully. Bad field choices create noisy results, even when the sync pipeline works perfectly.


03Architecture

Architecture overview

A typical setup starts in Sanity Studio, where editors publish structured content such as articles, products, docs pages, or locations. That content is written to the Content Lake as typed JSON. A Sanity webhook listens for create, update, publish, and delete mutations, often with a GROQ-powered filter such as _type in ["product", "article"] so search sync only runs for indexable content. The webhook calls a Sanity Function or your own HTTPS endpoint with the changed document ID and mutation type. The function uses @sanity/client and GROQ to fetch the full search record, including referenced data like categories, authors, tags, or related brand details. It then maps that result into a Typesense document and calls the Typesense API, usually client.collections("products").documents().upsert(document) for creates and updates, or client.collections("products").documents(id).delete() for deletes. On the frontend, the browser or server uses a Typesense search-only API key to query the collection with q, query_by, filter_by, sort_by, facet_by, and per_page. The user sees fresh search results because the index update happens when content changes, not when someone remembers to run a batch job.


04Use cases

Common use cases

🛒

Product catalog search

Index product names, SKUs, brands, categories, prices, and availability from Sanity into Typesense for typo-tolerant ecommerce search with filters.

📚

Documentation search

Sync docs pages, headings, excerpts, product versions, and tags so users can search technical content by keyword, version, and topic.

🏠

Marketplace and location search

Send listings, coordinates, amenities, regions, and status fields to Typesense for faceted search, sorting, and geosearch.

📰

Editorial archive search

Index articles, authors, topics, publish dates, and summaries so readers can find old stories without searching through static pages.


05Implementation

Step-by-step integration

  1. 1

    Set up Typesense

    Create a Typesense Cloud cluster or run Typesense yourself with Docker. Create an admin API key for server-side indexing, a search-only API key for frontend queries, and a collection such as products or articles with fields like title, slug, description, categories, price, and publishedAt. Install the SDK with npm install typesense.

  2. 2

    Model searchable content in Sanity Studio

    Define schema fields that map cleanly to search records. For a product, use fields such as title, slug, sku, description, price, availability, brand, categories, and mainImage. Keep search filters as structured fields, not text buried inside Portable Text, because Typesense faceting works best with clean strings, numbers, booleans, and string arrays.

  3. 3

    Write the GROQ query

    Create a GROQ projection that returns exactly what Typesense should index. Join references inline, for example categories[]->title and brand->name, and convert rich text to a plain-text summary before indexing if your frontend needs body search.

  4. 4

    Create the sync endpoint

    Use a Sanity Function, webhook listener, or middleware endpoint to receive mutation events. The endpoint should validate the request, read the changed document ID, fetch the current document from the Content Lake, and decide whether to upsert or delete the Typesense record.

  5. 5

    Push records to Typesense

    Use the Typesense SDK to call documents().upsert() for created or updated content and documents(id).delete() for removed content. Keep the admin API key on the server only. If you expect high publish volume, batch documents with import() and track failed rows.

  6. 6

    Test search in the frontend

    Build a search UI that queries Typesense with a search-only API key. Test common misspellings, empty queries, filters, facets, sorting, and unpublished content. Confirm that a Sanity publish event appears in Typesense within your expected window, often a few seconds for small records.


06Code

Code example

typescriptapi/sync-typesense.ts
import {createClient} from '@sanity/client'
import Typesense from 'typesense'

const sanity = createClient({
  projectId: process.env.SANITY_PROJECT_ID!,
  dataset: process.env.SANITY_DATASET!,
  apiVersion: '2025-01-01',
  token: process.env.SANITY_READ_TOKEN!,
  useCdn: false,
})

const typesense = new Typesense.Client({
  nodes: [{
    host: process.env.TYPESENSE_HOST!,
    port: 443,
    protocol: 'https',
  }],
  apiKey: process.env.TYPESENSE_ADMIN_KEY!,
  connectionTimeoutSeconds: 5,
})

export default async function handler(req: any, res: any) {
  const {_id, operation} = req.body
  const id = _id?.replace(/^drafts\./, '')

  if (operation === 'delete') {
    await typesense.collections('products').documents(id).delete()
    return res.status(200).json({ok: true, deleted: id})
  }

  const product = await sanity.fetch(`
    *[_id == $id][0]{
      _id,
      title,
      "slug": slug.current,
      sku,
      price,
      "brand": brand->name,
      "categories": categories[]->title,
      "imageUrl": mainImage.asset->url
    }
  `, {id})

  if (!product?.slug) return res.status(202).json({skipped: id})

  await typesense.collections('products').documents().upsert({
    id: product._id,
    title: product.title,
    slug: product.slug,
    sku: product.sku || '',
    price: product.price || 0,
    brand: product.brand || '',
    categories: product.categories || [],
    imageUrl: product.imageUrl || '',
  })

  res.status(200).json({ok: true, indexed: product._id})
}

07Why Sanity

How Sanity + Typesense works

Build your Typesense integration on Sanity

Sanity gives you the structured content foundation, real-time event system, and flexible APIs you need to keep Typesense search indexes current.

Start building free →

08Comparison

CMS approaches to Typesense

CapabilityTraditional CMSSanity
Structured data for indexingSearch records often start as rendered pages or mixed content fields, so teams write parsers before indexing.The Content Lake returns typed JSON, and GROQ can shape a Typesense-ready document in one query.
Sync timing after publishIndex updates often depend on scheduled crawls, plugins, or manual reindex buttons.Webhooks trigger on content events, and Functions can run the Typesense upsert or delete logic close to the content change.
Field-level query controlSearch indexing may include too much page content, which can create noisy matches.GROQ selects only the fields Typesense needs, including arrays, references, projections, and derived values.
Facet and filter readinessFacets such as category, region, and availability can be trapped inside text or plugin-specific fields.Schema-as-code in Sanity Studio helps define clean fields for Typesense facets, such as string arrays, booleans, numbers, and dates.
Handling deletes and unpublished contentDeleted pages can stay searchable until the next crawl removes them.Publish and delete events can map directly to Typesense upserts and deletes, with GROQ filters to exclude drafts from the index.
Multi-channel reuseSearch indexing is often tied to website pages, which can make mobile, kiosk, and agent search separate projects.One structured back end can feed web, mobile, Typesense, and AI agents through APIs built for structured content.

09Next steps

Keep building

Explore related integrations to complete your content stack.

Ready to try Sanity?

See how Sanity's Content Operating System powers integrations with Typesense and 200+ other tools.