Skip to content

Recommendation Rules

Recommendation in Dino Discover is deliberately simple: each answer carries upvote weights against products or product variants; the engine sums every upvote a shopper accumulates and ranks the result.

That model is small enough to debug in your head, which is the point. When you need something fancier, the engine is a single PHP interface you can replace — but the default works well for catalogues with hundreds of products and a four-or-five-question quiz.

Every answer in the Builder has an Upvotes panel on the right edge. It contains a small list of upvote rows:

  • Target — a product or a product variant.
  • Weight — a positive integer (usually 1).

Add a row, search for the product or variant, set the weight, save.

[SCREENSHOT: The Upvotes panel on a single-select answer with three product targets, each weighted 1.]

The recommendation engine, on a session-completion recommend call, sums these per product across every answer the shopper picked. The products are ranked by total upvotes, ties broken by stable product order.

Worked example — three questions, three products

Section titled “Worked example — three questions, three products”

Suppose your store sells three coffees: Bright, Bold, and Balanced. You build a three-question quiz:

QuestionAnswer AAnswer BAnswer C
Roast preferenceLight: +1 BrightMedium: +1 BalancedDark: +1 Bold
Brewing methodFilter: +1 Bright, +1 BalancedEspresso: +1 Bold, +1 BalancedFrench press: +1 Bold
StrengthEasy: +1 BrightStandard: +1 BalancedStrong: +1 Bold

A shopper picks Medium / Espresso / Standard. Their upvote totals become:

  • Bright = 0
  • Balanced = 1 + 1 + 1 = 3
  • Bold = 0 + 1 + 0 = 1

The recommendation engine returns Balanced first, then Bold, then Bright.

That’s the entire model.

Weight 1 is almost always the right value. You can set higher weights on answers that strongly indicate a product, but the model is addition-based — bigger weights compound across answers, so the maths gets harder to reason about quickly. Two patterns that tend to work:

  • Default to 1. Treat each upvote as one vote. The total ordering comes from how many votes a product accumulates, not from a few loud answers.
  • Use 2 or 3 sparingly, to encode “this answer all but guarantees this product.” For example, an allergy question that says “fragrance-free only” should weight unscented products higher because every other product is now disqualified by reality.

The default engine doesn’t just rank — it also filters. Before scoring, it drops products that:

  • Are not purchasable in WooCommerce (out of stock, draft post, trashed).
  • Are not visible on the storefront (catalogue visibility set to Hidden).
  • Don’t match any variation gates the merchant configured (e.g. only variants in stock that match the answer’s variant target).

So if a product is upvoted by a shopper’s answers but is out of stock, it disappears from the result list — the rest of the ranking shifts up.

After scoring and filtering, the engine returns a RecommendationSet through the filter dino_discover_recommendation_results. Subscribers can re-rank, suppress, or inject items at the last step. This is the canonical extension point for storefront-side business rules — “always promote products from the seasonal collection” or “never return more than two products from the same category”.

See Hook Reference for the signature.

Tag-based shortcuts vs per-product upvotes

Section titled “Tag-based shortcuts vs per-product upvotes”

You can upvote a product directly, or upvote a product variant. There is no first-class “upvote a tag” shortcut in the Beta — instead, the recommended workflow for tagged catalogues is:

  1. Tag your products.
  2. In the Builder, search by tag and select the products to upvote.

This keeps the upvote model concrete (each upvote points at a specific product) without forcing you to maintain a separate tagging convention.

For a worked filter that adds tag-style upvotes, see the dino_discover_recommendation_results example in Hook Reference.

For agencies and developers who want a different ranking model — an embedding-based ranker, an LLM ranker, a hybrid — the engine sits behind a single PHP interface:

RecommendationEngineInterface::recommend(
QuizContext $context,
AnswerHistory $history
): RecommendationSet;

Implement the interface, return it from a class your container can build, and use the dino_discover_recommendation_engine filter to substitute it.

The storefront calls POST /storefront/sessions/{sid}/recommend. That endpoint dispatches into whatever the filter resolves. Neither the storefront bundle nor the REST contract changes when you swap engines.

See Engine Interfaces for the full contracts.