×marble
all posts
Jun 18, 2026·14 min read

Meilisearch vs Typesense for Personalization: How to Choose in 2026

Both engines deliver fast, typo-tolerant search out of the box. When you need to inject user signals into the ranking pipeline, the differences that don't appear in benchmarks become the ones that control your ceiling. Here's the technical breakdown.

Alex Shrestha·Founder, ×marble

Meilisearch vs Typesense for Personalization: How to Choose in 2026

TL;DR.

  • Meilisearch's composable rankingRules pipeline and hybrid vector search make it easier to inject user preference embeddings at query time; Typesense's override rules and pinned_hits give more surgical per-query editorial control.
  • Neither engine stores user preferences — that layer lives outside both, in your knowledge graph, CDP, or feature store.
  • Typesense's scoped API keys with embeddable filter_by and pinned_hits are more mature for multi-tenant personalization in SaaS contexts; Meilisearch's tenant tokens are simpler but narrower.
  • By 2026, both engines ship HNSW-backed hybrid search (BM25 + ANN), collapsing the semantic recall gap that used to require Elasticsearch at 10× the ops cost.
  • If you only remember one thing: the personalization ceiling in both engines is the quality of signals you push in at query time — not the engine itself.

The standard benchmark for Meilisearch and Typesense compares indexing throughput, typo correction accuracy, and p99 query latency on shared datasets. These numbers matter when you're building a product search bar or a docs site. They tell you almost nothing about how the engine behaves under a personalization workload — where the ranking pipeline needs to incorporate user-specific signals, where cold-start users exist, and where the correct result for user A is the wrong result for user B on an identical query string.

This post is for teams that have already decided on an open-source search engine and are now asking which one gives more leverage when they want search results to reflect who is searching, not just what is being typed. The answer depends on which ranking seams you can reach from outside the engine.

What Personalization Actually Requires From a Search Engine

A search engine needs two things to support personalization: ranking seams you can reach from outside at query time, and signal injection that doesn't require a full re-index on every user state change.

Most search engines were designed around a single ranking objective: relevance to the query. Personalization requires a second objective layered on top — relevance to this user. The challenge is that "this user" is dynamic: it changes as the session progresses, it's absent for new users (cold-start), and it's different across 10 million concurrent sessions you cannot pre-materialize.

The naive approach is to store per-user scores as document fields and sort by them. This breaks at scale: O(users × documents) storage, score staleness, and re-index cost every time signals update. The scalable approach is signal injection at query time — pass a user preference vector, a user segment filter, or a per-user sort key that gets evaluated against a compact document representation. Both Meilisearch and Typesense expose surfaces for this, but the surfaces are different in meaningful ways.

Four injection points matter:

Vector injection. At query time, pass a user preference embedding. The engine finds documents whose vectors are nearest-neighbors of the user vector. This is the highest-leverage primitive for semantic content matching without per-user index state.

Filter injection. Scope results to user-allowed or user-preferred taxonomies. Both engines support this natively and equivalently via filter_by / filter query parameters.

Sort injection. Override or augment the default ranking with a user-context numeric field. Requires the field to exist on documents at index time — limits the signal cardinality you can express.

Override/pin injection. Promote or suppress specific document IDs per query. Requires external precomputation of which IDs to promote, but gives the most precise per-user result shaping either engine can produce.

Meilisearch's Personalization Surface

Meilisearch's core ranking is a deterministic, ordered pipeline: words → typo → proximity → attribute → sort → exactness. The first rule that distinguishes two documents wins. You can reorder this pipeline at index configuration time — moving sort earlier means a custom numeric score overrides proximity signals, which matters for preference-ranked content feeds.

The practical personalization mechanism in 2026 is hybrid search. Since v1.7, Meilisearch ships an embedded vector store (HNSW via arroy, written in Rust) with a hybrid query mode that combines BM25 and ANN with a configurable semanticRatio. At query time you pass:

{
  "q": "comfortable running shoes",
  "vector": [0.12, -0.34, 0.87, ...],
  "hybrid": {
    "embedder": "user-preference",
    "semanticRatio": 0.7
  }
}

The vector here is a user preference embedding generated outside the engine — from your knowledge graph, collaborative filter, or session encoder. Meilisearch scores each document against both the BM25 query and the user vector, blends them by semanticRatio, and returns a single ranked list. This is query-time personalization with no per-user index state.

The weakness: semanticRatio is set in the embedder configuration at the index level. You can define multiple named embedders and select one per query, which gives you discrete blend modes (e.g., user-preference vs content-only), but you cannot pass a continuously varying confidence weight per request the way you might want to for users transitioning from cold to warm state. You work around this by adjusting the vector itself: for cold users, pass a synthetic cluster centroid; for warm users, pass their actual preference embedding. The blending is coarse but the pattern works.

Meilisearch's tenant tokens are JWTs signed with your admin key that embed filter conditions and searchable attribute restrictions per user. A token for user A might encode filter: "content_rating <= 3 AND language = 'en'". The client uses the token instead of the admin key; Meilisearch enforces the constraints server-side without exposing admin credentials. This is clean for access control and content scoping. It is not a substitute for preference-based ranking — filter scope ≠ preference order.

One important limitation: Meilisearch's sortableAttributes requires the sort field to exist on the document at index time. You cannot compute a dynamic user-specific score at query time and inject it into the sort rule unless you pre-materialize it per-user per-document. At any meaningful user scale, this breaks. The hybrid vector path is the designed escape hatch, and it works well precisely because it bypasses the sort pipeline entirely.

Typesense's Personalization Surface

Typesense uses a weighted scoring model where multiple signals contribute to a final document score via configurable query_by_weights. Sorting is expressed via sort_by with up to three fields, each optionally weighted, specified per query. This is more flexible than Meilisearch's pipeline for cases where you have multiple numeric signals you want to blend — but it still requires those fields to exist on documents.

For personalization, Typesense's highest-leverage primitive is pinned_hits and hidden_hits. These are query-time parameters that promote or suppress specific document IDs regardless of their natural ranking score. This looks like a weak editorial tool until you realize it's actually the most precise personalization surface either engine exposes — if you can precompute which documents to pin for a given user+query combination.

The workflow:

  1. User submits query q = "javascript tutorials".
  2. Your personalization service looks up user's skill level, recent views, preferred format.
  3. Returns pinned_hits: ["doc_intermediate_async", "doc_video_closures"] and hidden_hits: ["doc_beginner_hello_world"].
  4. Typesense executes the search with these constraints embedded.

This is precise but expensive: it requires a real-time call to your personalization service on every search request, concurrent with or before the Typesense call. The latency budget gets tight. The pattern works at p50 < 30ms only if your personalization service is co-located and precomputed — not on-the-fly inference.

Typesense's override rules (curation rules) let you define query-matching conditions that trigger automated pinning, filtering, or result replacement, persisted in Typesense configuration rather than passed per query. You can create a rule: "for any query containing 'premium', add filter_by: tier:premium." Override rules are global, not per-user. Per-user behavior still requires query-time pinned_hits injection or scoped key embedding.

Scoped API keys in Typesense embed filter_by, query_by, and — critically — pinned_hits and hidden_hits directly into the key material. This is the richer multi-tenant personalization primitive compared to Meilisearch's tenant tokens. A scoped key for user A can pre-embed both access filters and known preference boosts, generated server-side at session start. The client-side search call carries no user-specific logic; the personalization is in the key. For SaaS architectures where the front-end should never see user-context logic, this is meaningful.

Typesense's hybrid search uses vector_query with configurable distance metrics (cosine, dot product, Euclidean) and an alpha blend weight passed per query:

{
  "q": "javascript tutorials",
  "vector_query": "preference_embedding:([0.12, -0.34, 0.87, ...], alpha:0.7)"
}

The per-query alpha is more flexible than Meilisearch's embedder-level semanticRatio for dynamic confidence-weighted blending — you can vary the keyword-vs-semantic balance per request based on signal confidence without changing index configuration.

Cold-Start: Neither Engine Solves It, Here's What You Do

Cold-start personalization is the gap when a new user arrives with no behavioral history. A query from user A (1,000 sessions, known preferences) and user B (first session, no history) both hit the search engine identically — as a query string. Without something in the vector injection, both users get the same result.

The standard fallback is global popularity signals: sort by view count, recency, or editorial rank. Both engines support this via numeric sort fields. It produces acceptable results but doesn't learn.

The better approach is synthetic user clones. At first session — or even pre-session via onboarding signals — assign the user to a cluster centroid derived from users with similar observable features: device type, locale, referrer, explicit onboarding choices. That centroid becomes the user's initial preference vector. You inject it on the first query. As the session accumulates behavioral signals, the vector shifts toward the individual. You never have a zero-signal moment; you have a low-confidence moment that improves continuously.

Neither Meilisearch nor Typesense handles this natively. Cluster centroid generation, vector interpolation, and signal accumulation happen in your external personalization layer. Both engines accept the resulting vector at query time without caring about its provenance.

What the engine choice does affect: whether you can express a confidence-weighted blend between the cluster centroid (broad, low-confidence) and the individual signal (specific, high-confidence). Typesense's per-query alpha is more flexible here — you can pass alpha: 0.3 for a cold user (lean on keyword relevance, use preference vector lightly) and alpha: 0.8 for a warm user (preference vector dominates), all without touching index configuration. Meilisearch requires multiple named embedders with fixed semantic ratios and query-time embedder selection to approximate this.

Real-Time Signal Ingestion and Index Freshness

Personalization degrades if signals are stale. A user who clicked three articles about Kubernetes 20 minutes ago should get Kubernetes-adjacent results now, not results calibrated from last week's session summary.

Both engines support partial document updates, but at different cost. Meilisearch re-indexes the entire document when any field changes — there is no field-level partial update. For high-signal-velocity fields (per-document view counts, freshness scores), this means re-indexing cost scales with signal update frequency. Typesense supports true partial document updates via PATCH /collections/{name}/documents/{id}, updating only changed fields without full re-indexing. For real-time document-level signal injection, Typesense has a measurable edge.

That said, document-level signal injection (updating per-user scores on documents) is the pattern you abandon first as you scale. The scalable replacement is query-time vector injection — user state lives in your feature store, not in document fields. At that point, index freshness is about content freshness (new documents appearing quickly), and both engines handle indexing throughput comparably for most workloads.

The more relevant concern at scale is multi-node write consistency. Typesense's clustering uses Raft consensus and provides strong write consistency with optionally eventual-consistent reads from any replica. Meilisearch's distributed/multi-search setup is newer and less battle-tested for write-heavy personalization workloads in 2026. If your architecture involves high-velocity real-time document mutations (clickstream → document update → next-query effect), Typesense is the more reliable choice operationally.

Learning-to-Rank and the External Re-Ranker Pattern

Neither Meilisearch nor Typesense ships a learning-to-rank system (Learning to rank, Wikipedia). For production personalized search — where a trained model scores results using features like user affinity, item popularity, and query-item co-occurrence — you need an external re-ranker regardless of which engine you choose.

The standard two-stage pattern:

  1. First-stage retrieval from Meilisearch/Typesense (BM25 + optional ANN) — returns top-K candidates, typically 50–200.
  2. Feature extraction for each candidate: user affinity score from your knowledge graph, item popularity, recency, diversity penalty.
  3. Re-ranking model (gradient boosted trees, cross-encoder, or lightweight neural ranker) scores each candidate using the full feature set.
  4. Final ordered list returned to user.

Both engines serve as first-stage retrieval equally well. After stage 1, the engine choice becomes irrelevant. The personalization depth available via a trained re-ranker exceeds anything either engine's native ranking can express — because the re-ranker consumes arbitrary per-user features from your external feature store, not just the signals you can embed in a query-time vector.

HNSW-based approximate nearest-neighbor search — the underlying mechanism for vector retrieval in both engines — was characterized in the foundational Malkov & Yashunin paper (arXiv:1603.09320). Both Meilisearch's arroy and Typesense's hnswlib implement this algorithm. At similar collection sizes and index settings, retrieval quality for preference vector lookups is equivalent. The differentiation is in what wraps the retrieval call, not the retrieval itself.

The re-ranker pattern adds 5–15ms for a shallow model inference pass, but it gives you a personalization quality ceiling that query-time vector injection alone cannot reach. Build it once you have enough click data to train on — typically a few weeks of production traffic.

Operational Differences That Shape Architecture

Multi-tenancy. Typesense's scoped API keys with embedded filter_by and pinned_hits are more expressive than Meilisearch's tenant tokens. For SaaS products where each customer's users need different personalization scopes, Typesense's key-material approach lets you generate a key at login time that encodes the user's full personalization context. The front-end uses that key directly; no server-side search proxy needed per request.

Search analytics and feedback loop. Personalization requires a feedback loop — click signals need to flow back into the preference model. Typesense ships a built-in analytics API that records query analytics and supports A/B testing of ranking configurations natively. Meilisearch's analytics are available in Meilisearch Cloud but require external instrumentation on the self-hosted path. If you're building the feedback loop (clicks → signal store → preference model → query injection), Typesense reduces the integration surface.

Embedder flexibility. Meilisearch's embedder configuration supports OpenAI, HuggingFace, and custom REST endpoints at the index level, and can auto-embed documents server-side at indexing time. Typesense expects pre-computed vectors stored in an embed field; embedding is done externally before document ingestion. For a personalization architecture where document vectors are content-derived and user vectors are preference-derived, this means: Meilisearch can remove the offline embedding pipeline for documents (useful for small teams), but you still pass user preference vectors at query time in both cases. At production scale, you'll have an offline embedding pipeline regardless.

Binary footprint. Meilisearch's Rust-based binary has a smaller memory footprint for small-to-medium collections and works well on edge hardware or resource-constrained environments. If you're shipping personalized search inside a mobile app SDK or an embedded device context, Meilisearch has the operational advantage.

Which One Should You Choose?

Choose Typesense if:

  • You need mature multi-tenant scoped API keys with embedded personalization context for a SaaS or marketplace product.
  • You want per-query alpha tuning for dynamic confidence-weighted hybrid search without embedder configuration changes.
  • You're building a feedback loop and want native analytics hooks without additional instrumentation.
  • You need real-time partial document updates at high velocity.
  • You require battle-tested clustered deployments with strong write consistency for production personalization workloads.

Choose Meilisearch if:

  • Your primary personalization mechanism is preference vector injection and you want the simplest possible hybrid search API.
  • You want server-side auto-embedding of documents using a configurable embedder (OpenAI, HuggingFace, REST) without a separate offline pipeline.
  • You're deploying on edge or embedded hardware where binary footprint matters.
  • You prefer a simpler ranking rule configuration over Typesense's more complex override rule system.
  • You're already on Meilisearch Cloud and want managed analytics and hosted embedder integrations.

Both choices converge once you add an external knowledge graph and re-ranking layer. At that point both engines are first-stage retrieval substrates, and the decision is driven by ops preference and the specific injection surface that fits your architecture — scoped keys vs tenant tokens, per-query alpha vs multi-embedder selection, native analytics vs external instrumentation.

Where to Start

1. Define your signal injection pattern first.

Before deploying either engine, decide: will your primary personalization mechanism be vector injection (user preference embedding), filter injection (user segment scoping), or pin injection (precomputed result lists)? Vector injection works with both engines via hybrid search. Pin injection favors Typesense's pinned_hits and scoped key architecture. Filter injection is equivalent in both. The injection pattern determines which engine's seams you actually need.

2. Ship BM25 first, instrument everything.

Deploy either engine with keyword-only search. Add click tracking, query logging, and session-level engagement signals from day one. These signals are the raw material for your preference model. Without them, the vector injection step has nothing to inject. The instrumentation layer — not the search engine — is the critical path.

3. Build the preference layer externally.

Your knowledge graph or feature store owns user preference state. Both engines are consumers of that state, not owners of it. Build the preference layer so it can emit a preference vector or a result ID list on any query, within your latency budget. The engine integration is approximately 20 lines of code once this layer exists. The preference layer is where the engineering investment belongs.

4. Add synthetic clone cold-start.

Cluster your users by observable first-session features — device, locale, referrer, explicit onboarding inputs. Store cluster centroids as queryable vectors. When a new user arrives, assign them to the nearest cluster, inject the centroid as their initial preference vector, and shift toward individual signals as the session progresses. Neither engine makes this harder or easier; it lives entirely in your preference layer.

5. Measure before you tune the engine.

Re-ranking quality is almost always dominated by signal quality, not engine parameters. Before tuning semanticRatio or alpha, measure whether your preference embeddings are actually correlated with engagement on held-out sessions. If the embeddings are not predictive, no engine configuration will fix it. Personalization is a data problem first.

Both Meilisearch and Typesense are capable substrates for personalized search in 2026. The personalization ceiling on either is bounded by the richness of your external signal layer and the quality of your preference model. The engine choice is an architectural seam selection — which surfaces you want to reach from outside, and at what operational cost.

the product behind these notes

×marble is the personalization graph.

One API. A living knowledge graph per user. Day-zero ready, explainable by construction. We built it so you don't have to.

See ×marble