const myFeedGroup = await serverClient.feeds.createFeedGroup({
id: "myid",
activity_selectors: [
{
type: "following",
},
],
ranking: { type: "recency" },
activity_processors: [
{
type: "text_interest_tags",
},
],
custom: {
description: "My custom feed group",
},
});
Intro & Defaults
Creating Feed Groups & Defaults
There are several feed groups setup by default.
Group | Description |
---|---|
user | A feed setup for the content a user creates. Typically you add activities here when someone writes a post |
timeline | The timeline feed is used when you’re following. So if user Charlie is following John, timeline:charlie would follow user:john |
foryou | A version of the timeline feed that adds popular content, and priorities popularity over recency |
notification | A notification feed. Think of the bell icon you see in most apps |
You can also create your own feed group. Here’s how to create a feed group using the API:
ctx := context.Background()
myFeedGroup, err := client.Feeds().CreateFeedGroup(ctx, &getstream.CreateFeedGroupRequest{
ID: "myid",
ActivitySelectors: []getstream.ActivitySelectorConfig{
{
Type: getstream.PtrTo("following"),
},
},
Ranking: &getstream.RankingConfig{
Type: getstream.PtrTo("recency"),
},
ActivityProcessors: []getstream.ActivityProcessorConfig{
{
Type: "text_interest_tags",
},
},
Custom: map[string]any{
"description": "My custom feed group",
},
})
if err != nil {
log.Fatal("Error creating feed group:", err)
}
fmt.Printf("Feed group created successfully: %+v\n", myFeedGroup.Data.FeedGroup)
Activity Ranking
When ranking activities you can specify a ranking formula.
const response = await serverClient.feeds.createFeedGroup({
id: "mytimeline",
ranking: { type: "expression", score: "decay_linear(time) * popularity" },
activity_selectors: [
{
type: "following",
},
],
});
ctx := context.Background()
createFeedGroupRequest := &getstream.CreateFeedGroupRequest{
ID: "mytimeline",
Ranking: &getstream.RankingConfig{
Type: getstream.PtrTo("expression"),
Score: getstream.PtrTo("decay_linear(time) * popularity"),
},
ActivitySelectors: []getstream.ActivitySelectorConfig{
{
Type: getstream.PtrTo("following"),
},
},
}
// Create the feed group
response, err := client.Feeds().CreateFeedGroup(ctx, createFeedGroupRequest)
if err != nil {
log.Fatal("Error creating feed group:", err)
}
fmt.Printf("Feed group created successfully!\n")
fmt.Printf("Duration: %s\n", response.Data.Duration)
fmt.Printf("Feed Group ID: %s\n", response.Data.FeedGroup.ID)
$this->feedsV3Client->createFeedGroup(
new GeneratedModels\CreateFeedGroupRequest(
id: $ranked_group,
defaultVisibility: 'public',
ranking: new GeneratedModels\RankingConfig(
type: 'default',
score: 'decay_linear(time) * popularity'
)
)
);
await _feedsV3Client.CreateFeedGroupAsync(new CreateFeedGroupRequest
{
ID = "ranked-group",
DefaultVisibility = "public",
Ranking = new RankingConfig
{
Type = "default",
Score = "decay_linear(time) * popularity"
}
});
self.client.feeds.create_feed_group(
id=feed_group_id,
default_visibility="public",
ranking=RankingConfig(
type="default",
score="decay_linear(time) * popularity"
)
)
For you Feeds
Many apps want to have a “for you” or personalized feed. There are a couple benefits to a personalized feed:
- Works well even if your users don’t spend much time setting up follows
- Can be a mechanism to discover new content or things to follow
Stream offers a few built-in methods to create a for you feed and gives you the API access to do more advanced customization if needed.
This next example is a bit more complicated. It uses an activity processor to add topic data to activities and ranks content you’re likely to engage with higher in the feed.
const response = await serverClient.feeds.createFeedGroup({
id: "mytimeline",
// Run the activity processors to analyse topics for text & images
activity_processors: [
{ type: "text_interest_tags" },
{ type: "image_interest_tags" },
],
// Activity selectors change which activities are included in the feed
// The default "following" selectors gets activities from the feeds you follow
// The "popular" activity selectors includes the popular activities
// And "interest" activities similar to activities you've engaged with in the past
// You can use multiple selectors in 1 feed
activity_selectors: [
{
type: "popular",
},
{
type: "following",
},
{
type: "interest",
},
],
// Rank for a user based on interest score
// This calculates a score 0-1.0 of how well the activity matches the user's prior interest
ranking: {
type: "interest",
score: "decay_linear(time) * interest_score * decay_linear(popularity)",
},
});
const forYouFeed = client.feeds.feed("mytimeline", "thierry");
const response = await forYouFeed.getOrCreate({ user_id: "thierry" });
ctx := context.Background()
// Create feed group with activity processors, activity selectors, and ranking
createFeedGroupRequest := &getstream.CreateFeedGroupRequest{
ID: "mytimeline",
// Run the activity processors to analyse topics for text & images
ActivityProcessors: []getstream.ActivityProcessorConfig{
{Type: "text_interest_tags"},
{Type: "image_interest_tags"},
},
// Activity selectors change which activities are included in the feed
// The default "following" selectors gets activities from the feeds you follow
// The "popular" activity selectors includes the popular activities
// And "interest" activities similar to activities you've engaged with in the past
// You can use multiple selectors in 1 feed
ActivitySelectors: []getstream.ActivitySelectorConfig{
{
Type: getstream.PtrTo("popular"),
},
{
Type: getstream.PtrTo("following"),
},
{
Type: getstream.PtrTo("interest"),
},
},
// Rank for a user based on interest score
// This calculates a score 0-1.0 of how well the activity matches the user's prior interest
Ranking: &getstream.RankingConfig{
Type: getstream.PtrTo("interest"),
Score: getstream.PtrTo("decay_linear(time) * interest_score * decay_linear(popularity)"),
},
}
// Create the feed group
response, err := client.Feeds().CreateFeedGroup(ctx, createFeedGroupRequest)
if err != nil {
log.Fatal("Error creating feed group:", err)
}
fmt.Printf("Feed group created successfully!\n")
fmt.Printf("Duration: %s\n", response.Data.Duration)
fmt.Printf("Feed Group ID: %s\n", response.Data.FeedGroup.ID)
// Create and get the feed for a specific user
forYouFeed := client.Feeds().Feed("mytimeline", "thierry")
feedResponse, err := forYouFeed.GetOrCreate(ctx, &getstream.GetOrCreateFeedRequest{
UserID: getstream.PtrTo("thierry"),
})
if err != nil {
log.Fatal("Error getting or creating feed:", err)
}
fmt.Printf("Feed created/retrieved successfully!\n")
fmt.Printf("Feed ID: %s\n", feedResponse.Data.Feed.ID)
fmt.Printf("Feed Group: %s\n", feedResponse.Data.Feed.GroupID)
Aggregation & Notification Feeds
Aggregation groups similar activities together, which is useful for notification feeds and reducing noise.
Aggregation Format
You can create your own aggregated feeds:
const myNotificationGrpup = await serverClient.feeds.createFeedGroup({
id: "myid",
// Group by activity type and day
aggregation: { format: '{{ type }}-{{ time.strftime("%Y-%m-%d") }}' },
// Enable notification tracking
notification: {
track_read: true,
track_seen: true,
},
});
myNotificationGroup, err := client.Feeds().CreateFeedGroup(context.Background(), &getstream.CreateFeedGroupRequest{
ID: "myid2",
// Group by activity type and day
Aggregation: &getstream.AggregationConfig{
Format: getstream.PtrTo("{{ type }}-{{ time.strftime(\"%Y-%m-%d\") }}"),
},
// Enable notification tracking
Notification: &getstream.NotificationConfig{
TrackRead: getstream.PtrTo(true),
TrackSeen: getstream.PtrTo(true),
},
})
if err != nil {
log.Fatal("Error creating feed group:", err)
}
$this->feedsV3Client->createFeedGroup(
new GeneratedModels\CreateFeedGroupRequest(
id: $group,
defaultVisibility: 'public',
activityProcessors: [
['type' => 'default']
],
aggregation: new GeneratedModels\AggregationConfig('{{ type }}-{{ time.strftime("%Y-%m-%d") }}')
)
);
await _feedsV3Client.CreateFeedGroupAsync(new CreateFeedGroupRequest
{
ID = "aggregated-group",
DefaultVisibility = "public",
ActivityProcessors = new List<ActivityProcessorConfig>
{
new() { Type = "default" }
},
Aggregation = new AggregationConfig
{
Format = "{{ type }}-{{ time.strftime(\"%Y-%m-%d\") }}"
}
});
self.client.feeds.create_feed_group(
id = feed_group_id,
default_visibility="public",
activity_processors=[
ActivityProcessorConfig(type="default")
],
aggregation=AggregationConfig(
format="{{ type }}-{{ time.strftime(\"%Y-%m-%d\") }}"
)
)
Notification Feed Example
The built-in notification
feed comes with the necessary configurations:
let notificationFeed = client.feed(group: "notification", id: "jane")
let notifications = try await notificationFeed.getOrCreate()
val notificationFeed = client.feed(group = "notification", id = "jane")
val notifications = notificationFeed.getOrCreate()
const notificationFeed = client.feed("notification", "jane");
// Read notifications
const notifications = (
await notificationFeed.getOrCreate({
limit: 20,
})
).aggregated_activities;
final notificationFeed = client.feed(group: 'notification', id: 'john');
await notificationFeed.getOrCreate();
const notificationFeed = client.feed("notification", "jane");
// Read notifications
const notifications = (
await notificationFeed.getOrCreate({
limit: 20,
user_id: "<user_id>",
})
).aggregated_activities;
notificationFeed := client.Feeds().Feed("notification", "john")
// Read notifications
response, err := notificationFeed.GetOrCreate(context.Background(), &getstream.GetOrCreateFeedRequest{
Limit: getstream.PtrTo(20),
UserID: getstream.PtrTo("john"),
})
if err != nil {
log.Fatal(err)
}
log.Printf("Response: %+v\n", response.Data.AggregatedActivities)
testFeed = new Feed("user", testUserId, feeds);
testFeed2 = new Feed("user", testUserId2, feeds);
GetOrCreateFeedRequest feedRequest1 =
GetOrCreateFeedRequest.builder().userID(testUserId).build();
GetOrCreateFeedRequest feedRequest2 =
GetOrCreateFeedRequest.builder().userID(testUserId2).build();
GetOrCreateFeedResponse feedResponse1 = testFeed.getOrCreate(feedRequest1).getData();
GetOrCreateFeedResponse feedResponse2 = testFeed2.getOrCreate(feedRequest2).getData();
testFeedId = feedResponse1.getFeed().getFeed();
testFeedId2 = feedResponse2.getFeed().getFeed();
$feedResponse1 = $this->testFeed->getOrCreateFeed(
new GeneratedModels\GetOrCreateFeedRequest(userID: $this->testUserId)
);
$feedResponse2 = $this->testFeed2->getOrCreateFeed(
new GeneratedModels\GetOrCreateFeedRequest(userID: $this->testUserId2)
);
var feedResponse1 = await _feedsV3Client.GetOrCreateFeedAsync(
FeedGroupID: "user",
FeedID: _testFeedId,
request: new GetOrCreateFeedRequest { UserID = _testUserId }
);
var feedResponse2 = await _feedsV3Client.GetOrCreateFeedAsync(
FeedGroupID: "user",
FeedID: _testFeedId2,
request: new GetOrCreateFeedRequest { UserID = _testUserId2 }
);
feed_response_1 = self.test_feed.get_or_create(user_id=self.test_user_id)
feed_response_2 = self.test_feed_2.get_or_create(
user_id=self.test_user_id_2
)
Marking Notifications as Read
notificationFeed.markActivity(
request = MarkActivityRequest(
// Mark all notifications as read...
markAllRead = true,
// ...or only selected ones
markRead = listOf(
/* group names to mark as read */
)
)
)
await notificationFeed.markActivity({
// Mark all notifications as read...
mark_all_read: true,
// ...or only selected ones
mark_read: [
/* group names to mark as read */
],
});
await notificationFeed.markActivity(
request: const MarkActivityRequest(
// Mark all notifications as read...
markAllRead: true,
// ...or only selected ones
markRead: [
// group names to mark as read
],
),
);
await notificationFeed.markActivity({
// Mark all notifications as read...
mark_all_read: true,
// ...or only selected ones
mark_read: [
/* group names to mark as read */
],
user_id: "<user id>",
});
notificationFeed := client.Feeds().Feed("notification", "john")
_, err = notificationFeed.MarkActivity(context.Background(), &getstream.MarkActivityRequest{
// Mark all notifications as read...
MarkAllRead: getstream.PtrTo(true),
// ...or only selected ones
MarkRead: []string{
// group names to mark as read
},
UserID: getstream.PtrTo("john"),
})
if err != nil {
log.Fatal("Error:", err)
}
Updating feed groups
It’s possible to update any custom or built-in feed group or view
await client.feeds.updateFeedGroup({
id: "<id of feed group to update>",
// Fields to update
});
_, err = client.Feeds().UpdateFeedGroup(context.Background(), "myid", &getstream.UpdateFeedGroupRequest{
// Fields to update
})
if err != nil {
log.Fatal("Error updating feed group:", err)
}
Deleting feed groups
await serverClient.feeds.deleteFeedGroup({
id: "<feed group id to delete>",
});
_, err = client.Feeds().DeleteFeedGroup(context.Background(), "mytimeline", &getstream.DeleteFeedGroupRequest{})