Skip to content
SearchJet
Headless CMS Integration

Search for Decoupled Architectures

Strapi, Sanity & Contentful Search in Minutes

Real-time indexing for headless CMS. Search your content instantly from any frontend, with zero database load and AI-powered relevance.

The Problem

Headless CMS Search is Broken

Decoupled architectures give you flexibility but leave search as an afterthought. Building reliable, fast search across multiple frontends is harder than it needs to be.

⚠

Decoupled Architectures Break Search

Headless CMSs separate content from presentation. Searching across multiple frontend frameworks, databases, and services requires custom solutions that are complex and fragile.

⚠

Manual Indexing & Sync Issues

Keeping search indexes synchronized with your CMS is a nightmare. Webhooks fail, cron jobs miss updates, and you're left with stale search results and frustrated users.

⚠

Search Performance Ignored

Most developers focus on API performance and overlook search UX. Default search is slow, inflexible, and doesn't handle typos or synonyms — driving users away.

The Solution

SearchJet for Headless CMS

Real-time indexing. Multiple frontends. One powerful search API. No complexity.

Simple Integration

Connect Strapi, Sanity, or Contentful with a lifecycle hook or webhook and a short endpoint of your own that calls the REST API. No package to install, no search servers to run.

Multi-Frontend Support

Whether you serve React frontends, Vue apps, or static Jamstack sites — the same search API powers them all. No framework coupling.

Instant Indexing

Webhooks fire the moment content publishes. New articles, product updates, and changes appear in search within seconds.

Advanced Filtering

Filter by content type, tags, locale, status, or custom fields. Build faceted search without writing backend logic (filter attributes must be marked filterable).

Typo Tolerance & Semantics

Users type "strapi serch" and still find "Strapi search guide". AI-powered ranking finds what users mean, not just what they type.

Zero Database Load

Search runs externally. Your CMS database stays focused on reads and writes, not expensive search queries.

Platform Support

Integrations for Every Major Headless CMS

Strapi

Add a Strapi lifecycle hook (your own code, no plugin needed) that sends each created or updated entry to the bulk indexing endpoint.

// src/api/article/content-types/article/lifecycles.js
module.exports = {
  async afterCreate(event) { await indexArticle(event.result); },
  async afterUpdate(event) { await indexArticle(event.result); },
};

async function indexArticle(article) {
  await fetch('https://app.searchjetengine.com/api/v1/index/bulk', {
    method: 'POST',
    headers: {
      // Private key: server-side env var only
      'Authorization': 'Bearer ' + process.env.SEARCHJET_PRIVATE_KEY,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      documents: [{
        id: 'article-' + article.id,
        title: article.title,
        content: article.body,
        url: 'https://example.com/blog/' + article.slug,
        type: 'article',
        date: article.publishedAt,
      }],
    }),
  });
}

Sanity

Create a Sanity webhook that calls your own endpoint. The endpoint maps the document and sends it to SearchJet (see the route handler below).

// Sanity webhook settings (sanity.io/manage → API → Webhooks)
{
  "name": "searchjet-sync",
  "url": "https://your-site.com/api/searchjet-sync",
  "trigger on": ["create", "update"],
  "filter": "_type in ['post', 'product', 'page']",
  "projection": "{ _id, _type, _updatedAt, title, 'slug': slug.current, 'body': pt::text(body) }",
  "headers": { "x-webhook-secret": "YOUR_OWN_SECRET" }
}

Contentful

Point a Contentful webhook at your own endpoint (for example a Next.js route handler or serverless function) that indexes the published entry.

// Contentful webhook (Settings → Webhooks)
{
  "name": "SearchJet Indexing",
  "url": "https://your-site.com/api/contentful-sync",
  "topics": [
    "Entry.publish"
  ],
  "headers": [
    { "key": "x-webhook-secret", "value": "YOUR_OWN_SECRET" }
  ]
}
Setup Examples

Configuration Across Platforms

Strapi Setup

components/BlogSearch.jsx
// Frontend search over your Strapi content (React)
import { useState } from 'react';

// Use a SEARCH key in the browser, never the private key
const SEARCH_KEY = process.env.NEXT_PUBLIC_SEARCHJET_SEARCH_KEY;

export default function BlogSearch() {
  const [results, setResults] = useState([]);

  async function search(q) {
    if (!q) return setResults([]);
    const params = new URLSearchParams({
      q,
      limit: '10',
      // "type" must be marked filterable in your index settings
      filters: 'type = "article"'
    });
    const res = await fetch(
      'https://app.searchjetengine.com/api/v1/search?' + params,
      { headers: { Authorization: 'Bearer ' + SEARCH_KEY } }
    );
    const data = await res.json();
    setResults(data.results); // results is the array of hits
  }

  return (
    <div>
      <input type="search" placeholder="Search the blog..."
        onChange={e => search(e.target.value)} />
      <div className="results">
        {results.map(post => (
          <article key={post.id}>
            <h3><a href={post.url}>{post.title}</a></h3>
            <p>{post.excerpt}</p>
            <small>{post.date}</small>
          </article>
        ))}
      </div>
    </div>
  );
}

Sanity Setup

app/api/searchjet-sync/route.js
// app/api/searchjet-sync/route.js
// Your own endpoint (Next.js), called by the Sanity webhook
export async function POST(req) {
  if (req.headers.get('x-webhook-secret') !== process.env.WEBHOOK_SECRET) {
    return new Response('Unauthorized', { status: 401 });
  }
  const doc = await req.json(); // shaped by the webhook projection

  const res = await fetch('https://app.searchjetengine.com/api/v1/index/bulk', {
    method: 'POST',
    headers: {
      'Authorization': 'Bearer ' + process.env.SEARCHJET_PRIVATE_KEY,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      documents: [{
        id: doc._id,
        title: doc.title,
        content: doc.body,
        url: 'https://example.com/' + doc.slug,
        type: doc._type,
        date: doc._updatedAt
      }]
    })
  });
  return Response.json(await res.json()); // includes taskUid
}

Contentful Setup

contentful-mapping.js
// Map a Contentful "Entry.publish" webhook payload
// inside your own endpoint (same pattern as the Sanity route)
function toSearchJetDocument(entry, locale = 'en-US') {
  const f = entry.fields;
  return {
    id: entry.sys.id,
    title: f.title?.[locale],
    content: f.body?.[locale], // assumes a Long text field
    excerpt: f.excerpt?.[locale],
    url: 'https://example.com/blog/' + f.slug?.[locale],
    type: entry.sys.contentType.sys.id,
    date: entry.sys.updatedAt,
    metadata: { tags: f.tags?.[locale] || [] }
  };
}

// Then POST { documents: [toSearchJetDocument(entry)] }
// to https://app.searchjetengine.com/api/v1/index/bulk
// with Authorization: Bearer <private key from an env var>
Illustrative Example

Before and After

Illustrative example of a headless CMS moving search to SearchJet. Your results will vary.

MetricBeforeAfterImpact
Content Indexing TimeBatch or cron syncsSeconds after publishMuch faster
Search Query SpeedSlower CMS API queries<150msNoticeably faster
API PerformanceBaselineSearch off your CMS APIMore headroom
Infrastructure CostsSearch servers neededIncludedNo search servers to run

Frequently Asked Questions

Everything you need to know about SearchJet for headless CMS

How does SearchJet sync with my CMS?
Your CMS fires a webhook (or a lifecycle hook) when you publish or update content. It calls a small endpoint you control, which maps the entry and sends it to the SearchJet bulk indexing API with your private key. We index it instantly — typically within 100ms. No polling, no cron jobs, no manual triggers.
Which headless CMS platforms do you support?
Strapi, Sanity, and Contentful all work with the setup shown on this page. Any CMS with webhooks or an API can integrate the same way through our REST API. Reach out if you use a different platform.
How do I handle multiple frontends?
SearchJet is platform-agnostic. The same search API works with React, Vue, Svelte, static HTML, or mobile apps. Just use your search API key in each frontend; the key determines which index is searched.
Can I search across content types?
Yes. Index all your content types together with a type field, then filter by type, locale, status, or custom fields at query time. Attributes you filter on must be marked filterable in your index settings.
What happens during CMS maintenance windows?
If your CMS is down, webhooks won't fire, but you can still search. Existing indexes remain live. When your CMS comes back online, re-send any entries that changed in the meantime, for example with a one-off script that posts them to the bulk indexing endpoint in upsert mode.
Is there a free plan for headless CMS search?
Yes. Our free forever plan includes up to 100K documents and 100K queries/month. Perfect for development and small to medium projects.

Ready to Add Search to Your Headless CMS?

Get started free. No credit card required. Real-time indexing for Strapi, Sanity, and Contentful.

Questions? Contact our team