Appearance
User Engagement
User Engagement lets you capture how a recognized shopper feels about every product, variant, and piece of content they interact with. Shoppers can explicitly favorite items (❤️) and assign sentiment (Like, Dislike, or Neutral), and Relewise remembers their choices so you can reflect them anywhere you display results.
Once tracked, the current engagement state can be returned inline in Search and Recommendation responses. That makes it straightforward to show filled hearts and thumb states on product listing pages, detail pages, content lists, or any Relewise-powered carousel. Ranking impact is opt-in and controlled through Merchandising or at the API request level through Filters and Relevance Modifiers when you decide it is appropriate.
Identify your user
User Engagement relies on the same user identifiers that you already send to Relewise for behavioral tracking. Temporary or Authenticated IDs ensure that favorites and sentiments can be persisted and retrieved for each shopper.
Viewing Engagements in My Relewise
You can inspect all tracked engagements directly in My Relewise. Search for a user, open the user information dialog, and switch to the Engagements tab to view every favorite and sentiment captured for that user.
Tracking Engagement Events
Engagement events are tracked with the Tracker API in exactly the same way as other behavioral events. Each event contains:
- The
Userwhose engagement should be recorded. - The entity identifier (
Product ID,Variant ID, orContent ID). - Engagement data describing whether the entity is favorited and/or which sentiment to store (
Like,Dislike, orNeutral).
ts
await tracker.trackProductEngagement({
user: UserFactory.byTemporaryId('user-id'),
product: {
productId: 'product-id',
},
engagement: {
isFavorite: true,
},
});csharp
await tracker.TrackAsync(new TrackProductEngagementRequest(
new ProductEngagement(
User.ByTemporaryId("user-id"),
new ProductAndVariantId("product-id"),
new ProductEngagement.Data(ProductEngagement.Data.SentimentKind.Like, isFavorite: true))));php
$request = TrackProductEngagementRequest::create(
ProductEngagement::create(
UserFactory::byTemporaryId("user-id"),
ProductAndVariantId::create("product-id"),
ProductEngagementData::create(
ProductEngagementData::SentimentKind::Like,
/* isFavorite: */ true)
)
);
$response = $tracker->trackProductEngagement($request);java
var request = TrackProductEngagementRequest.create(
ProductEngagement.create(
UserFactory.byTemporaryId("user-id"),
ProductAndVariantId.create("product-id"),
ProductEngagement.Data.create(
ProductEngagement.Data.SentimentKind.Like,
/* isFavorite */ true)
)
);
tracker.track(request);Use the corresponding ContentEngagement type to store favorites and sentiment for other non-product entities.
Requesting Engagement in Responses
User Engagement is an optional payload on each result. To have Search or Recommendation responses include the engagement state, enable the UserEngagement flag in the selected properties of your request settings.
ts
const builder = new ProductSearchBuilder(settings)
.setSelectedProductProperties({
displayName: true,
userEngagement: true,
})
.setTerm('shoe')
.pagination(p => p
.setPageSize(30)
.setPage(1));csharp
var request = new ProductSearchRequest(
new Language("da"),
new Currency("DKK"),
User.ByTemporaryId("user-id"),
"Search overlay",
"shoe",
skip: 0,
take: 30);
request.Settings.SelectedProductProperties = new SelectedProductPropertiesSettings
{
DisplayName = true,
UserEngagement = true
};
ProductSearchResponse response = await searcher.SearchAsync(request, cancellationToken);php
$request = ProductSearchRequest::create(
Language::create("da"),
Currency::create("DKK"),
UserFactory::byTemporaryId("user-id"),
"Search overlay",
"shoe",
0,
30
);
$request->setSettings(
ProductSearchSettings::create()
->setSelectedProductProperties(
SelectedProductPropertiesSettings::create()
->setDisplayName(true)
->setUserEngagement(true)
)
);
$response = $searcher->productSearch($request);java
var request = ProductSearchRequest.create(
Language.create("da"),
Currency.create("DKK"),
UserFactory.byTemporaryId("user-id"),
"Search overlay",
"shoe",
0,
30
).setSettings(ProductSearchSettings.create()
.setSelectedProductProperties(SelectedProductPropertiesSettings.create()
.setDisplayName(true)
.setUserEngagement(true)
)
);
searcher.search(request);The same flag exists for content responses through SelectedContentPropertiesSettings.
ts
const builder = new PersonalContentRecommendationBuilder(settings)
.setSelectedContentProperties({
displayName: true,
userEngagement: true,
})
.setNumberOfRecommendations(10);
await recommender.recommendPersonalContents(builder.build());csharp
var request = new PersonalContentRecommendationRequest(
new Language("da"),
new Currency("DKK"),
User.ByTemporaryId("user-id"),
"context"
);
request.Settings.SelectedContentProperties = new SelectedContentPropertiesSettings
{
DisplayName = true,
UserEngagement = true
};
ContentRecommendationResponse response = await recommender.RecommendAsync(request, cancellationToken);php
$request = PersonalContentRecommendationRequest::create(
Language::create("da"),
Currency::create("DKK"),
UserFactory::byTemporaryId("user-id"),
"context"
);
$request->setSettings(
PersonalContentRecommendationSettings::create()
->setSelectedContentProperties(
SelectedContentPropertiesSettings::create()
->setDisplayName(true)
->setUserEngagement(true)
)
);
$response = $recommender->recommendPersonalContents($request);java
var request = PersonalContentRecommendationRequest.create(
Language.create("da"),
Currency.create("DKK"),
UserFactory.byTemporaryId("user-id"),
"context"
).setSettings(
PersonalContentRecommendationSettings.create()
.setSelectedContentProperties(SelectedContentPropertiesSettings.create()
.setDisplayName(true)
.setUserEngagement(true)
)
).setNumberOfRecommendations(10);
recommender.recommendPersonalContents(request);Once included in responses, you can optionally apply request-level modifiers to boost or bury items based on engagement, or filter to the states that best match the experience you are building.
When enabled, each result contains a UserEngagement object:
| Field | Description |
|---|---|
IsFavorite | true if the shopper has marked the entity as a favorite. |
Sentiment | The shopper's latest sentiment (Like, Dislike, or Neutral). |
If the shopper has not expressed an opinion, the UserEngagement property will be null, and you can default to your standard UI.
With this in place, you can confidently surface hearts and thumbs anywhere in your experience, giving shoppers immediate feedback on their saved and rated items.
Implementing a Favorites Page
To build a dedicated Favorites Page for a user, you must retrieve only the products that the current user has explicitly marked as a favorite.
Keep in mind that anonymous users do not have a favorites list, so the API will fall back to returning popular products. Use the userIsAnonymous helper from the Relewise JavaScript SDK to guard for this.
This is achieved by using a standard Product Search request and applying the ProductEngagementFilter. This filter allows you to restrict results based on the shopper's stored engagement state.
The following code demonstrates how to filter a product search to return only the products favorited by the current user and return no results for anonymous users:
ts
if (userIsAnonymous(user)) {
return;
}
const builder = new ProductSearchBuilder(settings)
.setSelectedProductProperties({
displayName: true,
userEngagement: true,
})
.filters(f => f.addProductEngagementFilter({ isFavorite: true }));
await searcher.searchProducts(builder.build());csharp
var request = new ProductSearchRequest(
new Language("da"),
new Currency("DKK"),
User.ByTemporaryId("user-id"),
"Search overlay",
null,
skip: 0,
take: 10);
request.Settings.SelectedProductProperties = new SelectedProductPropertiesSettings
{
DisplayName = true,
UserEngagement = true
};
// Filter to only favorites for the current user
request.Filters.Add(new ProductEngagementFilter { IsFavorite = true });
ProductSearchResponse response = await searcher.SearchAsync(request, cancellationToken);php
$request = ProductSearchRequest::create(
Language::create("da"),
Currency::create("DKK"),
UserFactory::byTemporaryId("user-id"),
"Search overlay",
null,
0,
10);
$request->setSettings(
ProductSearchSettings::create()
->setSelectedProductProperties(
SelectedProductPropertiesSettings::create()
->setDisplayName(true)
->setUserEngagement(true)
)
);
// Filter to only favorites for the current user
$request->setFilters(FilterCollection::create(ProductEngagementFilter::create()->setIsFavorite(true)));
$response = $searcher->productSearch($request);java
var request = ProductSearchRequest.create(
Language.create("da"),
Currency.create("DKK"),
UserFactory.byTemporaryId("user-id"),
"Search overlay",
null,
0,
10)
.setSettings(ProductSearchSettings.create()
.setSelectedProductProperties(SelectedProductPropertiesSettings.create()
.setDisplayName(true)
.setUserEngagement(true)
)
);
request.setFilters(FilterCollection.create(ProductEngagementFilter.create().setIsFavorite(true)));
searcher.search(request);See the full implementation of this functionality in our Vue demo shop: Favorites.vue.