From 5e5dd883010d05a6e5f7215d489826500da1b601 Mon Sep 17 00:00:00 2001 From: vitoUwu Date: Tue, 24 Feb 2026 19:16:18 -0400 Subject: [PATCH 01/12] feat: add types and setup http client --- vtex/mod.ts | 13 + .../recommendations-bff.openapi.gen.ts | 241 +++++ .../openapi/recommendations-bff.openapi.json | 949 ++++++++++++++++++ vtex/utils/types.ts | 21 + 4 files changed, 1224 insertions(+) create mode 100644 vtex/utils/openapi/recommendations-bff.openapi.gen.ts create mode 100644 vtex/utils/openapi/recommendations-bff.openapi.json diff --git a/vtex/mod.ts b/vtex/mod.ts index 8c075d3bb..eda325845 100644 --- a/vtex/mod.ts +++ b/vtex/mod.ts @@ -9,6 +9,7 @@ import { OpenAPI as VCS } from "./utils/openapi/vcs.openapi.gen.ts"; import { OpenAPI as API } from "./utils/openapi/api.openapi.gen.ts"; import { OpenAPI as MY } from "./utils/openapi/my.openapi.gen.ts"; import { OpenAPI as VPAY } from "./utils/openapi/payments.openapi.gen.ts"; +import { OpenAPI as BFF } from "./utils/openapi/recommendations-bff.openapi.gen.ts"; import { OpenAPI as SUB } from "./utils/openapi/subscriptions.openapi.gen.ts"; import { Segment } from "./utils/types.ts"; import type { Secret } from "../website/loaders/secret.ts"; @@ -92,6 +93,12 @@ export interface Props { * @description Remove UTM from cache key to prevent cache fragmentation. */ removeUTMFromCacheKey?: boolean; + /** + * @title Auto Start Recommendation Session + * @description This automatically starts the recommendation session if the recommendation ID is not present in the product recommendations loader, this might lead to a performance impact for the very first request when the user doesn't have a session yet. + * @default false + */ + autoStartRecommendationSession?: boolean; }; /** @@ -184,6 +191,11 @@ export default function VTEX( fetcher: fetchSafe, headers: headers, }); + const bff = createHttpClient({ + base: `https://api.vtexcommercestable.com.br`, + processHeaders: removeDirtyCookies, + fetcher: fetchSafe, + }); const cachedSearchTerms = [ ...(props.cachedSearchTerms?.terms?.searches ?? []).map((search) => @@ -205,6 +217,7 @@ export default function VTEX( api, vpay, sub, + bff, cachedSearchTerms, }; const app: A Date: Tue, 24 Feb 2026 19:16:51 -0400 Subject: [PATCH 02/12] feat: add toProduct option include additional properties to product object --- vtex/utils/transform.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/vtex/utils/transform.ts b/vtex/utils/transform.ts index c6dc0974c..684d34b10 100644 --- a/vtex/utils/transform.ts +++ b/vtex/utils/transform.ts @@ -88,6 +88,7 @@ interface ProductOptions { imagesByKey?: Map; /** Original attributes to be included in the transformed product */ includeOriginalAttributes?: string[]; + isVariantOfAdditionalProperty?: PropertyValue[]; } /** Returns first available sku */ @@ -396,6 +397,7 @@ export const toProduct =

( url: getProductGroupURL(baseUrl, product).href, name: product.productName, additionalProperty: [ + ...(options.isVariantOfAdditionalProperty ?? []), ...groupAdditionalProperty, ...originalAttributesAdditionalProperties, ], From 21722697289992166e0db4c7e93ebf92856c1881 Mon Sep 17 00:00:00 2001 From: vitoUwu Date: Tue, 24 Feb 2026 19:17:15 -0400 Subject: [PATCH 03/12] feat: add event actions --- vtex/actions/events/productView.ts | 61 ++++++++++++++++++++++ vtex/actions/events/recommendationClick.ts | 59 +++++++++++++++++++++ vtex/actions/events/recommendationView.ts | 59 +++++++++++++++++++++ 3 files changed, 179 insertions(+) create mode 100644 vtex/actions/events/productView.ts create mode 100644 vtex/actions/events/recommendationClick.ts create mode 100644 vtex/actions/events/recommendationView.ts diff --git a/vtex/actions/events/productView.ts b/vtex/actions/events/productView.ts new file mode 100644 index 000000000..b01976003 --- /dev/null +++ b/vtex/actions/events/productView.ts @@ -0,0 +1,61 @@ +import type { AppContext } from "../../mod.ts"; +import { getOrigin, parseCookie } from "../../utils/recommendations.ts"; +import type { ProductViewSource } from "../../utils/types.ts"; + +interface Props { + /** + * @title User ID + * @description The ID of the user who viewed the recommendation. + */ + userId?: string; + /** + * @title Product ID + * @description The ID of the product that was clicked. + */ + productId: string; + /** + * @title Source + * @description The source of the product view event. + */ + source: ProductViewSource; + /** + * @description The origin of the recommendation request. E.g: apiexamples/storefront/vtex.recommendation-shelf@2.x + */ + "x-vtex-rec-origin"?: string; +} + +export default async function loader( + props: Props, + req: Request, + ctx: AppContext, +) { + const { bff } = ctx; + + const origin = getOrigin(req, ctx.account, props["x-vtex-rec-origin"]); + if (!origin) { + throw new Error("x-vtex-rec-origin header is required"); + } + + const cookies = parseCookie(req.headers); + const userId = props.userId ?? cookies?.userId; + + if (!userId) { + throw new Error("userId is required"); + } + + await bff["POST /api/recommend-bff/v2/events/product-view"]({ + an: ctx.account, + }, { + body: { + userId, + product: props.productId, + source: props.source, + }, + headers: { + "x-vtex-rec-origin": origin, + "user-agent": req.headers.get("user-agent") || "", + }, + }); + + return { ok: true }; +} diff --git a/vtex/actions/events/recommendationClick.ts b/vtex/actions/events/recommendationClick.ts new file mode 100644 index 000000000..7b6b31ae0 --- /dev/null +++ b/vtex/actions/events/recommendationClick.ts @@ -0,0 +1,59 @@ +import { AppContext } from "../../mod.ts"; +import { getOrigin, parseCookie } from "../../utils/recommendations.ts"; + +interface Props { + /** + * @title User ID + * @description The ID of the user who viewed the recommendation. + */ + userId?: string; + /** + * @title Product ID + * @description The ID of the product that was clicked. + */ + productId: string; + /** + * @title Correlation ID + * @description The correlation ID of the recommendation request. + */ + correlationId: string; + /** + * @description The origin of the recommendation request. E.g: apiexamples/storefront/vtex.recommendation-shelf@2.x + */ + "x-vtex-rec-origin"?: string; +} + +export default async function loader( + props: Props, + req: Request, + ctx: AppContext, +) { + const { bff } = ctx; + + const origin = getOrigin(req, ctx.account, props["x-vtex-rec-origin"]); + if (!origin) { + throw new Error("x-vtex-rec-origin header is required"); + } + + const cookies = parseCookie(req.headers); + const userId = props.userId ?? cookies?.userId; + + if (!userId) { + throw new Error("userId is required"); + } + + await bff["POST /api/recommend-bff/v2/events/recommendation-click"]({ + an: ctx.account, + }, { + body: { + correlationId: props.correlationId, + userId, + product: props.productId, + }, + headers: { + "x-vtex-rec-origin": origin, + }, + }); + + return { ok: true }; +} diff --git a/vtex/actions/events/recommendationView.ts b/vtex/actions/events/recommendationView.ts new file mode 100644 index 000000000..94346bd0b --- /dev/null +++ b/vtex/actions/events/recommendationView.ts @@ -0,0 +1,59 @@ +import { AppContext } from "../../mod.ts"; +import { getOrigin, parseCookie } from "../../utils/recommendations.ts"; + +interface Props { + /** + * @title User ID + * @description The ID of the user who viewed the recommendation. + */ + userId?: string; + /** + * @title Correlation ID + * @description The correlation ID of the recommendation request. + */ + correlationId: string; + /** + * @title Products + * @description The products SKU IDs that were viewed. + */ + products: string[]; + /** + * @description The origin of the recommendation request. E.g: apiexamples/storefront/vtex.recommendation-shelf@2.x + */ + "x-vtex-rec-origin"?: string; +} + +export default async function loader( + props: Props, + req: Request, + ctx: AppContext, +) { + const { bff } = ctx; + + const origin = getOrigin(req, ctx.account, props["x-vtex-rec-origin"]); + if (!origin) { + throw new Error("x-vtex-rec-origin header is required"); + } + + const cookies = parseCookie(req.headers); + const userId = props.userId ?? cookies?.userId; + + if (!userId) { + throw new Error("userId is required"); + } + + await bff["POST /api/recommend-bff/v2/events/recommendation-view"]({ + an: ctx.account, + }, { + body: { + correlationId: props.correlationId, + userId, + products: props.products, + }, + headers: { + "x-vtex-rec-origin": origin, + }, + }); + + return { ok: true }; +} From 29beb57a96ffd557a8044fd14a8e37018f3e51c7 Mon Sep 17 00:00:00 2001 From: vitoUwu Date: Tue, 24 Feb 2026 19:17:28 -0400 Subject: [PATCH 04/12] feat: add startSession api --- vtex/actions/recommendation/startSession.ts | 43 +++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 vtex/actions/recommendation/startSession.ts diff --git a/vtex/actions/recommendation/startSession.ts b/vtex/actions/recommendation/startSession.ts new file mode 100644 index 000000000..4d57ca93a --- /dev/null +++ b/vtex/actions/recommendation/startSession.ts @@ -0,0 +1,43 @@ +import { getSetCookies } from "std/http/cookie.ts"; +import type { AppContext } from "../../mod.ts"; +import { proxySetCookie } from "../../utils/cookies.ts"; +import { parseCookie } from "../../utils/orderForm.ts"; + +export default async function action( + _: unknown, + req: Request, + ctx: AppContext, +) { + const { bff } = ctx; + const { orderFormId } = parseCookie(req.headers); + + const url = new URL(req.url); + const host = url.host; + + const headers = new Headers(); + headers.set( + "x-vtex-rec-origin", + `${ctx.account}/storefront/deco.recommendations@1.x`, + ); + headers.set("x-forwarded-host", host); + headers.set("host", `${ctx.account}.vtexcommercestable.com.br`); + + const cookie = req.headers.get("cookie"); + if (cookie) { + headers.set("cookie", cookie); + } + + const response = await bff["POST /api/recommend-bff/v2/users/start-session"]({ + an: ctx.account, + }, { + body: { orderFormId }, + headers, + }); + + const data = await response.json(); + + console.log(data, getSetCookies(response.headers)); + proxySetCookie(response.headers, ctx.response.headers, req.url); + + return data; +} From 646aaba728732aa0895f07c5d04c99b1fb5762bb Mon Sep 17 00:00:00 2001 From: vitoUwu Date: Tue, 24 Feb 2026 19:18:04 -0400 Subject: [PATCH 05/12] feat: product recommendation APIs --- vtex/loaders/recommendations/productList.ts | 169 ++++++++++++++++++++ vtex/utils/recommendations.ts | 25 +++ 2 files changed, 194 insertions(+) create mode 100644 vtex/loaders/recommendations/productList.ts create mode 100644 vtex/utils/recommendations.ts diff --git a/vtex/loaders/recommendations/productList.ts b/vtex/loaders/recommendations/productList.ts new file mode 100644 index 000000000..b69073b9f --- /dev/null +++ b/vtex/loaders/recommendations/productList.ts @@ -0,0 +1,169 @@ +import { Product } from "../../../commerce/types.ts"; +import { AppContext } from "../../mod.ts"; +import { getOrigin, parseCookie } from "../../utils/recommendations.ts"; +import { getSegmentFromBag, withSegmentCookie } from "../../utils/segment.ts"; +import { toProduct } from "../../utils/transform.ts"; +import { + CampaignType, + LegacyProduct, + ProductListToId, +} from "../../utils/types.ts"; +import { getFirstItemAvailable } from "../legacy/productListingPage.ts"; + +type CampaignTypeLabel = + | "Best sellers" + | "Personalized recommendations" + | "Similar items" + | "Cross-sell" + | "Cart-based recommendations" + | "Last seen" + | "Recent interactions" + | "Visual similarity" + | "Search-based recommendations" + | "Next interaction"; + +const campaignTypeMap: Record = { + "Best sellers": "rec-top-items-v2", + "Personalized recommendations": "rec-persona-v2", + "Similar items": "rec-similar-v2", + "Cross-sell": "rec-cross-v2", + "Cart-based recommendations": "rec-cart-v2", + "Last seen": "rec-last-v2", + "Recent interactions": "rec-interactions-v2", + "Visual similarity": "rec-visual-v2", + "Search-based recommendations": "rec-search-v2", + "Next interaction": "rec-next-v2", +}; + +interface Props { + /** + * @title Campaign Type + * @description The type of campaign to fetch recommendations for. + */ + campaignType: CampaignTypeLabel; + /** + * @title Campaign ID + * @description Contact https://supporticket.vtex.com/support to get the campaign ID. + */ + campaignId: string; + /** + * @title Products + * @description List of product IDs for context-based recommendations. For similar items and cross-sell, send only one product ID. For cart recommendations, send all product IDs in the user's cart. + */ + products?: ProductListToId; + /** + * @title Zip Code + * @description Zip code for location-based recommendations. If not provided, it is extracted from the segment cookie. + */ + zipCode?: string; + /** + * @title Pickup Point + * @description Pickup point identifier for location-based recommendations. If not provided, it is extracted from the segment cookie. + */ + pickupPoint?: string; + /** + * @title Region ID + * @description Region identifier for location-based recommendations. If not provided, it is extracted from the segment cookie. + */ + regionId?: string; + /** + * @ignore true + */ + "x-vtex-rec-origin"?: string; +} + +/** + * @title VTEX Recommendations Product List + * @description Get a list of products from the VTEX Recommendations API + */ +export default async function loader( + props: Props, + req: Request, + ctx: AppContext, +): Promise { + try { + const { bff, account } = ctx; + + const origin = getOrigin(req, ctx.account, props["x-vtex-rec-origin"]); + const segment = getSegmentFromBag(ctx); + const cookies = parseCookie(req.headers); + + let userId = cookies?.userId; + + if (ctx.advancedConfigs?.autoStartRecommendationSession && !userId) { + const response = await ctx.invoke.vtex.actions.recommendation + .startSession(); + userId = response.recommendationsUserId; + } + + const campaignType = campaignTypeMap[props.campaignType]; + const campaignVrn = + `vrn:recommendations:${account}:${campaignType}:${props.campaignId}`; + + // From vtex docs: + // "For similar items and cross-sell, send only one product ID. + // For cart recommendations, send all product IDs in the user's cart." + const products = + campaignType === "rec-cross-v2" || campaignType === "rec-similar-v2" + ? [props.products?.[0]] + : props.products; + + const productIds = products?.filter(Boolean).join(","); + + 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 }, + }).then((res) => res.json()); + + const baseUrl = new URL(req.url).origin; + + return (response.products as unknown as LegacyProduct[]).map((product) => + toProduct( + product, + product.items.find(getFirstItemAvailable) ?? product.items[0], + 0, + { + baseUrl, + priceCurrency: segment?.payload?.currencyCode ?? "BRL", + isVariantOfAdditionalProperty: [ + { + "@type": "PropertyValue", + name: "correlationId", + value: response.correlationId, + valueReference: "RECOMMENDATION", + }, + { + "@type": "PropertyValue", + name: "campaignId", + value: response.campaign?.id, + valueReference: "RECOMMENDATION", + }, + { + "@type": "PropertyValue", + name: "campaignTitle", + value: response.campaign?.title, + valueReference: "RECOMMENDATION", + }, + { + "@type": "PropertyValue", + name: "campaignType", + value: response.campaign?.type, + valueReference: "RECOMMENDATION", + }, + ], + }, + ) + ); + } catch (error) { + console.error(error); + return null; + } +} diff --git a/vtex/utils/recommendations.ts b/vtex/utils/recommendations.ts new file mode 100644 index 000000000..bd150ae4c --- /dev/null +++ b/vtex/utils/recommendations.ts @@ -0,0 +1,25 @@ +import { getCookies } from "std/http/cookie.ts"; + +/** + * @description Get the "x-vtex-rec-origin" header of the recommendation request. + * @param req - The request object. + * @param fallback - The fallback "x-vtex-rec-origin" header. + * @returns The "x-vtex-rec-origin" header of the recommendation request. + */ +export function getOrigin(req: Request, account: string, fallback?: string) { + const origin = req.headers.get("x-vtex-rec-origin"); + return origin || fallback || `${account}/storefront/deco.recommendations@1.x`; +} + +const VTEX_RECOMMENDATIONS_COOKIE = "vtex-rec-user-id"; + +export function parseCookie(headers: Headers) { + const cookies = getCookies(headers); + const userId = cookies[VTEX_RECOMMENDATIONS_COOKIE]; + if (!userId) { + return null; + } + return { + userId: userId, + }; +} From ca9d67becdd6d16c103b8b267f47d8ffab349017 Mon Sep 17 00:00:00 2001 From: vitoUwu Date: Tue, 24 Feb 2026 19:18:19 -0400 Subject: [PATCH 06/12] feat: product id list helpers --- .../recommendations/productListFromCart.ts | 23 +++++++++++++++ .../recommendations/productListFromPage.ts | 29 +++++++++++++++++++ 2 files changed, 52 insertions(+) create mode 100644 vtex/loaders/recommendations/productListFromCart.ts create mode 100644 vtex/loaders/recommendations/productListFromPage.ts diff --git a/vtex/loaders/recommendations/productListFromCart.ts b/vtex/loaders/recommendations/productListFromCart.ts new file mode 100644 index 000000000..51f073677 --- /dev/null +++ b/vtex/loaders/recommendations/productListFromCart.ts @@ -0,0 +1,23 @@ +import type { AppContext } from "../../mod.ts"; +import type { ProductListToId } from "../../utils/types.ts"; + +function isUnique(item: T, index: number, array: T[]): boolean { + return array.indexOf(item) === index; +} + +/** + * @title Product IDs from Cart + * @description Get product IDs from the user's cart + */ +export default async function loader( + _: unknown, + _req: Request, + ctx: AppContext, +): Promise { + const cart = await ctx.invoke.vtex.loaders.cart(); + if (!cart || !cart.items.length) { + return []; + } + + return cart.items.map(({ productId }) => productId).filter(isUnique); +} diff --git a/vtex/loaders/recommendations/productListFromPage.ts b/vtex/loaders/recommendations/productListFromPage.ts new file mode 100644 index 000000000..254318a75 --- /dev/null +++ b/vtex/loaders/recommendations/productListFromPage.ts @@ -0,0 +1,29 @@ +import type { ProductDetailsPage } from "../../../commerce/types.ts"; +import type { AppContext } from "../../mod.ts"; +import type { ProductListToId } from "../../utils/types.ts"; + +interface Props { + /** + * @title Page + */ + page: ProductDetailsPage | null; +} + +/** + * @title Product ID from Page + * @description Get product ID from the page + */ +export default function loader( + props: Props, + _req: Request, + _ctx: AppContext, +): ProductListToId { + const { page } = props; + const id = page?.product?.inProductGroupWithID; + + if (!id) { + return []; + } + + return [id]; +} From fd5cbcdf78c3b722e995be6998fec2a8c4162a25 Mon Sep 17 00:00:00 2001 From: vitoUwu Date: Tue, 24 Feb 2026 19:18:36 -0400 Subject: [PATCH 07/12] chore: manifest --- vtex/manifest.gen.ts | 98 +++++++++++++++++++++++++------------------- 1 file changed, 56 insertions(+), 42 deletions(-) diff --git a/vtex/manifest.gen.ts b/vtex/manifest.gen.ts index 1105a54b4..3af05cc20 100644 --- a/vtex/manifest.gen.ts +++ b/vtex/manifest.gen.ts @@ -30,21 +30,25 @@ import * as $$$$$$$$$24 from "./actions/cart/updateItemPrice.ts"; import * as $$$$$$$$$25 from "./actions/cart/updateItems.ts"; import * as $$$$$$$$$26 from "./actions/cart/updateProfile.ts"; import * as $$$$$$$$$27 from "./actions/cart/updateUser.ts"; -import * as $$$$$$$$$28 from "./actions/masterdata/createDocument.ts"; -import * as $$$$$$$$$29 from "./actions/masterdata/updateDocument.ts"; -import * as $$$$$$$$$30 from "./actions/newsletter/subscribe.ts"; -import * as $$$$$$$$$31 from "./actions/newsletter/updateNewsletterOptIn.ts"; -import * as $$$$$$$$$32 from "./actions/notifyme.ts"; -import * as $$$$$$$$$33 from "./actions/orders/cancel.ts"; -import * as $$$$$$$$$34 from "./actions/payment/deletePaymentToken.ts"; -import * as $$$$$$$$$35 from "./actions/profile/updateProfile.ts"; -import * as $$$$$$$$$36 from "./actions/review/submit.ts"; -import * as $$$$$$$$$37 from "./actions/session/createSession.ts"; -import * as $$$$$$$$$38 from "./actions/session/deleteSession.ts"; -import * as $$$$$$$$$39 from "./actions/session/editSession.ts"; -import * as $$$$$$$$$40 from "./actions/trigger.ts"; -import * as $$$$$$$$$41 from "./actions/wishlist/addItem.ts"; -import * as $$$$$$$$$42 from "./actions/wishlist/removeItem.ts"; +import * as $$$$$$$$$28 from "./actions/events/productView.ts"; +import * as $$$$$$$$$29 from "./actions/events/recommendationClick.ts"; +import * as $$$$$$$$$30 from "./actions/events/recommendationView.ts"; +import * as $$$$$$$$$31 from "./actions/masterdata/createDocument.ts"; +import * as $$$$$$$$$32 from "./actions/masterdata/updateDocument.ts"; +import * as $$$$$$$$$33 from "./actions/newsletter/subscribe.ts"; +import * as $$$$$$$$$34 from "./actions/newsletter/updateNewsletterOptIn.ts"; +import * as $$$$$$$$$35 from "./actions/notifyme.ts"; +import * as $$$$$$$$$36 from "./actions/orders/cancel.ts"; +import * as $$$$$$$$$37 from "./actions/payment/deletePaymentToken.ts"; +import * as $$$$$$$$$38 from "./actions/profile/updateProfile.ts"; +import * as $$$$$$$$$39 from "./actions/recommendation/startSession.ts"; +import * as $$$$$$$$$40 from "./actions/review/submit.ts"; +import * as $$$$$$$$$41 from "./actions/session/createSession.ts"; +import * as $$$$$$$$$42 from "./actions/session/deleteSession.ts"; +import * as $$$$$$$$$43 from "./actions/session/editSession.ts"; +import * as $$$$$$$$$44 from "./actions/trigger.ts"; +import * as $$$$$$$$$45 from "./actions/wishlist/addItem.ts"; +import * as $$$$$$$$$46 from "./actions/wishlist/removeItem.ts"; import * as $$$$0 from "./handlers/sitemap.ts"; import * as $$$0 from "./loaders/address/getAddressByPostalCode.ts"; import * as $$$1 from "./loaders/address/getUserAddresses.ts"; @@ -90,12 +94,15 @@ import * as $$$40 from "./loaders/profile/getCurrentProfile.ts"; import * as $$$41 from "./loaders/profile/getProfileByEmail.ts"; import * as $$$42 from "./loaders/promotion/getPromotionById.ts"; import * as $$$43 from "./loaders/proxy.ts"; -import * as $$$44 from "./loaders/session/getSession.ts"; -import * as $$$45 from "./loaders/session/getUserSessions.ts"; -import * as $$$46 from "./loaders/user.ts"; -import * as $$$47 from "./loaders/wishlist.ts"; -import * as $$$48 from "./loaders/workflow/product.ts"; -import * as $$$49 from "./loaders/workflow/products.ts"; +import * as $$$44 from "./loaders/recommendations/productList.ts"; +import * as $$$45 from "./loaders/recommendations/productListFromCart.ts"; +import * as $$$46 from "./loaders/recommendations/productListFromPage.ts"; +import * as $$$47 from "./loaders/session/getSession.ts"; +import * as $$$48 from "./loaders/session/getUserSessions.ts"; +import * as $$$49 from "./loaders/user.ts"; +import * as $$$50 from "./loaders/wishlist.ts"; +import * as $$$51 from "./loaders/workflow/product.ts"; +import * as $$$52 from "./loaders/workflow/products.ts"; import * as $$$$$$0 from "./sections/Analytics/Vtex.tsx"; import * as $$$$$$$$$$0 from "./workflows/events.ts"; import * as $$$$$$$$$$1 from "./workflows/product/index.ts"; @@ -146,12 +153,15 @@ const manifest = { "vtex/loaders/profile/getProfileByEmail.ts": $$$41, "vtex/loaders/promotion/getPromotionById.ts": $$$42, "vtex/loaders/proxy.ts": $$$43, - "vtex/loaders/session/getSession.ts": $$$44, - "vtex/loaders/session/getUserSessions.ts": $$$45, - "vtex/loaders/user.ts": $$$46, - "vtex/loaders/wishlist.ts": $$$47, - "vtex/loaders/workflow/product.ts": $$$48, - "vtex/loaders/workflow/products.ts": $$$49, + "vtex/loaders/recommendations/productList.ts": $$$44, + "vtex/loaders/recommendations/productListFromCart.ts": $$$45, + "vtex/loaders/recommendations/productListFromPage.ts": $$$46, + "vtex/loaders/session/getSession.ts": $$$47, + "vtex/loaders/session/getUserSessions.ts": $$$48, + "vtex/loaders/user.ts": $$$49, + "vtex/loaders/wishlist.ts": $$$50, + "vtex/loaders/workflow/product.ts": $$$51, + "vtex/loaders/workflow/products.ts": $$$52, }, "handlers": { "vtex/handlers/sitemap.ts": $$$$0, @@ -188,21 +198,25 @@ const manifest = { "vtex/actions/cart/updateItems.ts": $$$$$$$$$25, "vtex/actions/cart/updateProfile.ts": $$$$$$$$$26, "vtex/actions/cart/updateUser.ts": $$$$$$$$$27, - "vtex/actions/masterdata/createDocument.ts": $$$$$$$$$28, - "vtex/actions/masterdata/updateDocument.ts": $$$$$$$$$29, - "vtex/actions/newsletter/subscribe.ts": $$$$$$$$$30, - "vtex/actions/newsletter/updateNewsletterOptIn.ts": $$$$$$$$$31, - "vtex/actions/notifyme.ts": $$$$$$$$$32, - "vtex/actions/orders/cancel.ts": $$$$$$$$$33, - "vtex/actions/payment/deletePaymentToken.ts": $$$$$$$$$34, - "vtex/actions/profile/updateProfile.ts": $$$$$$$$$35, - "vtex/actions/review/submit.ts": $$$$$$$$$36, - "vtex/actions/session/createSession.ts": $$$$$$$$$37, - "vtex/actions/session/deleteSession.ts": $$$$$$$$$38, - "vtex/actions/session/editSession.ts": $$$$$$$$$39, - "vtex/actions/trigger.ts": $$$$$$$$$40, - "vtex/actions/wishlist/addItem.ts": $$$$$$$$$41, - "vtex/actions/wishlist/removeItem.ts": $$$$$$$$$42, + "vtex/actions/events/productView.ts": $$$$$$$$$28, + "vtex/actions/events/recommendationClick.ts": $$$$$$$$$29, + "vtex/actions/events/recommendationView.ts": $$$$$$$$$30, + "vtex/actions/masterdata/createDocument.ts": $$$$$$$$$31, + "vtex/actions/masterdata/updateDocument.ts": $$$$$$$$$32, + "vtex/actions/newsletter/subscribe.ts": $$$$$$$$$33, + "vtex/actions/newsletter/updateNewsletterOptIn.ts": $$$$$$$$$34, + "vtex/actions/notifyme.ts": $$$$$$$$$35, + "vtex/actions/orders/cancel.ts": $$$$$$$$$36, + "vtex/actions/payment/deletePaymentToken.ts": $$$$$$$$$37, + "vtex/actions/profile/updateProfile.ts": $$$$$$$$$38, + "vtex/actions/recommendation/startSession.ts": $$$$$$$$$39, + "vtex/actions/review/submit.ts": $$$$$$$$$40, + "vtex/actions/session/createSession.ts": $$$$$$$$$41, + "vtex/actions/session/deleteSession.ts": $$$$$$$$$42, + "vtex/actions/session/editSession.ts": $$$$$$$$$43, + "vtex/actions/trigger.ts": $$$$$$$$$44, + "vtex/actions/wishlist/addItem.ts": $$$$$$$$$45, + "vtex/actions/wishlist/removeItem.ts": $$$$$$$$$46, }, "workflows": { "vtex/workflows/events.ts": $$$$$$$$$$0, From 2811c9d63130563e575f7d01e1acb56262887cad Mon Sep 17 00:00:00 2001 From: vitoUwu Date: Thu, 12 Mar 2026 12:06:30 -0400 Subject: [PATCH 08/12] feat: add update orderform custom data --- vtex/actions/cart/updateCustomData.ts | 56 ++++++++++++++ vtex/manifest.gen.ts | 102 +++++++++++++------------- vtex/utils/client.ts | 6 ++ 3 files changed, 114 insertions(+), 50 deletions(-) create mode 100644 vtex/actions/cart/updateCustomData.ts diff --git a/vtex/actions/cart/updateCustomData.ts b/vtex/actions/cart/updateCustomData.ts new file mode 100644 index 000000000..5e37ffb42 --- /dev/null +++ b/vtex/actions/cart/updateCustomData.ts @@ -0,0 +1,56 @@ +import { AppContext } from "../../mod.ts"; +import { proxySetCookie } from "../../utils/cookies.ts"; +import { parseCookie } from "../../utils/orderForm.ts"; +import type { OrderForm } from "../../utils/types.ts"; +import { DEFAULT_EXPECTED_SECTIONS } from "./updateItemAttachment.ts"; +import { getSegmentFromBag } from "../../utils/segment.ts"; + +export interface Props { + appId: string; + expectedOrderFormSections?: string[]; + // deno-lint-ignore no-explicit-any + body: any; +} + +/** + * @title Update Attachment + * @description Update an attachment in the cart + */ +const action = async ( + props: Props, + req: Request, + ctx: AppContext, +): Promise => { + const { vcsDeprecated } = ctx; + const { + appId, + body, + expectedOrderFormSections = DEFAULT_EXPECTED_SECTIONS, + } = props; + const { orderFormId } = parseCookie(req.headers); + + if (!orderFormId || orderFormId === "") { + throw new Error("Order form ID is required"); + } + + const cookie = req.headers.get("cookie") ?? ""; + + const response = await vcsDeprecated + ["PUT /api/checkout/pub/orderForm/:orderFormId/customData/:appId"]({ + orderFormId, + appId, + }, { + body: { expectedOrderFormSections, ...body }, + headers: { + accept: "application/json", + "content-type": "application/json", + cookie, + }, + }); + + proxySetCookie(response.headers, ctx.response.headers, req.url); + + return response.json(); +}; + +export default action; diff --git a/vtex/manifest.gen.ts b/vtex/manifest.gen.ts index 3af05cc20..71ff26943 100644 --- a/vtex/manifest.gen.ts +++ b/vtex/manifest.gen.ts @@ -24,31 +24,32 @@ import * as $$$$$$$$$18 from "./actions/cart/removeOffering.ts"; import * as $$$$$$$$$19 from "./actions/cart/simulation.ts"; import * as $$$$$$$$$20 from "./actions/cart/updateAttachment.ts"; import * as $$$$$$$$$21 from "./actions/cart/updateCoupons.ts"; -import * as $$$$$$$$$22 from "./actions/cart/updateGifts.ts"; -import * as $$$$$$$$$23 from "./actions/cart/updateItemAttachment.ts"; -import * as $$$$$$$$$24 from "./actions/cart/updateItemPrice.ts"; -import * as $$$$$$$$$25 from "./actions/cart/updateItems.ts"; -import * as $$$$$$$$$26 from "./actions/cart/updateProfile.ts"; -import * as $$$$$$$$$27 from "./actions/cart/updateUser.ts"; -import * as $$$$$$$$$28 from "./actions/events/productView.ts"; -import * as $$$$$$$$$29 from "./actions/events/recommendationClick.ts"; -import * as $$$$$$$$$30 from "./actions/events/recommendationView.ts"; -import * as $$$$$$$$$31 from "./actions/masterdata/createDocument.ts"; -import * as $$$$$$$$$32 from "./actions/masterdata/updateDocument.ts"; -import * as $$$$$$$$$33 from "./actions/newsletter/subscribe.ts"; -import * as $$$$$$$$$34 from "./actions/newsletter/updateNewsletterOptIn.ts"; -import * as $$$$$$$$$35 from "./actions/notifyme.ts"; -import * as $$$$$$$$$36 from "./actions/orders/cancel.ts"; -import * as $$$$$$$$$37 from "./actions/payment/deletePaymentToken.ts"; -import * as $$$$$$$$$38 from "./actions/profile/updateProfile.ts"; -import * as $$$$$$$$$39 from "./actions/recommendation/startSession.ts"; -import * as $$$$$$$$$40 from "./actions/review/submit.ts"; -import * as $$$$$$$$$41 from "./actions/session/createSession.ts"; -import * as $$$$$$$$$42 from "./actions/session/deleteSession.ts"; -import * as $$$$$$$$$43 from "./actions/session/editSession.ts"; -import * as $$$$$$$$$44 from "./actions/trigger.ts"; -import * as $$$$$$$$$45 from "./actions/wishlist/addItem.ts"; -import * as $$$$$$$$$46 from "./actions/wishlist/removeItem.ts"; +import * as $$$$$$$$$22 from "./actions/cart/updateCustomData.ts"; +import * as $$$$$$$$$23 from "./actions/cart/updateGifts.ts"; +import * as $$$$$$$$$24 from "./actions/cart/updateItemAttachment.ts"; +import * as $$$$$$$$$25 from "./actions/cart/updateItemPrice.ts"; +import * as $$$$$$$$$26 from "./actions/cart/updateItems.ts"; +import * as $$$$$$$$$27 from "./actions/cart/updateProfile.ts"; +import * as $$$$$$$$$28 from "./actions/cart/updateUser.ts"; +import * as $$$$$$$$$29 from "./actions/events/productView.ts"; +import * as $$$$$$$$$30 from "./actions/events/recommendationClick.ts"; +import * as $$$$$$$$$31 from "./actions/events/recommendationView.ts"; +import * as $$$$$$$$$32 from "./actions/masterdata/createDocument.ts"; +import * as $$$$$$$$$33 from "./actions/masterdata/updateDocument.ts"; +import * as $$$$$$$$$34 from "./actions/newsletter/subscribe.ts"; +import * as $$$$$$$$$35 from "./actions/newsletter/updateNewsletterOptIn.ts"; +import * as $$$$$$$$$36 from "./actions/notifyme.ts"; +import * as $$$$$$$$$37 from "./actions/orders/cancel.ts"; +import * as $$$$$$$$$38 from "./actions/payment/deletePaymentToken.ts"; +import * as $$$$$$$$$39 from "./actions/profile/updateProfile.ts"; +import * as $$$$$$$$$40 from "./actions/recommendation/startSession.ts"; +import * as $$$$$$$$$41 from "./actions/review/submit.ts"; +import * as $$$$$$$$$42 from "./actions/session/createSession.ts"; +import * as $$$$$$$$$43 from "./actions/session/deleteSession.ts"; +import * as $$$$$$$$$44 from "./actions/session/editSession.ts"; +import * as $$$$$$$$$45 from "./actions/trigger.ts"; +import * as $$$$$$$$$46 from "./actions/wishlist/addItem.ts"; +import * as $$$$$$$$$47 from "./actions/wishlist/removeItem.ts"; import * as $$$$0 from "./handlers/sitemap.ts"; import * as $$$0 from "./loaders/address/getAddressByPostalCode.ts"; import * as $$$1 from "./loaders/address/getUserAddresses.ts"; @@ -192,31 +193,32 @@ const manifest = { "vtex/actions/cart/simulation.ts": $$$$$$$$$19, "vtex/actions/cart/updateAttachment.ts": $$$$$$$$$20, "vtex/actions/cart/updateCoupons.ts": $$$$$$$$$21, - "vtex/actions/cart/updateGifts.ts": $$$$$$$$$22, - "vtex/actions/cart/updateItemAttachment.ts": $$$$$$$$$23, - "vtex/actions/cart/updateItemPrice.ts": $$$$$$$$$24, - "vtex/actions/cart/updateItems.ts": $$$$$$$$$25, - "vtex/actions/cart/updateProfile.ts": $$$$$$$$$26, - "vtex/actions/cart/updateUser.ts": $$$$$$$$$27, - "vtex/actions/events/productView.ts": $$$$$$$$$28, - "vtex/actions/events/recommendationClick.ts": $$$$$$$$$29, - "vtex/actions/events/recommendationView.ts": $$$$$$$$$30, - "vtex/actions/masterdata/createDocument.ts": $$$$$$$$$31, - "vtex/actions/masterdata/updateDocument.ts": $$$$$$$$$32, - "vtex/actions/newsletter/subscribe.ts": $$$$$$$$$33, - "vtex/actions/newsletter/updateNewsletterOptIn.ts": $$$$$$$$$34, - "vtex/actions/notifyme.ts": $$$$$$$$$35, - "vtex/actions/orders/cancel.ts": $$$$$$$$$36, - "vtex/actions/payment/deletePaymentToken.ts": $$$$$$$$$37, - "vtex/actions/profile/updateProfile.ts": $$$$$$$$$38, - "vtex/actions/recommendation/startSession.ts": $$$$$$$$$39, - "vtex/actions/review/submit.ts": $$$$$$$$$40, - "vtex/actions/session/createSession.ts": $$$$$$$$$41, - "vtex/actions/session/deleteSession.ts": $$$$$$$$$42, - "vtex/actions/session/editSession.ts": $$$$$$$$$43, - "vtex/actions/trigger.ts": $$$$$$$$$44, - "vtex/actions/wishlist/addItem.ts": $$$$$$$$$45, - "vtex/actions/wishlist/removeItem.ts": $$$$$$$$$46, + "vtex/actions/cart/updateCustomData.ts": $$$$$$$$$22, + "vtex/actions/cart/updateGifts.ts": $$$$$$$$$23, + "vtex/actions/cart/updateItemAttachment.ts": $$$$$$$$$24, + "vtex/actions/cart/updateItemPrice.ts": $$$$$$$$$25, + "vtex/actions/cart/updateItems.ts": $$$$$$$$$26, + "vtex/actions/cart/updateProfile.ts": $$$$$$$$$27, + "vtex/actions/cart/updateUser.ts": $$$$$$$$$28, + "vtex/actions/events/productView.ts": $$$$$$$$$29, + "vtex/actions/events/recommendationClick.ts": $$$$$$$$$30, + "vtex/actions/events/recommendationView.ts": $$$$$$$$$31, + "vtex/actions/masterdata/createDocument.ts": $$$$$$$$$32, + "vtex/actions/masterdata/updateDocument.ts": $$$$$$$$$33, + "vtex/actions/newsletter/subscribe.ts": $$$$$$$$$34, + "vtex/actions/newsletter/updateNewsletterOptIn.ts": $$$$$$$$$35, + "vtex/actions/notifyme.ts": $$$$$$$$$36, + "vtex/actions/orders/cancel.ts": $$$$$$$$$37, + "vtex/actions/payment/deletePaymentToken.ts": $$$$$$$$$38, + "vtex/actions/profile/updateProfile.ts": $$$$$$$$$39, + "vtex/actions/recommendation/startSession.ts": $$$$$$$$$40, + "vtex/actions/review/submit.ts": $$$$$$$$$41, + "vtex/actions/session/createSession.ts": $$$$$$$$$42, + "vtex/actions/session/deleteSession.ts": $$$$$$$$$43, + "vtex/actions/session/editSession.ts": $$$$$$$$$44, + "vtex/actions/trigger.ts": $$$$$$$$$45, + "vtex/actions/wishlist/addItem.ts": $$$$$$$$$46, + "vtex/actions/wishlist/removeItem.ts": $$$$$$$$$47, }, "workflows": { "vtex/workflows/events.ts": $$$$$$$$$$0, diff --git a/vtex/utils/client.ts b/vtex/utils/client.ts index b485045ea..dd4970dd5 100644 --- a/vtex/utils/client.ts +++ b/vtex/utils/client.ts @@ -212,6 +212,12 @@ export interface VTEXCommerceStable { [x: string]: unknown; }; }; + "PUT /api/checkout/pub/orderForm/:orderFormId/customData/:appId": { + response: OrderForm; + body: { + [x: string]: unknown; + }; + }; "POST /api/checkout/pub/orderForm/:orderFormId/items": { response: OrderForm; searchParams: { From 25a5da782b7ebfd266ed6d28d3bd5951b0ce57e4 Mon Sep 17 00:00:00 2001 From: vitoUwu Date: Thu, 12 Mar 2026 12:16:44 -0400 Subject: [PATCH 09/12] fix: update custom data body and orderform type --- vtex/actions/cart/updateCustomData.ts | 10 +++------- vtex/utils/types.ts | 13 ++++++++++++- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/vtex/actions/cart/updateCustomData.ts b/vtex/actions/cart/updateCustomData.ts index 5e37ffb42..fa7a82154 100644 --- a/vtex/actions/cart/updateCustomData.ts +++ b/vtex/actions/cart/updateCustomData.ts @@ -2,19 +2,16 @@ import { AppContext } from "../../mod.ts"; import { proxySetCookie } from "../../utils/cookies.ts"; import { parseCookie } from "../../utils/orderForm.ts"; import type { OrderForm } from "../../utils/types.ts"; -import { DEFAULT_EXPECTED_SECTIONS } from "./updateItemAttachment.ts"; -import { getSegmentFromBag } from "../../utils/segment.ts"; export interface Props { appId: string; - expectedOrderFormSections?: string[]; // deno-lint-ignore no-explicit-any body: any; } /** - * @title Update Attachment - * @description Update an attachment in the cart + * @title Update Custom Data + * @description Update the custom data in the cart */ const action = async ( props: Props, @@ -25,7 +22,6 @@ const action = async ( const { appId, body, - expectedOrderFormSections = DEFAULT_EXPECTED_SECTIONS, } = props; const { orderFormId } = parseCookie(req.headers); @@ -40,7 +36,7 @@ const action = async ( orderFormId, appId, }, { - body: { expectedOrderFormSections, ...body }, + body, headers: { accept: "application/json", "content-type": "application/json", diff --git a/vtex/utils/types.ts b/vtex/utils/types.ts index edc7846b9..68aa62c34 100644 --- a/vtex/utils/types.ts +++ b/vtex/utils/types.ts @@ -33,7 +33,7 @@ export interface OrderForm { giftRegistryData: null; openTextField: null; invoiceData: null; - customData: null; + customData: CustomData | null; itemMetadata: ItemMetadata; hooksData: null; ratesAndBenefitsData: RatesAndBenefitsData; @@ -42,6 +42,17 @@ export interface OrderForm { itemsOrdination: null; } +export interface CustomApp { + fields: Record; + id: string; + major: number; +} + +export interface CustomData { + customApps: CustomApp[]; + customFields: unknown[]; +} + export interface ClientProfileData { email: string; firstName: null; From 9379bd109e118e9a05851272a6b988be5485661d Mon Sep 17 00:00:00 2001 From: vitoUwu Date: Thu, 12 Mar 2026 12:45:15 -0400 Subject: [PATCH 10/12] refactor: QoL change to recommendations cookie parsing --- vtex/utils/recommendations.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/vtex/utils/recommendations.ts b/vtex/utils/recommendations.ts index bd150ae4c..686e0a06a 100644 --- a/vtex/utils/recommendations.ts +++ b/vtex/utils/recommendations.ts @@ -17,9 +17,7 @@ export function parseCookie(headers: Headers) { const cookies = getCookies(headers); const userId = cookies[VTEX_RECOMMENDATIONS_COOKIE]; if (!userId) { - return null; + return { userId: undefined }; } - return { - userId: userId, - }; + return { userId }; } From 30661af44ae7993dc421456c94ba3cb54d273b81 Mon Sep 17 00:00:00 2001 From: vitoUwu Date: Thu, 12 Mar 2026 12:45:51 -0400 Subject: [PATCH 11/12] refactor: avoid calling start-session when the session is already started --- vtex/actions/recommendation/startSession.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/vtex/actions/recommendation/startSession.ts b/vtex/actions/recommendation/startSession.ts index 4d57ca93a..3bc7313d4 100644 --- a/vtex/actions/recommendation/startSession.ts +++ b/vtex/actions/recommendation/startSession.ts @@ -1,7 +1,7 @@ -import { getSetCookies } from "std/http/cookie.ts"; import type { AppContext } from "../../mod.ts"; import { proxySetCookie } from "../../utils/cookies.ts"; -import { parseCookie } from "../../utils/orderForm.ts"; +import { parseCookie as parseOrderformCookie } from "../../utils/orderForm.ts"; +import { parseCookie as parseRecommendationsCookie } from "../../utils/recommendations.ts"; export default async function action( _: unknown, @@ -9,7 +9,11 @@ export default async function action( ctx: AppContext, ) { const { bff } = ctx; - const { orderFormId } = parseCookie(req.headers); + const { orderFormId } = parseOrderformCookie(req.headers); + const { userId } = parseRecommendationsCookie(req.headers); + if (userId) { + return { recommendationsUserId: userId }; + } const url = new URL(req.url); const host = url.host; @@ -36,7 +40,6 @@ export default async function action( const data = await response.json(); - console.log(data, getSetCookies(response.headers)); proxySetCookie(response.headers, ctx.response.headers, req.url); return data; From c8840f511f91b4ee04d468a2a3361991d5ef8790 Mon Sep 17 00:00:00 2001 From: vitoUwu Date: Thu, 12 Mar 2026 12:46:27 -0400 Subject: [PATCH 12/12] refactor: include request cookies for sanity check --- vtex/actions/events/productView.ts | 3 ++- vtex/loaders/recommendations/productList.ts | 20 ++++++++++++-------- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/vtex/actions/events/productView.ts b/vtex/actions/events/productView.ts index b01976003..fb587d69f 100644 --- a/vtex/actions/events/productView.ts +++ b/vtex/actions/events/productView.ts @@ -24,7 +24,7 @@ interface Props { "x-vtex-rec-origin"?: string; } -export default async function loader( +export default async function action( props: Props, req: Request, ctx: AppContext, @@ -54,6 +54,7 @@ export default async function loader( headers: { "x-vtex-rec-origin": origin, "user-agent": req.headers.get("user-agent") || "", + cookie: req.headers.get("cookie") || "", }, }); diff --git a/vtex/loaders/recommendations/productList.ts b/vtex/loaders/recommendations/productList.ts index b69073b9f..eb84983e3 100644 --- a/vtex/loaders/recommendations/productList.ts +++ b/vtex/loaders/recommendations/productList.ts @@ -1,7 +1,7 @@ import { Product } from "../../../commerce/types.ts"; import { AppContext } from "../../mod.ts"; import { getOrigin, parseCookie } from "../../utils/recommendations.ts"; -import { getSegmentFromBag, withSegmentCookie } from "../../utils/segment.ts"; +import { getSegmentFromBag } from "../../utils/segment.ts"; import { toProduct } from "../../utils/transform.ts"; import { CampaignType, @@ -86,9 +86,7 @@ export default async function loader( const origin = getOrigin(req, ctx.account, props["x-vtex-rec-origin"]); const segment = getSegmentFromBag(ctx); - const cookies = parseCookie(req.headers); - - let userId = cookies?.userId; + let { userId } = parseCookie(req.headers); if (ctx.advancedConfigs?.autoStartRecommendationSession && !userId) { const response = await ctx.invoke.vtex.actions.recommendation @@ -100,9 +98,12 @@ export default async function loader( const campaignVrn = `vrn:recommendations:${account}:${campaignType}:${props.campaignId}`; - // From vtex docs: - // "For similar items and cross-sell, send only one product ID. - // For cart recommendations, send all product IDs in the user's cart." + // + // https://developers.vtex.com/docs/api-reference/recommendations-bff-api#get-/api/recommend-bff/v2/recommendations + // + // For similar items and cross-sell, send only one product ID. + // For cart recommendations, send all product IDs in the user's cart. + // const products = campaignType === "rec-cross-v2" || campaignType === "rec-similar-v2" ? [props.products?.[0]] @@ -120,7 +121,10 @@ export default async function loader( products: productIds, salesChannel: segment?.payload?.channel || ctx.salesChannel, }, { - headers: { ...withSegmentCookie(segment), "x-vtex-rec-origin": origin }, + headers: { + cookie: req.headers.get("cookie") ?? "", + "x-vtex-rec-origin": origin, + }, }).then((res) => res.json()); const baseUrl = new URL(req.url).origin;