The ranking cascade
The eight tiers that decide result order, each one only breaking ties the tier before it left.
suo ranks results with a cascade, the same shape Algolia and Meilisearch use: eight tiers, evaluated in a fixed order, where each tier only decides between hits the tiers above it could not separate. A hit's full ranking vector is available on every result — this is what the playground's "why this rank" panel shows.
The eight tiers
- Pinned — a query-time pin rule places this exact record at a fixed position, above everything else.
- Words matched — how many of the query's words were found at all, regardless of typos.
- Typos — fewer typo corrections beats more; an exact match beats a corrected one.
- Proximity — how close together the matched words appear in the text.
- Attribute — which searchable tier the match came from, and where in it (a title match outranks a body match).
- Exactness — an ICU whole-word match outranks a prefix match, which outranks a bigram-only match (CJK recall floor — see CJK).
- Custom ranking — your own tie-breaking sort, for example by recency or popularity.
- bm25 — a standard relevance score, as the final tiebreaker.
Why a cascade instead of one score
A single blended score is hard to reason about: why did result B beat result A? A cascade answers that directly — "B matched one more word than A" is the whole explanation, at tier 2, and nothing below tier 2 needed to run. This is also why custom ranking sits near the bottom: it should break ties among equally relevant results, not override relevance itself.
Reading the response
{
"pinned": null,
"words": 2,
"typos": 0,
"proximity": 1,
"attribute": { "tier": 0, "position": 3, "name": "title" },
"exactness": 2,
"custom": [],
"bm25": 8.41
}Pass explain: true on a search to get this vector, plus decidedAgainstNextBy — which tier actually separated this hit from the next one — on every hit. See Search.
Typos are lazy
Typo expansion only runs when the exact and prefix candidates come up short — see Typo tolerance for the exact thresholds. This keeps a query that already has plenty of exact matches fast, and keeps a typo'd query from drowning out an exact one.