Skip to content

Feat(VTEX): Recommendations BFF API#1532

Open
vitoUwu wants to merge 7 commits intodeco-cx:mainfrom
vitoUwu:feat/vtex-recommendation-api
Open

Feat(VTEX): Recommendations BFF API#1532
vitoUwu wants to merge 7 commits intodeco-cx:mainfrom
vitoUwu:feat/vtex-recommendation-api

Conversation

@vitoUwu
Copy link
Contributor

@vitoUwu vitoUwu commented Feb 24, 2026

What is this Contribution About?

Implementation of VTEX Recommendations BFF API


Summary by cubic

Adds VTEX Recommendations BFF integration to fetch product recommendations and track product/recommendation events. Includes session bootstrap and enriches products with correlation and campaign metadata.

  • New Features

    • BFF HTTP client with OpenAPI spec for recommendations.
    • Product recommendations loader: supports campaign types, context products, location/sales channel, and returns products annotated with correlationId and campaign info.
    • Helpers to derive product IDs from cart and PDP.
    • Event actions: product-view, recommendation-view, recommendation-click (uses vtex-rec-user-id cookie or provided userId).
    • Start session action: creates recommendations user and proxies Set-Cookie.
    • Config: advancedConfigs.autoStartRecommendationSession to start a session when userId is missing.
  • Migration

    • Set x-vtex-rec-origin as {account}/{source}/{app} when calling loaders/actions; default used is {account}/storefront/deco.recommendations@1.x.
    • If enabling autoStartRecommendationSession, expect one extra call on a user’s first request; requires orderForm cookie.
    • Use loaders/recommendations/productList and pass correlationId to emit recommendation-view and recommendation-click events.

Written for commit fd5cbcd. Summary will update on new commits.

Summary by CodeRabbit

Release Notes

  • New Features
    • Added product view event tracking to monitor user engagement with products.
    • Added recommendation click event tracking to capture user interaction with recommended items.
    • Added recommendation view event tracking to record recommendation impressions.
    • Implemented product recommendation system with automatic session management for personalized recommendations based on cart contents and product page context.

@github-actions
Copy link
Contributor

Tagging Options

Should a new tag be published when this PR is merged?

  • 👍 for Patch 0.135.2 update
  • 🎉 for Minor 0.136.0 update
  • 🚀 for Major 1.0.0 update

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 24, 2026

📝 Walkthrough

Walkthrough

This PR introduces an event-driven recommendation system that integrates with VTEX's Recommendations BFF API. It adds event tracking loaders (product view, recommendation click/view), session management, product recommendation fetchers from multiple sources, utility functions for origin resolution and cookie parsing, OpenAPI specifications for the BFF API, and updates the manifest and module configuration to register new components.

Changes

Cohort / File(s) Summary
Event Tracking Actions
vtex/actions/events/productView.ts, vtex/actions/events/recommendationClick.ts, vtex/actions/events/recommendationView.ts
Three server loaders that track user interactions (product views, recommendation clicks/views). Each validates origin and userId, posts event data to the BFF endpoint, and returns success confirmation.
Recommendation Session
vtex/actions/recommendation/startSession.ts
Action that initiates a recommendation session by calling the BFF start-session endpoint, parsing the response, and proxying Set-Cookie headers back to the client.
Product Recommendation Loaders
vtex/loaders/recommendations/productList.ts, vtex/loaders/recommendations/productListFromCart.ts, vtex/loaders/recommendations/productListFromPage.ts
Three loaders that fetch product recommendations: from the BFF with complex campaign/segment logic, from current cart items, and from the current product page context.
Utility Functions & Types
vtex/utils/recommendations.ts, vtex/utils/types.ts, vtex/utils/transform.ts
Helper utilities for origin resolution and cookie parsing; new type exports (CampaignType, ProductListToId, ProductViewSource); extended ProductOptions interface with additional properties support.
OpenAPI Specifications
vtex/utils/openapi/recommendations-bff.openapi.gen.ts, vtex/utils/openapi/recommendations-bff.openapi.json
Auto-generated and manual OpenAPI 3.0.0 specifications defining the Recommendations BFF API surface, including all event endpoints, schemas, and response types.
Configuration & Integration
vtex/manifest.gen.ts, vtex/mod.ts
Manifest updates reorganizing actions/loaders from legacy modules to event/recommendation namespaces; mod.ts adds BFF HTTP client configuration with new autoStartRecommendationSession prop.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant App as Deco App
    participant BFF as Recommendations BFF
    
    Client->>App: Request product list with campaign config
    App->>App: Determine origin & segment from request
    App->>App: Parse cookies for userId (or start session)
    App->>BFF: POST /api/recommend-bff/v2/recommendations<br/>(account, campaign, location, user, products)
    BFF-->>App: RecommendationsV2Response
    App->>App: Map LegacyProduct[] to Product[]<br/>Enrich with correlationId, campaignId,<br/>campaignTitle, campaignType
    App-->>Client: Product[]
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Suggested reviewers

  • guitavano

Poem

🐰 Hop, hop, hooray! Events now track every view,
Sessions bloom and recommendations brew,
From cart to page, the BFF calls ring true,
A fluffy system, fresh and shiny new! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: adding VTEX Recommendations BFF API integration with event tracking, session management, and product recommendation features.
Description check ✅ Passed The PR description includes a detailed summary of the implementation, new features, and migration guidance. It covers the main contribution, references the official API documentation, and provides context about usage.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 14 files

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="vtex/actions/recommendation/startSession.ts">

<violation number="1" location="vtex/actions/recommendation/startSession.ts:39">
P2: Logging response data and Set-Cookie headers can leak sensitive session/auth information into logs. Remove or sanitize these logs in production.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.


const data = await response.json();

console.log(data, getSetCookies(response.headers));
Copy link

@cubic-dev-ai cubic-dev-ai bot Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Logging response data and Set-Cookie headers can leak sensitive session/auth information into logs. Remove or sanitize these logs in production.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At vtex/actions/recommendation/startSession.ts, line 39:

<comment>Logging response data and Set-Cookie headers can leak sensitive session/auth information into logs. Remove or sanitize these logs in production.</comment>

<file context>
@@ -0,0 +1,43 @@
+
+  const data = await response.json();
+
+  console.log(data, getSetCookies(response.headers));
+  proxySetCookie(response.headers, ctx.response.headers, req.url);
+
</file context>
Fix with Cubic

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@vtex/actions/events/productView.ts`:
- Around line 12-14: Update the JSDoc for the "Product ID" prop in the
product-view loader so the description reflects a view event rather than a
click: locate the comment block with `@title` "Product ID" in productView.ts (the
product-view loader) and change the phrase "The ID of the product that was
clicked." to something like "The ID of the product that was viewed." to match
the emitted product-view event.

In `@vtex/actions/recommendation/startSession.ts`:
- Line 39: Remove the sensitive console.log in the startSession action: delete
the line that logs "data" and the result of getSetCookies(response.headers) in
the startSession function, since it exposes session/recommendation identifiers;
if you need runtime visibility, replace it with a non-sensitive log such as
response.status or a sanitized message (e.g., "startSession succeeded") and
never log raw cookies or full response body—avoid calling getSetCookies for
logging or redact its output before any log.

In `@vtex/loaders/recommendations/productList.ts`:
- Around line 106-112: For rec-cross-v2 and rec-similar-v2 ensure a source
product exists before building the products/productIds: check campaignType and
validate props.products?.[0] is present; if missing, bail out (throw, return an
error response, or skip calling the BFF) instead of constructing products =
[props.products?.[0]] which yields undefined and productIds = "". Update the
logic around the products and productIds variables (referencing campaignType,
products, productIds, and props.products) to perform this guard and handle the
missing-product case explicitly.
- Line 123: The headers spread is dropping the segment cookie because
withSegmentCookie(segment) returns a Headers instance which doesn't spread into
a plain object; change the merge to preserve entries by converting the Headers
to a plain object (e.g. Object.fromEntries(withSegmentCookie(segment))) or build
a Headers object and call .set("x-vtex-rec-origin", origin) so the segment
cookie from withSegmentCookie(segment) is retained; update the code that
currently uses headers: { ...withSegmentCookie(segment), "x-vtex-rec-origin":
origin } to one of these approaches (refer to withSegmentCookie and the headers
property in productList.ts).

In `@vtex/utils/openapi/recommendations-bff.openapi.json`:
- Around line 266-269: The documentation for the "userId" query parameter
references the wrong cookie name; update its description to mention the correct
cookie "vtex-rec-user-id" (replace `vtex-recommendations-user` with
`vtex-rec-user-id`) so it matches the session flow and utilities, and scan the
OpenAPI spec for any other occurrences of `vtex-recommendations-user` to replace
them with `vtex-rec-user-id`.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9683a21 and fd5cbcd.

📒 Files selected for processing (14)
  • vtex/actions/events/productView.ts
  • vtex/actions/events/recommendationClick.ts
  • vtex/actions/events/recommendationView.ts
  • vtex/actions/recommendation/startSession.ts
  • vtex/loaders/recommendations/productList.ts
  • vtex/loaders/recommendations/productListFromCart.ts
  • vtex/loaders/recommendations/productListFromPage.ts
  • vtex/manifest.gen.ts
  • vtex/mod.ts
  • vtex/utils/openapi/recommendations-bff.openapi.gen.ts
  • vtex/utils/openapi/recommendations-bff.openapi.json
  • vtex/utils/recommendations.ts
  • vtex/utils/transform.ts
  • vtex/utils/types.ts

Comment on lines +12 to +14
* @title Product ID
* @description The ID of the product that was clicked.
*/
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix product-view prop description typo.

Line 13 says the product was “clicked”, but this loader emits a product-view event.

✏️ Suggested edit
-   * `@description` The ID of the product that was clicked.
+   * `@description` The ID of the product that was viewed.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
* @title Product ID
* @description The ID of the product that was clicked.
*/
* `@title` Product ID
* `@description` The ID of the product that was viewed.
*/
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@vtex/actions/events/productView.ts` around lines 12 - 14, Update the JSDoc
for the "Product ID" prop in the product-view loader so the description reflects
a view event rather than a click: locate the comment block with `@title` "Product
ID" in productView.ts (the product-view loader) and change the phrase "The ID of
the product that was clicked." to something like "The ID of the product that was
viewed." to match the emitted product-view event.


const data = await response.json();

console.log(data, getSetCookies(response.headers));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Remove sensitive logging from start-session action.

Line 39 logs response data and Set-Cookie values, which can expose recommendation/session identifiers in logs.

🔐 Proposed fix
-  console.log(data, getSetCookies(response.headers));
   proxySetCookie(response.headers, ctx.response.headers, req.url);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
console.log(data, getSetCookies(response.headers));
proxySetCookie(response.headers, ctx.response.headers, req.url);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@vtex/actions/recommendation/startSession.ts` at line 39, Remove the sensitive
console.log in the startSession action: delete the line that logs "data" and the
result of getSetCookies(response.headers) in the startSession function, since it
exposes session/recommendation identifiers; if you need runtime visibility,
replace it with a non-sensitive log such as response.status or a sanitized
message (e.g., "startSession succeeded") and never log raw cookies or full
response body—avoid calling getSetCookies for logging or redact its output
before any log.

Comment on lines +106 to +112
const products =
campaignType === "rec-cross-v2" || campaignType === "rec-similar-v2"
? [props.products?.[0]]
: props.products;

const productIds = products?.filter(Boolean).join(",");

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Validate required product context for cross/similar campaigns.

Lines 106-112 allow missing product IDs for rec-cross-v2 / rec-similar-v2, which can send an empty products parameter and cause avoidable BFF failures.

🛡️ Suggested fix
-    const products =
-      campaignType === "rec-cross-v2" || campaignType === "rec-similar-v2"
-        ? [props.products?.[0]]
-        : props.products;
+    const requiresSingleProduct =
+      campaignType === "rec-cross-v2" || campaignType === "rec-similar-v2";
+
+    if (requiresSingleProduct && !props.products?.[0]) {
+      throw new Error(
+        `campaignType "${props.campaignType}" requires one product ID`,
+      );
+    }
+
+    const products = requiresSingleProduct
+      ? [props.products![0]]
+      : props.products;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const products =
campaignType === "rec-cross-v2" || campaignType === "rec-similar-v2"
? [props.products?.[0]]
: props.products;
const productIds = products?.filter(Boolean).join(",");
const requiresSingleProduct =
campaignType === "rec-cross-v2" || campaignType === "rec-similar-v2";
if (requiresSingleProduct && !props.products?.[0]) {
throw new Error(
`campaignType "${props.campaignType}" requires one product ID`,
);
}
const products = requiresSingleProduct
? [props.products![0]]
: props.products;
const productIds = products?.filter(Boolean).join(",");
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@vtex/loaders/recommendations/productList.ts` around lines 106 - 112, For
rec-cross-v2 and rec-similar-v2 ensure a source product exists before building
the products/productIds: check campaignType and validate props.products?.[0] is
present; if missing, bail out (throw, return an error response, or skip calling
the BFF) instead of constructing products = [props.products?.[0]] which yields
undefined and productIds = "". Update the logic around the products and
productIds variables (referencing campaignType, products, productIds, and
props.products) to perform this guard and handle the missing-product case
explicitly.

products: productIds,
salesChannel: segment?.payload?.channel || ctx.salesChannel,
}, {
headers: { ...withSegmentCookie(segment), "x-vtex-rec-origin": origin },
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Check vtex/utils/segment.ts for withSegmentCookie definition and Headers usage
echo "=== Checking vtex/utils/segment.ts ==="
rg -n -C3 'export const withSegmentCookie|new Headers\(|return h' vtex/utils/segment.ts

echo ""
echo "=== Checking vtex/loaders/recommendations/productList.ts line 123 ==="
rg -n -C3 '\.\.\.withSegmentCookie\(segment\)' vtex/loaders/recommendations/productList.ts

Repository: deco-cx/apps

Length of output: 858


Segment cookie is dropped when spreading Headers into a plain object.

Line 123 spreads withSegmentCookie(segment) (which returns a Headers instance) into a plain object literal. A Headers object doesn't preserve its entries when spread into {} because it's a Web API object with custom iteration semantics, not a plain object with enumerable properties. This silently drops the segment cookie and breaks segment-aware recommendations.

Fix
-    const response = await bff["GET /api/recommend-bff/v2/recommendations"]({
+    const response = await bff["GET /api/recommend-bff/v2/recommendations"]({
       an: account,
       campaignVrn,
       pickupPoint: props.pickupPoint,
       regionId: props.regionId,
       zipCode: props.zipCode,
       userId,
       products: productIds,
       salesChannel: segment?.payload?.channel || ctx.salesChannel,
     }, {
-      headers: { ...withSegmentCookie(segment), "x-vtex-rec-origin": origin },
+      headers: withSegmentCookie(
+        segment,
+        new Headers({ "x-vtex-rec-origin": origin }),
+      ),
     }).then((res) => res.json());
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@vtex/loaders/recommendations/productList.ts` at line 123, The headers spread
is dropping the segment cookie because withSegmentCookie(segment) returns a
Headers instance which doesn't spread into a plain object; change the merge to
preserve entries by converting the Headers to a plain object (e.g.
Object.fromEntries(withSegmentCookie(segment))) or build a Headers object and
call .set("x-vtex-rec-origin", origin) so the segment cookie from
withSegmentCookie(segment) is retained; update the code that currently uses
headers: { ...withSegmentCookie(segment), "x-vtex-rec-origin": origin } to one
of these approaches (refer to withSegmentCookie and the headers property in
productList.ts).

Comment on lines +266 to +269
"name": "userId",
"in": "query",
"description": "User ID, required for personalized recommendations based on user behavior. It can be found in the `vtex-recommendations-user` cookie.",
"required": false,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix inconsistent cookie name in userId parameter docs.

Line 268 references vtex-recommendations-user, but the session flow and utilities use vtex-rec-user-id. This inconsistency can cause integration mistakes.

🛠️ Proposed fix
-            "description": "User ID, required for personalized recommendations based on user behavior. It can be found in the `vtex-recommendations-user` cookie.",
+            "description": "User ID, required for personalized recommendations based on user behavior. It can be found in the `vtex-rec-user-id` cookie.",
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"name": "userId",
"in": "query",
"description": "User ID, required for personalized recommendations based on user behavior. It can be found in the `vtex-recommendations-user` cookie.",
"required": false,
"name": "userId",
"in": "query",
"description": "User ID, required for personalized recommendations based on user behavior. It can be found in the `vtex-rec-user-id` cookie.",
"required": false,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@vtex/utils/openapi/recommendations-bff.openapi.json` around lines 266 - 269,
The documentation for the "userId" query parameter references the wrong cookie
name; update its description to mention the correct cookie "vtex-rec-user-id"
(replace `vtex-recommendations-user` with `vtex-rec-user-id`) so it matches the
session flow and utilities, and scan the OpenAPI spec for any other occurrences
of `vtex-recommendations-user` to replace them with `vtex-rec-user-id`.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant