Home· Features· Custom Ranking
🎯 Custom Ranking

Three effects. Three triggers. Stacked cleanly.

Boost, pin, or hide — composed via _eval arrays, run through a relevance-bucketing tie-breaker cascade, with a separate prefix-match Tier Sort guaranteeing "starts-with-the-query" results win.

Starter Growth Scale Enterprise
Composed sort_by for "sneakers"
// Tier 1: prefix-pinned IDs (titles starting with "sneakers")
// Tier 2:
_eval([
  (in_stock:1):5,
  (brand:Anker):3
]):desc,
_text_match(buckets: 1):desc,
points:desc,
margin:desc
The model

Two layers, composable.

Skryx separates rules (per-query if-this-then-that) from custom-ranking attributes (always-on tiebreakers). Rules express editorial decisions; custom-ranking attributes express your catalog's business priorities. They compose without fighting each other.

Rules · per query

boost · pin · hide

Triggered by query patterns (exact / contains / starts-with) or by "every query". Stack on top of relevance — never replace it.

Custom ranking · always-on

Ordered field cascade

Sortable fields (numeric, bool, or string with sort:true) appended after a single relevance bucket. Cascade breaks ties left to right.

The three rule effects

Boost, pin, hide. No demote.

Penalising a product is just hiding it. The smaller surface keeps rules predictable and prevents the "two rules secretly cancelling each other" trap.

📈 boost

Add weight to anything matching a filter.

Multiple boosts compose into a single _eval([…]) array that prepends to sort_by. Each boost is (filter):weight; weights are positive integers. Matching documents get the weight summed into their rank score before the text-match tiebreaker.

  • Filter syntax: field:value, field:>n, &&, ||
  • Safety validator rejects field:(… && …) — a known Typesense footgun
  • Unsafe rules log a warning and are skipped, not crashing the query
{
  "name": "Boost noise-cancelling",
  "type": "boost",
  "conditions": { "query_contains": "headphones" },
  "effect": {
    "filter_by": "tags:noise_cancelling",
    "weight": 8
  },
  "priority": 10
}
📌 pin

Lock a specific document at a position.

A pin's effect carries document_id and position. Apply per query (exact / contains / starts-with) or as an always-on seasonal hold. Pinned hits push the rest of the result list down by one slot — they don't replace organic results, they prepend.

  • Per-rule priority orders multiple pins on the same query
  • Pinning is editorial: bypasses relevance entirely
  • Visual Curator (a separate module) adds A/B testing + scheduling on top
{
  "name": "Pin holiday hero",
  "type": "pin",
  "conditions": { "query": "sneakers" },
  "effect": {
    "document_id": "sku-air-jordan-1",
    "position": 1
  }
}
🚫 hide

Drop a document or anything matching a filter.

Two shapes: hide a single SKU by document_id, or hide a whole class via filter_by. Hides apply as engine-side filters so the document never appears in results, facet counts, or related-hits — even when semantic mode is on.

  • Per-query hide (e.g., hide a competitor brand on a sponsored query)
  • Always-on hide for OOS, region restrictions, age-gated SKUs
  • Toggle enabled on the rule — no redeploy, no engine restart
// Hide everything that's OOS
{
  "type": "hide",
  "effect": { "filter_by": "in_stock:false" }
}

// Hide one product
{
  "type": "hide",
  "effect": { "document_id": "sku-discontinued-42" }
}
Rule triggers

Three condition shapes. Or none — for always-on.

1

query

Exact, case-sensitive match. { "query": "sneakers" } fires only when the literal query is "sneakers". Useful for editorial decisions on canonical search terms.

2

query_contains

Substring match, case-insensitive. Value can be a string or an array. { "query_contains": ["headphone", "earbuds"] } fires when the query contains either token.

3

query_starts_with

Prefix match, case-insensitive. Useful for category-style boosts. { "query_starts_with": "iphone" } fires for "iphone", "iphone 15", "iphone case", etc.

Always-on rules

Empty conditions = every query.

The most common ranking rule is "keep in-stock first" — applied to every query. Just leave conditions empty (or omit). The rule fires regardless of what the customer typed.

  • Used by the six quick-start templates (in-stock boost, hide OOS, top-rated, …)
  • Combines with query-scoped rules: always-on first, then query-specific stacks on top
{
  "name": "In-stock first",
  "type": "boost",
  "conditions": null,    // always-on
  "effect": {
    "filter_by": "in_stock:1",
    "weight": 5
  }
}
Custom-ranking attributes

A tiebreaker cascade on sortable fields.

Separate from rules. You nominate an ordered list of fields and directions in relevance_config.custom_ranking. Skryx appends them to sort_by after a single-bucket text-match tier — so once relevance has spoken, your business priorities decide the order.

Bucketed relevance

Text match gets one tier. The rest is yours.

Skryx rewrites _text_match:desc as _text_match(buckets: 1):desc when custom ranking is active. All keyword-matched documents land in a single relevance tier, so your custom fields absolutely dominate ordering inside that tier — the Algolia-style "relevance, then custom ranking" cascade.

  • Only sortable fields qualify (numeric, bool, or strings with sort:true)
  • Cascade order respected: first tiebreaker decides, second only if first ties, etc.
  • Mix asc / desc per field
// Index → Search Settings
{
  "custom_ranking": [
    { "field": "in_stock", "direction": "desc" },
    { "field": "points",   "direction": "desc" },
    { "field": "margin",   "direction": "desc" }
  ]
}
Tier Sort prefix pinning

"Actually starts with what I typed" wins.

Before the main search, Skryx runs a cheap prefix-only side query on the title head token (≥ 3 chars, diacritics folded). Documents whose title literally starts with the query become Tier 1 pinned hits, guaranteed top positions. Extra cost: 3–8 ms. Effect: zero "but why is the obvious match on page 2?" complaints.

  • Pre-search side query, parallel-safe
  • Both tiers share your sort_by — no special-cased rules
  • Auto-disables when query is too short or contains operators
Query: "sneakers"
─────────────────
Tier 1 (prefix pinned)
1. Sneakers Air Jordan 1
2. Sneakers Nike Pegasus
Tier 2 (relevance + rules)
3. Adidas Stan Smith sneakers
4. Puma Suede sneakers
In the dashboard

Six quick-start templates + raw editor.

The Ranking Rules page ships with six one-click templates so first-day tenants don't stare at a blank form: in-stock first, hide out-of-stock, discounted boost, top-rated boost, freshness boost, price-required hide. Power users get a raw JSON editor for conditions and effect under the Filament admin view.

6
Quick-start templates · adapt to your schema (e.g., in_stock:1 vs in_stock:true)
Auto-Pilot
First-batch sync suggests rules with source = auto_pilot · priority 30
~100 ms
From "Apply" click in the dashboard to a live rule in the engine
Scoping

Per-index. Not per-tenant.

Every rule belongs to exactly one index. That's a deliberate constraint — two indexes with the same name in different products will have different ranking needs, and accidentally cross-applying a rule is a class of bug Skryx prevents at the schema level. If you need the same rule on two indexes, copy it explicitly (or use the copy-settings-from lifecycle endpoint).

Auto re-ranking · Pro+

Click signal becomes a ranking rule — with a self-revert safety net.

The Skryx engine watches what customers click on every search and detects (query, product) pairs that consistently win clicks but don't appear at the top. When a pair clears all safety gates, the engine creates a ranking rule automatically — and the circuit breaker monitors performance daily so a bad rule never sticks around.

🛡️ Four safety gates before auto-apply

Conservative by default.

A pair must clear EVERY one of these before the engine writes a rule:

  • Plan flagauto_rerank: true on tenant plan (Pro+ only)
  • Per-index opt-in — operator toggles "Auto-apply learned reranks" on the Ranking Rules page (default off)
  • Signal quality — min click volume + share-of-clicks > 30% + CTR > 0.20 + high confidence rating
  • Daily cap — max N rules per index per day (configurable per tenant, default 10) — prevents runaway storms

Pairs that already have a manual rule for the same (query, product) are skipped — the engine never fights the operator. A global kill switch in Filament admin can freeze the entire engine platform-wide for emergencies, no redeploy required.

// Auto-applied ranking rule
{ "id": 4218,
  "source": "auto_rerank",
  "index_id": 36,
  "name": "Auto rerank: 'supape' → BK52310",
  "type": "boost",
  "conditions": {
    "query_contains": ["supape"]
  },
  "effect": {
    "filter_by": "id:=BK52310",
    "weight": 7
  },
  "priority": 60,
  "auto_applied_at": "2026-06-02T04:15Z",
  "baseline_ctr": 0.180 }
📉 Circuit breaker — daily CTR check

If a rule hurts, the engine takes it back.

RerankCircuitBreakerJob runs every day at 04:45. For each auto-applied rule past the 3-day observation window, it compares the query's current CTR to the baseline snapshotted at apply time. If the drop is > 10%, the rule auto-reverts — disabled, timestamped with reverted_at, and a Coach notification fires with the reason so the operator can review.

Rules that survive observation get marked validated. They keep earning value silently. Long-running validated rules get a periodic re-check so vocabulary drift can't quietly degrade them either.

  • 3-day observation window per auto-applied rule
  • 10% CTR drop threshold — configurable per tenant
  • Coach notification on revert — operator stays in the loop
  • Validated rules get re-checked every 30 days against fresh data
// 3 days later — observation closes

// Healthy case: CTR held
{ "state": "validated",
  "baseline_ctr": 0.180,
  "current_ctr":  0.214,
  "delta_pct":   "+18.9%",
  "validated_at": "2026-06-05T04:45Z" }

// Unhealthy case: auto-revert
{ "state": "reverted",
  "baseline_ctr": 0.180,
  "current_ctr":  0.146,
  "delta_pct":    "-18.9%",
  "reverted_reason": "CTR dropped 18.9%",
  "enabled": false }
Keep exploring

Other things Skryx does

Try it on your own catalog.

Free tier, no credit card. EU-hosted from day one.