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")
],
activity_filter: GetStream::Generated::Models::ActivityFilterConfig.new(
exclude_owner_activities: true
),
custom: { description: "My custom feed group" }
)
response = client.feeds.create_feed_group(request)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.
Note that any write operation to feed groups/views can take up to 30 seconds to propagate to all API nodes.
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 prioritizes 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:
Applications can't have more than 100 feed groups
Alternatively you can use the getOrCreateFeedGroup endpoint to create and apply settings, or return the existing group.
Exclude owner activities
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:jimmyhides activities whereuser_idisjimmy. - Reading
user:amystill shows activities fanned out fromuser: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, orstoriesbuilt-in groups- feed groups that also set
aggregation,notification, orstoriesconfig
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. On ranked feeds (for example foryou, or groups with a ranking config), updating the feed group invalidates outstanding pagination tokens, so clients mid-pagination may need to restart from the first page.
Overview of the feed group model
FeedGroupResponse fields:
| Field | Description |
|---|---|
id | Feed group identifier. |
default_visibility | Visibility assigned to feeds created in this group (public, visible, followers, members, private). Applies at creation only. |
default_follower_role | Role new followers of feeds in this group receive: a built-in (feed_follower, feed_member_viewer) or a custom role. Unset behaves as feed_follower. |
activity_selectors | Activity source selection rules. |
activity_processors | Enrichment and processing pipeline configuration. |
ranking | Ranking formula/configuration for feed ordering. |
aggregation | Aggregation settings for grouped reads. |
activity_filter | Read-time filters, including exclude_owner_activities. |
notification | Notification configuration for notification feeds. |
stories | Stories configuration for stories feeds. |
push_notification | Push notification behavior configuration. |
custom | Custom metadata object. |
created_at | Feed group creation timestamp. |
updated_at | Feed group last updated timestamp. |
deleted_at | Deletion timestamp (present when soft-deleted). |
Default follower role
Every follow carries a follower_role that decides what that follower may do. By default a new follow gets feed_follower. To give followers a different tier you would otherwise have to call updateFollow after each follow, usually from a feeds.follow.created webhook, which leaves a window where the follower holds permissions you did not intend.
default_follower_role on the feed group removes that round trip. Set it once, and every new follow of a feed in the group starts with that role.
The motivating case is a follower tier you don't have to correct after the fact: a group whose followers get feed_member_viewer can read, react and vote, but not comment, from the moment the follow is accepted.
require 'getstream_ruby'
create_request = GetStream::Generated::Models::CreateFeedGroupRequest.new(
id: "premium",
default_follower_role: "feed_member_viewer"
)
response = client.feeds.create_feed_group(create_request)Setting it is server-side only, like every feed group write. Allowed values are the two built-in follower roles, feed_follower and feed_member_viewer, plus any custom role you created for your app. Anything else, including reserved names such as admin or feed_member, is rejected when you write the feed group rather than being silently ignored later. Leaving it unset behaves exactly as before the field existed.
When the role is applied
The default applies once the follow is accepted, which for most visibility levels is the moment it is created.
On a followers-visibility feed a follow starts as pending, and a pending follower deliberately holds no follower permissions until the owner approves. The group default is applied at approval instead, whether the owner accepts the request directly or a visibility change auto-approves the backlog. That way an unapproved follower never receives the role early.
An explicit follower_role on acceptFollow or updateFollow always wins over the group default.
Existing accepted follows are never rewritten, so changing the default does not change the permissions of anyone who already follows a feed in the group. A follow still pending when you change it is a different case: it picks up whatever the default is at the moment it is approved, not the one in place when it was requested.
Check the visibility of the feeds in the group
feed_member_viewer is not a restriction-only control, and a group default applies to every feed in the group regardless of its visibility.
On public, visible and followers feeds it is a strict narrowing of feed_follower. On members and private feeds it widens a follower's access instead: it grants read access to the feed and its activities that feed_follower does not have. That is the subscriber-tier case, and it is deliberate: the follow itself is what admits a paying read-only audience to a restricted feed. It also means a group default of feed_member_viewer gives followers of a members or private feed in that group more than they would otherwise get.
See Follower permission tiers for the per-level breakdown.
List feed groups
To list existing feed groups and their configurations:
# List all feed groups (excluding soft-deleted ones)
response = client.feeds.list_feed_groups(false)
# List all feed groups including soft-deleted ones
response = client.feeds.list_feed_groups(true)Activity Ranking
When ranking activities you can specify a ranking formula.
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, activity selectors to pull in content from different sources, and ranks content you're likely to engage with higher in the feed.
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:
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:
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
await notificationFeed.markActivity({
// Mark all notifications as read...
mark_all_read: true,
// ...or only selected ones
mark_read: [
/* group names to mark as read */
],
});Updating feed groups
It's possible to update any custom or built-in feed group:
Updates replace every field. Omitting custom, ranking, aggregation, notification, push_notification, stories, activity_processors or activity_filter clears the stored value, so send every field you want to keep. activity_selectors, default_visibility and default_follower_role are the exceptions: omitting any of them leaves the stored value in place. default_follower_role therefore cannot be cleared once set, only changed to another role.
Changing default_visibility does not affect feeds that already exist. Every feed stores its own visibility, copied from the group default at the moment the feed was created, so the new default only applies to feeds created from then on. To move an existing feed to a different level, change it per feed with Changing Feed Visibility, which also reconciles that feed's follows.
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)Deleting feed groups
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.
require 'getstream_ruby'
# Soft delete (default)
client.feeds.delete_feed_group("mytimeline", false)
# Hard delete
# client.feeds.delete_feed_group("mytimeline", true)