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
Feed groups are templates that define how different feeds behave in your application. The configuration options let you create feeds that work differently depending on your use case.
By adjusting settings like ranking, aggregation, activity selectors, and processors, you can tailor each feed group to serve specific purposes in your app.
Built-in groups
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 |
story | A feed set up for users to post story activities (activities with expiration data) |
stories | A timeline feed which can be used to follow other users’ stories. |
You can update the default feed group configurations or create your own feed groups.
Create feed groups
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)
use GetStream\GeneratedModels\CreateFeedGroupRequest;
use GetStream\GeneratedModels\ActivitySelectorConfig;
use GetStream\GeneratedModels\RankingConfig;
use GetStream\GeneratedModels\ActivityProcessorConfig;
// Create feed group request
$request = new CreateFeedGroupRequest(
id: "myid",
activitySelectors: [
new ActivitySelectorConfig(type: "following")
],
ranking: new RankingConfig(type: "recency"),
activityProcessors: [
new ActivityProcessorConfig(type: "text_interest_tags")
],
custom: (object) [
"description" => "My custom feed group"
]
);
// Create the feed group
$response = $feedsClient->createFeedGroup($request);
require 'getstream_ruby'
# Assuming you have an initialized API client
# client = GetStream::Client.new(api_key: ENV['STREAM_KEY'], api_secret: ENV['STREAM_SECRET'])
request = GetStream::Generated::Models::CreateFeedGroupRequest.new(
id: "myid",
activity_selectors: [
GetStream::Generated::Models::ActivitySelectorConfig.new(type: "following")
],
ranking: GetStream::Generated::Models::RankingConfig.new(type: "recency"),
activity_processors: [
GetStream::Generated::Models::ActivityProcessorConfig.new(type: "text_interest_tags")
],
custom: { description: "My custom feed group" }
)
response = client.feeds.create_feed_group(request)
Note that any write operation to feed groups/views can take up to 30 seconds to propagate to all API nodes.
Applications can’t have more than 100 feed groups
List feed groups
To list existing feed groups and their configurations:
await client.feeds.listFeedGroups();
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)
$createResponse = $feedsClient->createFeedGroup(
new GeneratedModels\CreateFeedGroupRequest(
id: "mytimeline",
defaultVisibility: 'public',
ranking: new GeneratedModels\RankingConfig(
type: 'expression',
score: 'decay_linear(time) * popularity'
),
activitySelectors: [
new GeneratedModels\ActivitySelectorConfig(
type: 'following'
)
]
)
);
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"
)
)
require 'getstream_ruby'
response = client.feeds.create_feed_group(
GetStream::Generated::Models::CreateFeedGroupRequest.new(
id: 'mytimeline',
ranking: GetStream::Generated::Models::RankingConfig.new(
type: 'expression',
score: 'decay_linear(time) * popularity'
),
activity_selectors: [
GetStream::Generated::Models::ActivitySelectorConfig.new(type: 'following')
]
)
)
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)
use GetStream\GeneratedModels\CreateFeedGroupRequest;
use GetStream\GeneratedModels\ActivitySelectorConfig;
use GetStream\GeneratedModels\RankingConfig;
use GetStream\GeneratedModels\ActivityProcessorConfig;
use GetStream\GeneratedModels\GetOrCreateFeedRequest;
// Create feed group with activity processors, activity selectors, and ranking
$createFeedGroupRequest = new CreateFeedGroupRequest(
id: "mytimeline",
// Run the activity processors to analyse topics for text & images
activityProcessors: [
new ActivityProcessorConfig(type: "text_interest_tags"),
new ActivityProcessorConfig(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: [
new ActivitySelectorConfig(type: "popular"),
new ActivitySelectorConfig(type: "following"),
new ActivitySelectorConfig(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: new RankingConfig(
type: "interest",
score: "decay_linear(time) * interest_score * decay_linear(popularity)"
)
);
// Create the feed group
$response = $feedsClient->createFeedGroup($createFeedGroupRequest);
// Create and get the feed for a specific user
$forYouFeed = $feedsClient->feed("mytimeline", "thierry");
$feedRequest = new GetOrCreateFeedRequest(userID: "thierry");
$feedResponse = $forYouFeed->getOrCreateFeed($feedRequest);
require 'getstream_ruby'
# Create feed group with activity processors, activity selectors, and ranking
create_request = GetStream::Generated::Models::CreateFeedGroupRequest.new(
id: "mytimeline",
activity_processors: [
GetStream::Generated::Models::ActivityProcessorConfig.new(type: "text_interest_tags"),
GetStream::Generated::Models::ActivityProcessorConfig.new(type: "image_interest_tags")
],
activity_selectors: [
GetStream::Generated::Models::ActivitySelectorConfig.new(type: "popular"),
GetStream::Generated::Models::ActivitySelectorConfig.new(type: "following"),
GetStream::Generated::Models::ActivitySelectorConfig.new(type: "interest")
],
ranking: GetStream::Generated::Models::RankingConfig.new(
type: "interest",
score: "decay_linear(time) * interest_score * decay_linear(popularity)"
)
)
client.feeds.create_feed_group(create_request)
# Create and get the feed for a specific user
get_or_create_request = GetStream::Generated::Models::GetOrCreateFeedRequest.new(
user_id: "thierry"
)
feed_response = client.feeds.get_or_create_feed("mytimeline", "thierry", get_or_create_request)
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)
}
// Create notification feed group with aggregation and tracking
$request = new GeneratedModels\CreateFeedGroupRequest(
id: "myid",
defaultVisibility: 'public',
// Group by activity type and day
aggregation: new GeneratedModels\AggregationConfig(
format: '{{ type }}-{{ time.strftime("%Y-%m-%d") }}'
),
// Enable notification tracking
notification: new GeneratedModels\NotificationConfig(
trackRead: true,
trackSeen: true
)
);
// Create the feed group
$response = $feedsClient->createFeedGroup($request);
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\") }}"
)
)
require 'getstream_ruby'
my_notification_group = client.feeds.create_feed_group(
GetStream::Generated::Models::CreateFeedGroupRequest.new(
id: 'myid',
# Group by activity type and day
aggregation: GetStream::Generated::Models::AggregationConfig.new(
format: '{{ type }}-{{ time.strftime("%Y-%m-%d") }}'
),
# Enable notification tracking
notification: GetStream::Generated::Models::NotificationConfig.new(
track_read: true,
track_seen: true
)
)
)
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();
use GetStream\GeneratedModels\GetOrCreateFeedRequest;
$notificationFeed = $feedsClient->feed("notification", "jane");
// Read notifications
$feedRequest = new GetOrCreateFeedRequest(
limit: 20,
userID: "jane"
);
$response = $notificationFeed->getOrCreateFeed($feedRequest);
// Access the aggregated activities
$notifications = $response->data->aggregatedActivities;
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
)
require 'getstream_ruby'
# Get or create notification feed
notification_feed_request = GetStream::Generated::Models::GetOrCreateFeedRequest.new(
limit: 20,
user_id: 'jane'
)
response = client.feeds.get_or_create_feed('notification', 'jane', notification_feed_request)
# Access the aggregated activities
notifications = response.aggregated_activities if response.respond_to?(:aggregated_activities)
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)
}
use GetStream\GeneratedModels\MarkActivityRequest;
// Create notification feed
$notificationFeed = $feedsClient->feed("notification", "john");
// Mark all notifications as read
$markRequest = new MarkActivityRequest(
markAllRead: true,
userID: "john"
);
$response = $notificationFeed->markActivity($markRequest);
// Or mark only selected notifications as read
$markSelectedRequest = new MarkActivityRequest(
markRead: [
// group names to mark as read
],
userID: "john"
);
$response = $notificationFeed->markActivity($markSelectedRequest);
Updating feed groups
It’s possible to update any custom or built-in feed group:
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)
}
use GetStream\GeneratedModels\UpdateFeedGroupRequest;
// Create update request
$updateRequest = new UpdateFeedGroupRequest(
// Fields to update
// activityProcessors: [...],
// activitySelectors: [...],
// ranking: new RankingConfig(...),
// custom: (object) [...]
);
// Update the feed group
$response = $feedsClient->updateFeedGroup("myid", $updateRequest);
require 'getstream_ruby'
update_request = GetStream::Generated::Models::UpdateFeedGroupRequest.new(
# Fields to update, e.g.:
# activity_processors: [...],
# activity_selectors: [...],
# ranking: GetStream::Generated::Models::RankingConfig.new(...),
# custom: { ... }
)
response = client.feeds.update_feed_group("myid", update_request)
Note that any write operation to feed groups/views can take up to 30 seconds to propagate to all API nodes.
Deleting feed groups
await serverClient.feeds.deleteFeedGroup({
id: "<feed group id to delete>",
});
_, err = client.Feeds().DeleteFeedGroup(context.Background(), "mytimeline", &getstream.DeleteFeedGroupRequest{})
// Delete the feed group (soft delete by default)
$response = $feedsClient->deleteFeedGroup("mytimeline", false);
// For hard delete, pass true as the second parameter
// $response = $feedsClient->deleteFeedGroup("mytimeline", true);
require 'getstream_ruby'
# Soft delete (default)
client.feeds.delete_feed_group("mytimeline", false)
# Hard delete
# client.feeds.delete_feed_group("mytimeline", true)