Smart Sorting and Reranking with the SDK

Smart sorting lets you pass an existing candidate set, such as search results or an editorial list, and ask Froomle to return those candidates in personalized order. The SDK uses the recommendation API list_content field for this.

Use this SDK page when you want browser-side integration details. For the product/API concept, see Smart Sorting & Reranking.

When to Use It

Use smart sorting when:

  • your application already owns the candidate set,

  • Froomle should rank that set for the current visitor,

  • you still want normal recommendation attribution where the SDK owns or can stamp rendered recommendation metadata.

Typical examples:

  • reranking search results that are already returned by a commerce/search backend,

  • reranking editorially selected article lists,

  • pinning sponsored or editorial items while personalizing the remaining positions.

Smart sorting is list-based. It is not the same as slot-based proxyReco(…​), useCreateReco(…​), or repeated data-froomle-reco placeholders. A smart-sort request sends one list with candidate items in list_content and receives one sorted list back.

Shared SDK Model

All SDK smart-sorting integrations send a recommendation list with list_content. The behavior depends on whether candidates have rank.

Shape SDK input Expected behavior

Smart sorting request

Candidates without rank.

Froomle reranks the supplied candidates. It does not fill missing positions with unrelated recommendations.

Request with fixed-rank items

Candidates where every item has rank / data-froomle-rank / getRank(…​).

Froomle pins those items and may fill the remaining positions with normal recommendations.

Combined request

A mix of ranked and unranked candidates in the same list.

Ranked candidates are pinned. Unranked candidates are reranked into open positions. Do not rely on unrelated fill when free candidates are present.

Plain smart sorting request
{
  "list_name": "search_results_rerank",
  "list_size": 3,
  "limit": 3,
  "list_content": [
    { "id": "sku-1", "item_type": "product" },
    { "id": "sku-2", "item_type": "product" },
    { "id": "sku-3", "item_type": "product" }
  ]
}
Fixed-rank request
{
  "list_name": "curated_feed",
  "list_size": 6,
  "limit": 6,
  "list_content": [
    { "id": "promo-article", "item_type": "article", "rank": 1 },
    { "id": "sponsored-item", "item_type": "article", "rank": 4 }
  ]
}
Combined request
{
  "list_name": "search_results_rerank",
  "list_size": 4,
  "limit": 4,
  "list_content": [
    { "id": "sku-sponsored", "item_type": "product", "rank": 1 },
    { "id": "sku-1", "item_type": "product" },
    { "id": "sku-2", "item_type": "product" },
    { "id": "sku-3", "item_type": "product" }
  ]
}

Candidate fields:

  • id: required candidate item ID.

  • item_type: item type. The SDK defaults to article when no type is provided.

  • rank: optional 1-based fixed-rank position.

The returned recommendation items follow the normal recommendation response model. The SDK matches returned items back to candidates by item_type + id.

Integration Methods

The SDK docs use the same ordering throughout:

  1. Declarative DOM / script-tag.

  2. Module/import setup with declarative DOM.

  3. Programmatic JS/TS.

  4. React bindings.

Choose the first method that matches your rendering ownership.

Declarative DOM / Script-Tag

Use this when the final browser DOM already contains the candidate elements and the SDK may reorder those elements directly. This works for plain HTML, SSR templates, JSP, Twig, ArcXP static output types, and similar DOM-first pages.

Markup contract:

  • Put data-froomle-smart-sort="list_name" on the direct container.

  • Mark direct child candidates with data-froomle-smart-sort-item.

  • Put the candidate ID on each child with data-froomle-id.

  • Set data-froomle-item-type on the container or child when the item type is not article.

  • Optionally set data-froomle-rank on a candidate child for fixed-rank placement.

  • Optionally set data-froomle-limit and data-froomle-list-size on the container.

<script
  src="https://cdn.example.com/froomle.global.js"
  data-froomle-env="demo_shop"
  data-froomle-page-visit="search"
  data-froomle-consent="2"
  defer
></script>

<ul
  data-froomle-smart-sort="search_results_rerank"
  data-froomle-item-type="product"
  data-froomle-list-size="3"
  data-froomle-limit="3"
>
  <li data-froomle-smart-sort-item data-froomle-id="sku-1">...</li>
  <li data-froomle-smart-sort-item data-froomle-id="sku-2" data-froomle-rank="1">...</li>
  <li data-froomle-smart-sort-item data-froomle-id="sku-3">...</li>
</ul>

Runtime behavior:

  • The SDK reads only direct child candidates.

  • The SDK sends those candidates as list_content.

  • A child with data-froomle-rank becomes a fixed-rank item; children without it are free candidates for reranking.

  • Matched returned items are moved into returned order.

  • Unmatched candidates are kept after matched candidates in original order.

  • Matched candidates are stamped with data-froomle-reco, data-froomle-request-id, data-froomle-item-type, and returned user-group metadata where available.

  • Automatic impression and click_on_recommendation tracking can work on stamped candidates, following the same rules as normal SDK-owned DOM recommendation elements.

Failure behavior:

  • If required page context is missing, the SDK leaves the customer DOM untouched.

  • If a request fails, the SDK restores the candidate DOM and fails open.

  • Candidates without usable data-froomle-id are skipped and reported in smart-sorting diagnostics warnings.

Module / JavaScript Setup with Declarative DOM

Use this when your app imports @froomle/frontend-sdk but still renders declarative smart-sort DOM. This is the same DOM contract as script-tag mode, but module/import integrations must start DOM processing explicitly with init().

import {
  init,
  setConsent,
  setEnvironment,
  setPageVisit,
} from '@froomle/frontend-sdk'

setEnvironment('demo_shop')
setPageVisit('search')
setConsent(2)

await init()
<ul data-froomle-smart-sort="search_results_rerank" data-froomle-item-type="product">
  <li data-froomle-smart-sort-item data-froomle-id="sku-1">...</li>
  <li data-froomle-smart-sort-item data-froomle-id="sku-2">...</li>
</ul>

Use this path when:

  • your build system owns SDK loading,

  • templates or server output still own the candidate markup,

  • you want the SDK to reorder/stamp that existing DOM.

Do not use this path inside a React-rendered subtree unless you intentionally run declarative DOM processing there. For React-owned UI, prefer React bindings.

Programmatic JS/TS

Use smartSort(…​) when your code owns candidates and rendering. The helper normalizes candidates, sends list_content, and maps returned Froomle items back to the original candidates.

import {
  setEnvironment,
  setPageVisit,
  setConsent,
  smartSort,
} from '@froomle/frontend-sdk'

setEnvironment('demo_shop')
setPageVisit('search')
setConsent(2)

const result = await smartSort({
  list_name: 'search_results_rerank',
  list_size: products.length,
  limit: products.length,
  candidates: products,
  getId: (product) => product.sku,
  getItemType: () => 'product',
  getRank: (product) => product.pinnedRank,
})

for (const entry of result.entries) {
  renderProduct(entry.candidate, entry.item)
}

Candidate ID resolution order:

  • getId(candidate, index) when provided,

  • candidate.id,

  • candidate.item_id,

  • candidate.itemId,

  • string/number candidate values directly.

Candidate item type resolution order:

  • getItemType(candidate, index) when provided,

  • candidate.item_type,

  • candidate.itemType,

  • request-level item_type / itemType,

  • article fallback.

Fixed-rank resolution order:

  • getRank(candidate, index) when provided,

  • candidate.rank.

If no candidate has a rank, smartSort(…​) is a plain smart-sorting request. If every candidate has a rank, it is a fixed-rank request. If only some candidates have a rank, it is a combined request.

Return value:

  • entries: matched original candidate + returned Froomle item pairs.

  • items: returned Froomle recommendation items.

  • sortedCandidates: original candidates in Froomle response order.

  • unmatchedCandidates: original candidates not returned by Froomle.

  • response and list: raw response/list objects for inspection.

Programmatic rendering is customer-owned. If you render arbitrary custom DOM without SDK metadata, automatic recommendation impression/click tracking is not guaranteed. Either render equivalent attribution metadata into DOM, use manual sendEvent(…​), or use React FroomleReco entry={entry} when rendering in React.

Raw getRecommendations(…​) with list_content

Use raw getRecommendations(…​) only when you deliberately want to own response mapping yourself.

const response = await getRecommendations([
  {
    list_name: 'search_results_rerank',
    list_size: products.length,
    limit: products.length,
    list_content: products.map((product) => ({
      id: product.sku,
      item_type: 'product',
      rank: product.pinnedRank,
    })),
  },
])

This keeps the request shape explicit, but unlike smartSort(…​), it does not return sortedCandidates, unmatchedCandidates, or matched candidate entries.

React Bindings

Use useSmartSort(…​) when a React component owns candidate data and rendering. Render returned entries through FroomleReco entry={entry} when you want automatic recommendation impression and click_on_recommendation tracking.

import { FroomleReco, useSmartSort } from '@froomle/frontend-sdk/react'

function SearchResults({ products }) {
  const { entries, loading, unmatchedCandidates } = useSmartSort({
    listName: 'search_results_rerank',
    list_size: products.length,
    candidates: products,
    getId: (product) => product.sku,
    getItemType: () => 'product',
    getRank: (product) => product.pinnedRank,
  })

  if (loading) return null

  return (
    <>
      {entries.map((entry) => (
        <FroomleReco key={entry.itemId} entry={entry} asChild>
          <ProductCard product={entry.candidate} />
        </FroomleReco>
      ))}
      {unmatchedCandidates.map((product) => (
        <ProductCard key={product.sku} product={product} />
      ))}
    </>
  )
}

Return value:

  • status: loading, success, or error.

  • loading: boolean loading flag.

  • error: request error when status is error.

  • entries: renderable entries for FroomleReco entry={entry}.

  • items: returned Froomle recommendation items.

  • sortedCandidates: original candidates in Froomle response order.

  • unmatchedCandidates: original candidates not returned by Froomle.

  • response and list: raw response/list objects for inspection.

Request-shaping options:

  • benchmark follows the same benchmark config rules as useRecoList(…​).

  • exclusions, excludeItems, and autoExclusions follow the same in-page deduplication rules as useRecoList(…​).

If you render entries without FroomleReco, the SDK no longer owns recommendation attribution metadata for those React nodes. Treat recommendation impressions and clicks as manual in that case.

Backend-Fetched or Server-Side Reranking

The browser frontend SDK does not run on the backend. For backend-side smart sorting, call the recommendation API directly with list_content from your server and then decide how the browser should track rendered recommendations.

Supported browser follow-up options:

  • Render returned metadata into DOM with data-froomle-reco, data-froomle-id, data-froomle-request-id, and optional data-froomle-user-group; then the DOM event layer can auto-track.

  • Render custom UI and send recommendation events manually with sendEvent(…​) including action_item, action_item_type, list_name, request_id, and applicable user_group.

  • In React, prefer browser-side useSmartSort(…​) if the candidate list is available client-side and automatic tracking is desired.

Diagnostics

The browser runtime exposes the latest smart-sorting resolution under:

window.FroomleFrontendSdkRuntime.diagnostics.smartSorting

Fields:

  • lastResolvedAt: timestamp of the last smart-sorting resolution.

  • lastSurface: dom, programmatic, or react.

  • lastListName: last smart-sorted list name.

  • lastCandidateCount: number of candidates sent.

  • lastFixedRankCount: number of candidates with fixed rank.

  • lastResponseCount: number of returned recommendation items.

  • lastUnmatchedCount: number of input candidates not returned by Froomle.

  • warnings: recent smart-sorting warnings, for example missing DOM candidate IDs.

Troubleshooting

Symptom Check

No request is sent

Verify shared SDK context: environment, page type, consent, and initialization. Module/import DOM integrations must call init().

DOM candidates do not reorder

Verify the smart-sort container has data-froomle-smart-sort, and candidate elements are direct children with data-froomle-smart-sort-item and data-froomle-id.

Some candidates are missing from the sorted result

Check unmatchedCandidates or diagnostics.smartSorting.lastUnmatchedCount. Froomle can only return candidates it recognizes for that list/environment.

Recommendation impressions/clicks are missing

Verify the rendered item is SDK-stamped/SDK-rendered. In React, render through FroomleReco entry={entry}. In custom programmatic UI, either render equivalent DOM metadata or send events manually.

Product IDs do not match returned items

Check the ID and item-type adapters. Matching is done by item_type + id.