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.
Note that any write operation to feed groups/views can take up to 30 seconds to propagate to all API nodes.
Set activity_filter.exclude_owner_activities on a feed group to hide the owner's own activities when reading that owner's feed.
Each feed has a created_by_id. When this flag is on, the filter drops activities whose user_id matches the feed's created_by_id, so the feed owner's own posts are hidden from their own feed, but still visible to followers who see the fanout in their own feeds.
Reading user:jimmy hides activities where user_id is jimmy.
Reading user:amy still shows activities fanned out from user:jimmy (and hides Amy's own).
The flag is set once at the feed group level (not per view) and applied automatically per feed. It works on the user, timeline, and foryou built-in groups, and on custom groups.
Where it's not valid: the API rejects activity_filter on:
notification, story, or stories built-in groups
feed groups that also set aggregation, notification, or stories config
For self-notifications, no configuration is needed. The Feeds API already skips creating notifications when the actor is the activity owner.
Update semantics: feed group updates use full-replacement semantics. Omitting activity_filter on an UpdateFeedGroup call clears the stored value, so include it on every update if you want to preserve it.
When flag changes take effect: changes apply on the next read for most feeds. Ranked feeds (for example foryou, or groups with a ranking config) can continue serving pre-change pages for up to one hour due to cursor-backed caching.
To list existing feed groups and their configurations:
await client.feeds.listFeedGroups();
// List all feed groups (excluding soft-deleted ones)$response = $feedsClient->listFeedGroups(false);// List all feed groups including soft-deleted ones$response = $feedsClient->listFeedGroups(true);
var response = await _feedsV3Client.ListFeedGroupsAsync(false);
# List all feed groups (excluding soft-deleted ones)response = self.client.feeds.list_feed_groups(include_deleted=False)# List all feed groups including soft-deleted onesresponse = self.client.feeds.list_feed_groups(include_deleted=True)
# List all feed groups (excluding soft-deleted ones)response = client.feeds.list_feed_groups(false)# List all feed groups including soft-deleted onesresponse = client.feeds.list_feed_groups(true)
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, activity selectors to pull in content from different sources, 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 rankingcreateFeedGroupRequest := &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 groupresponse, 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 userforYouFeed := 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 rankingcreate_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 userget_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)
import io.getstream.services.FeedsImpl;import io.getstream.models.*;FeedsImpl feedsClient = new FeedsImpl(new StreamHTTPClient("<API key>", "<API secret>"));CreateFeedGroupRequest createRequest = CreateFeedGroupRequest.builder() .id("mytimeline") .activityProcessors(List.of( ActivityProcessorConfig.builder().type("text_interest_tags").build(), ActivityProcessorConfig.builder().type("image_interest_tags").build() )) .activitySelectors(List.of( ActivitySelectorConfig.builder().type("popular").build(), ActivitySelectorConfig.builder().type("following").build(), ActivitySelectorConfig.builder().type("interest").build() )) .ranking(RankingConfig.builder() .type("interest") .score("decay_linear(time) * interest_score * decay_linear(popularity)") .build()) .build();CreateFeedGroupResponse response = feedsClient.createFeedGroup(createRequest).execute().getData();// Create and get the feed for a specific userFeed forYouFeed = new Feed("mytimeline", "thierry", feedsClient);GetOrCreateFeedRequest feedRequest = GetOrCreateFeedRequest.builder() .userID("thierry") .build();forYouFeed.getOrCreate(feedRequest);
var createRequest = new CreateFeedGroupRequest{ ID = "mytimeline", ActivityProcessors = new List<ActivityProcessorConfig> { new() { Type = "text_interest_tags" }, new() { Type = "image_interest_tags" } }, ActivitySelectors = new List<ActivitySelectorConfig> { new() { Type = "popular" }, new() { Type = "following" }, new() { Type = "interest" } }, Ranking = new RankingConfig { Type = "interest", Score = "decay_linear(time) * interest_score * decay_linear(popularity)" }};var response = await _feedsV3Client.CreateFeedGroupAsync(createRequest);// Create and get the feed for a specific userawait _feedsV3Client.GetOrCreateFeedAsync( "mytimeline", "thierry", new GetOrCreateFeedRequest { UserID = "thierry" });
create_request = { "id": "mytimeline", "activity_processors": [ {"type": "text_interest_tags"}, {"type": "image_interest_tags"} ], "activity_selectors": [ {"type": "popular"}, {"type": "following"}, {"type": "interest"} ], "ranking": { "type": "interest", "score": "decay_linear(time) * interest_score * decay_linear(popularity)" }}response = self.client.feeds.create_feed_group(**create_request)# Create and get the feed for a specific userfeed = self.client.feeds.feed("mytimeline", "thierry")feed.get_or_create(user_id="thierry")
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 ) ))
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({ // Mark all notifications as read... mark_all_read: true, // ...or only selected ones mark_read: [ /* 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);
Deleting feed groups will not cascade delete associated resources. It will make feeds within the feed group unreadable but any fanout that has happened before deletion will stay in effect.
If you need a clean slate during development it is advised to create a new feed group instead.
await serverClient.feeds.deleteFeedGroup({ id: "<feed group id to delete>",});
// 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)