Skip to content
Merchandising · 2026-04-22 · 7 min read

Merchandising without the ticket queue.

"Pin this SKU during the weekend sale." "Boost our exclusive brand above the private-label stuff." "Hide the discontinued line from search." These are merchandising decisions. They shouldn't require a pull request.

Three rule types, one engine

Trooply's merchandising layer runs on every search — image, text, voice, fusion, widget. It's a stack of rules with three possible effects:

  • Pin. Force one or more products to the top of the results in a specific order, ignoring their CLIP score. Use this to guarantee a SKU ships above organic hits for a named query.
  • Boost. Multiply a product's final score by boost_multiplier (0.01–5.0). Soft promotion. A 1.5× boost gives a product a nudge; a 0.5× boost de-emphasises without hiding.
  • Bury. Remove listed products from the response entirely. The shopper never sees them for the matching query.

Scope: when does a rule fire?

Every rule has a scope_type. Four options, each covering a distinct merchandising intent:

scope_typeFires when…Use case
alwaysEvery search"Our featured brand stays 1.3× boosted on every query."
query_exactShopper query equals scope_value (case-insensitive)"When someone searches 'sale', pin these three SKUs."
query_containsShopper query contains scope_value as a substring"Anything with 'sneaker' in it? Boost the new drop."
category_matchThe result's metadata.category equals scope_value"Bury two discontinued SKUs from the Footwear category."

A pin rule end to end

The API call is stable across rule types — only the fields that apply differ.

POST /v1/merchandising/rules
{
  "rule_type": "pin",
  "scope_type": "query_exact",
  "scope_value": "sale",
  "product_ids": ["SKU-HERO", "SKU-FEATURED-2"],
  "priority": 5,
  "start_at": "2026-04-25T00:00:00Z",
  "end_at":   "2026-04-28T00:00:00Z",
  "description": "Spring-sale campaign pins"
}

Three things to notice:

  • Order matters for pins. The first product_id lands at position #1, the second at #2, and so on. For boost and bury, the list is a set — order is ignored.
  • The time window is optional. If you fill it in, the rule is a no-op before start_at and after end_at. No cron job required; no one has to remember to turn it off Monday morning.
  • Priority defaults to 100. Lower numbers run first. Use it to order rules that could conflict.

Apply order (this is the important bit)

When multiple rules match the same query, the engine runs them in a fixed order:

  1. Bury first. Drop every buried product_id from the candidate list. Pinning or boosting a buried product has no effect — it's already gone.
  2. Boost next. Multiply each matched product's similarity score by the winning multiplier (the highest one when multiple boost rules match the same product). Re-sort.
  3. Pin last. Prepend pinned products to the top in list order. Pinned products keep their real similarity score for display, but get positioned above the organic leaders.

Inside each phase, rules are ordered by priority ascending, then by created_at descending (newest wins ties).

Why bury-before-boost

If boost ran first, a buried product could be re-boosted by a conflicting rule and end up back in the response anyway. Bury is the most destructive effect, so it has to be unconditional. If you want a product gone, one bury rule is enough — no boost or pin elsewhere in the stack can override it.

Explainability: every moved result carries a breadcrumb

When a rule affects a result, we stamp the rule's ID and the effect into the product's _match_breakdown.applied_rules array:

"metadata": {
  "_match_breakdown": {
    "applied_rules": [
      {"rule_id": "…", "rule_type": "pin", "effect": "pinned",    "description": "Spring-sale pins"},
      {"rule_id": "…", "rule_type": "boost", "effect": "×1.50 boost", "description": ""}
    ],
    "explanation": "Strong match (91%) — pinned for campaign."
  }
}

The portal's Match Analysis modal reads this so a merchandiser can click any result and see exactly which rule moved it. Six months later when a colleague asks "why is SKU-HERO ranked #1 on 'sale'?", the answer is one click away instead of spelunking through Git.

Boost multiplier reality check

It's tempting to set boost_multiplier: 5.0 and call it a day, but scores are capped at 1.0 after boosting — so anything above ~2.5× produces identical results in practice. The useful range is narrow:

  • 1.2–1.5×: subtle nudge. Good for featured brands or new arrivals.
  • 1.5–2.0×: strong promotion without overriding relevance.
  • 0.5–0.8×: soft suppression — the product stays discoverable but drops below competitors.
  • ≥ 2.0× is usually a sign that pin is the right rule instead — you actually want a guarantee, not a nudge.

Operational notes

Rules are cached per tenant for 60 seconds in-process. When you create, update, or delete a rule via the API or portal, we invalidate the cache for that tenant immediately — so a weekend-sale campaign goes live on the next search, not a minute later.

The portal at /portal/merchandising is the recommended surface for business users. It mirrors the API exactly — dropdowns for rule and scope types, a boost-multiplier slider with a live preview, datetime pickers for the window, an active toggle, and a per-row visual dim for paused rules.

Next up

Merchandising rules work on whatever metadata your catalog ships with. When the default fields (category, brand, price) aren't enough, you need custom fields — that's the next post.