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,
},
});
Notification Feeds
Notification feeds let you notify users about relevant interactions, for example
- someone started to follow them
- someone liked their post
- someone left a comment on their post
Creating notification feeds
The built-in notification
feed comes with the necessary configurations, but it’s also possible to create your own notification feed group:
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\") }}"
)
)
Aggregation format
The built-in notification
feed uses the following aggregation format: "{{ type }}_{{ time.strftime(\"%Y-%m-%d\") }}"
. You can change this syntax by updating the notification
feed group.
You can see all supported fields and syntax for aggregation in the Aggregation guide.
It’s possible to turn off aggregation, but still enable notification tracking. In that case every new activity will increase unread
/unseen
count. When you have aggregation turned on, unread
/unseen
will refer to the number of aggregated groups.
Adding notification activities
The built-in notification
groups can automatically create notifications for the most common interactions (see Built-in notification feed section).
If you want to extend that, or create your own notification feed, you can add notification activities using server-side integration. You can add webhook handlers for the relevant events to create notifications without API calls from your client-side application to your server-side application.
Manual Activity Addition to Notification Feeds
You can directly add activities to notification feeds for complete control over notifications (only available server-side):
// Add a custom notification directly to a user's notification feed
await serverClient.feeds.addActivity({
feeds: ["notification:user-123"], // Target user's notification feed
type: "milestone", // Custom activity type
text: "You've reached 1000 followers!",
user_id: "user_id",
extra_data: {
milestone_type: "followers",
count: 1000,
},
});
// Add activity to custom notification feed group
await serverClient.feeds.addActivity({
feeds: ["alerts:user-123"], // Custom notification feed group
type: "system_alert",
text: "Your subscription expires in 3 days",
user_id: "user_id",
});
ctx := context.Background()
// Add a custom notification directly to a user's notification feed
_, err = client.Feeds().AddActivity(ctx, &getstream.AddActivityRequest{
Feeds: []string{"notification:john"}, // Target user's notification feed
Type: "milestone", // Custom activity type
Text: getstream.PtrTo("You've reached 1000 followers!"),
UserID: getstream.PtrTo("<user id>"),
Custom: map[string]any{
"milestone_type": "followers",
"count": 1000,
},
})
if err != nil {
log.Fatal("Error adding milestone activity:", err)
}
// Add activity to custom notification feed group
_, err = client.Feeds().AddActivity(ctx, &getstream.AddActivityRequest{
Feeds: []string{"alerts:john"}, // Custom notification feed group
Type: "system_alert",
Text: getstream.PtrTo("Your subscription expires in 3 days"),
UserID: getstream.PtrTo("<user id>"),
})
if err != nil {
log.Fatal("Error adding system alert activity:", err)
}
Important: When adding activities directly to notification feeds, ensure the activity type
is included in the feed group’s push_types
configuration to trigger push notifications.
Built-in notification feed
Creating notification activities
The built-in notification
feed allows you to automatically create notification activities.
Adding notifications with the create notification activity flag only works if the target user (the one who should receive the notification) has a feed with group notification
, and id <user id>
.
// Eric follows Jane
ericFeed.follow(
targetFid = janeFeed.fid,
createNotificationActivity = true // When true Jane's notification feed will be updated with follow activity
)
// Eric comments on Jane's activity
ericFeed.addComment(
ActivityAddCommentRequest(
comment = "Agree!",
activityId = janeActivity.activityId,
createNotificationActivity = true // When true Jane's notification feed will be updated with comment activity
)
)
// Eric reacts to Jane's activity
ericFeed.addReaction(
activityId = janeActivity.activityId,
request = AddReactionRequest(
type = "like",
createNotificationActivity = true // When true Jane's notification feed will be updated with reaction activity
),
)
// Eric reacts to a comment posted to Jane's activity by Sara
ericFeed.addCommentReaction(
commentId = saraComment.id,
request = AddCommentReactionRequest(
type = "like",
createNotificationActivity = true // When true Sara's notification feed will be updated with comment reaction activity
),
)
// Eric follows Jane
await ericTimeline.follow(janeFeed, {
// When true Jane's notification feed will be updated with follow activity
create_notification_activity: true,
});
// Eric comments on Jane's activity
await ericClient.addComment({
comment: "Agree!",
object_id: janeActivity.id,
object_type: "activity",
// When true Jane's notification feed will be updated with comment activity
create_notification_activity: true,
});
// Eric reacts to Jane's activity
await ericClient.addReaction({
activity_id: janeActivity.id,
// When true Jane's notification feed will be updated with reaction activity
type: "like",
create_notification_activity: true,
});
// Eric reacts to a comment posted to Jane's activity by Sara
await ericClient.addCommentReaction({
id: saraComment.id,
type: "like",
// When true Sara's notification feed will be updated with comment reaction activity
create_notification_activity: true,
});
// Eric follows Jane
await ericFeed.follow(
targetFid: janeFeed.fid,
createNotificationActivity:
true, // When true Jane's notification feed will be updated with follow activity
);
// Eric comments on Jane's activity
await ericFeed.addComment(
request: ActivityAddCommentRequest(
comment: 'Agree!',
activityId: janeActivity.activityId,
createNotificationActivity:
true, // When true Jane's notification feed will be updated with comment activity
),
);
// Eric reacts to Jane's activity
await ericFeed.addReaction(
activityId: janeActivity.activityId,
request: const AddReactionRequest(
type: 'like',
createNotificationActivity:
true, // When true Jane's notification feed will be updated with reaction activity
),
);
// Eric reacts to a comment posted to Jane's activity by Sara
await ericFeed.addCommentReaction(
commentId: saraComment.activityId,
request: const AddCommentReactionRequest(
type: 'like',
createNotificationActivity:
true, // When true Sara's notification feed will be updated with comment reaction activity
),
);
// Eric follows Jane
await serverClient.feeds.follow({
source: ericTimeline.feed,
target: janeFeed.feed,
// When true Jane's notification feed will be updated with follow activity
create_notification_activity: true,
});
// Eric comments on Jane's activity
await serverClient.feeds.addComment({
comment: "Agree!",
object_id: janeActivity.id,
object_type: "activity",
// When true Jane's notification feed will be updated with comment activity
create_notification_activity: true,
user_id: "eric",
});
// Eric reacts to Jane's activity
await serverClient.feeds.addReaction({
activity_id: janeActivity.id,
// When true Jane's notification feed will be updated with reaction activity
type: "like",
create_notification_activity: true,
user_id: "eric",
});
// Eric reacts to a comment posted to Jane's activity by Sara
await serverClient.feeds.addCommentReaction({
id: saraComment.id,
type: "like",
// When true Sara's notification feed will be updated with comment reaction activity
create_notification_activity: true,
user_id: "eric",
});
ctx := context.Background()
// Eric follows Jane
_, err = client.Feeds().Follow(ctx, &getstream.FollowRequest{
Source: "user:eric",
Target: "user:jane",
CreateNotificationActivity: getstream.PtrTo(true), // When true Jane's notification feed will be updated with follow activity
})
if err != nil {
log.Fatal("Error following user:", err)
}
// Eric comments on Jane's activity
_, err = client.Feeds().AddComment(ctx, &getstream.AddCommentRequest{
Comment: "Agree!",
ObjectID: "janeActivity.id", // This would be the actual activity ID
ObjectType: "activity",
CreateNotificationActivity: getstream.PtrTo(true), // When true Jane's notification feed will be updated with comment activity
UserID: getstream.PtrTo("eric"),
})
if err != nil {
log.Fatal("Error adding comment:", err)
}
// Eric reacts to Jane's activity
_, err = client.Feeds().AddReaction(ctx, "janeActivity.id", &getstream.AddReactionRequest{ // This would be the actual activity ID
Type: "like",
CreateNotificationActivity: getstream.PtrTo(true), // When true Jane's notification feed will be updated with reaction activity
UserID: getstream.PtrTo("eric"),
})
if err != nil {
log.Fatal("Error adding reaction:", err)
}
// Eric reacts to a comment posted to Jane's activity by Sara
_, err = client.Feeds().AddCommentReaction(ctx, "saraComment.id", &getstream.AddCommentReactionRequest{ // This would be the actual comment ID
Type: "like",
CreateNotificationActivity: getstream.PtrTo(true), // When true Sara's notification feed will be updated with comment reaction activity
UserID: getstream.PtrTo("eric"),
})
if err != nil {
log.Fatal("Error adding comment reaction:", err)
}
Reading notification activities
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
)
This is what Jane’s notification feed looks like after the above interactions (only relevant fields shown):
- Three aggregated activity groups:
comment-2025-08-04
reaction-2025-08-04
follow-2025-08-04
notification_context
has information about the activity/action that triggered the notification- Please note that
notification_context
field is only defined if you’re using the built-innotification
feed andcreate_notification_activity
flag
- Please note that
{
aggregated_activities: [
{
activity_count: 1,
user_count: 1,
group: "comment-2025-08-04",
activities: [
{
type: "comment",
user: {
id: "eric",
name: "Eric",
// other User fields
},
notification_context: {
trigger: {
text: "Eric commented on your activity",
type: "comment",
},
target: {
user_id: "jane",
type: "post",
text: "As earnestly shameless elsewhere defective estimable fulfilled of",
id: "a0668408-0eb9-4906-a1cf-be79f988051d",
},
},
// Other activity fields
},
],
},
{
activity_count: 1,
user_count: 1,
group: "reaction-2025-08-04",
activities: [
{
type: "reaction",
user: {
id: "eric",
name: "Eric",
},
notification_context: {
target: {
id: "8966090a-30bf-4fe2-b8bc-b0fe36200e56",
user_id: "jane",
type: "post",
text: "Ask too matter formed county wicket oppose talent",
},
trigger: {
type: "reaction",
text: "Eric reacted to your activity",
},
},
},
],
},
{
activity_count: 1,
user_count: 1,
group: "follow-2025-08-04",
activities: [
{
type: "follow",
user: {
id: "eric",
name: "Eric",
},
notification_context: {
target: {
id: "jane",
name: "Jane",
},
trigger: {
type: "follow",
text: "Eric started following you",
},
},
},
],
},
];
}
{
aggregated_activities: [
{
activity_count: 1,
user_count: 1,
group: "comment-2025-08-04",
activities: [
{
type: "comment",
user: {
id: "eric",
name: "Eric",
// other User fields
},
notification_context: {
trigger: {
text: "Eric commented on your activity",
type: "comment",
},
target: {
user_id: "jane",
type: "post",
text: "As earnestly shameless elsewhere defective estimable fulfilled of",
id: "a0668408-0eb9-4906-a1cf-be79f988051d",
},
},
// Other activity fields
},
],
},
{
activity_count: 1,
user_count: 1,
group: "reaction-2025-08-04",
activities: [
{
type: "reaction",
user: {
id: "eric",
name: "Eric",
},
notification_context: {
target: {
id: "8966090a-30bf-4fe2-b8bc-b0fe36200e56",
user_id: "jane",
type: "post",
text: "Ask too matter formed county wicket oppose talent",
},
trigger: {
type: "reaction",
text: "Eric reacted to your activity",
},
},
},
],
},
{
activity_count: 1,
user_count: 1,
group: "follow-2025-08-04",
activities: [
{
type: "follow",
user: {
id: "eric",
name: "Eric",
},
notification_context: {
target: {
id: "jane",
name: "Jane",
},
trigger: {
type: "follow",
text: "Eric started following you",
},
},
},
],
},
];
}
{
"AggregatedActivities": [
{
"ActivityCount": 1,
"UserCount": 1,
"Group": "comment-2025-08-04",
"Activities": [
{
"Type": "comment",
"User": {
"ID": "eric",
"Name": "Eric"
// other User fields
},
"NotificationContext": {
"trigger": {
"text": "Eric commented on your activity",
"type": "comment"
},
"target": {
"user_id": "jane",
"type": "post",
"text": "As earnestly shameless elsewhere defective estimable fulfilled of",
"id": "a0668408-0eb9-4906-a1cf-be79f988051d"
}
}
// Other activity fields
}
]
},
{
"ActivityCount": 1,
"UserCount": 1,
"Group": "reaction-2025-08-04",
"Activities": [
{
"Type": "reaction",
"User": {
"ID": "eric",
"Name": "Eric"
},
"NotificationContext": {
"target": {
"id": "8966090a-30bf-4fe2-b8bc-b0fe36200e56",
"user_id": "jane",
"type": "post",
"text": "Ask too matter formed county wicket oppose talent"
},
"Trigger": {
"type": "reaction",
"text": "Eric reacted to your activity"
}
}
}
]
},
{
"ActivityCount": 1,
"UserCount": 1,
"Group": "follow-2025-08-04",
"Activities": [
{
"Type": "follow",
"User": {
"ID": "eric",
"Name": "Eric"
},
"NotificationContext": {
"target": {
"id": "jane",
"name": "Jane"
},
"trigger": {
"type": "follow",
"text": "Eric started following you"
}
}
}
]
}
]
}
Push Notifications
For information on configuring push notifications, see Feed Group Push Configuration.
Notification status
If notification tracking is turned on for the feed group then you’ll receive notification status when reading the feed. Notification status tells
- How many unread notifications does the feed have?
- Which notifications are read?
- How many unseen notifications does the feed have?
- Which notifications are seen?
For example, take the notification system on Facebook. If you click the notification icon, all notifications get marked as seen. However, an individual notification only gets marked as read when you click on it.
Here is an example payload for notificaiton status:
{
notification_status: {
// Number of unread notifications
unread: 12,
// Number of unseen notifications
unseen: 0,
last_seen_at: // Date object with last seen timestamp,
seen_activities: [],
last_read_at: // Date object with last read timestamp,
read_activities: ['reaction-2025-08-04'],
}
}
// Check if a group is read/seen
const isRead = (lastReadAt && group.updated_at.getTime() < lastReadAt.getTime()) || readActivities.includes(group.group);
const isSeen = (lastSeenAt && group.updated_at.getTime() < lastSeenAt.getTime()) || seenActivities.includes(group.group);
{
notification_status: {
// Number of unread notifications
unread: 12,
// Number of unseen notifications
unseen: 0,
last_seen_at: // Date object with last seen timestamp,,
seen_activities: [],
last_read_at: // Date object with last read timestamp,,
read_activities: ['reaction-2025-08-04'],
}
}
// Check if a group is read/seen
const isRead = (lastReadAt && group.updated_at.getTime() < lastReadAt.getTime()) || readActivities.includes(group.group);
const isSeen = (lastSeenAt && group.updated_at.getTime() < lastSeenAt.getTime()) || seenActivities.includes(group.group);
{
NotificationStatus: {
// Number of unread notifications
Unread: 12,
// Number of unseen notifications
Unseen: 0,
LastReadAt: // Timestamp object with last seen timestamp,,
SeenActivities: [],
LastSeenAt: // Timestamp object with last read timestamp,,
ReadActivities: ['reaction-2025-08-04'],
}
}
import (
// Other imports...
"slices"
)
// Check if a group is read/seen
group := response.Data.AggregatedActivities[0]
lastSeenAt := response.Data.NotificationStatus.LastSeenAt
seenActivities := response.Data.NotificationStatus.SeenActivities
lastReadAt := response.Data.NotificationStatus.LastReadAt
readActivities := response.Data.NotificationStatus.ReadActivities
isRead := (lastReadAt != nil && lastReadAt.Time != nil && group.UpdatedAt.Time != nil && group.UpdatedAt.Time.Before(*lastReadAt.Time)) || slices.Contains(readActivities, group.Group)
isSeen := (lastSeenAt != nil && lastSeenAt.Time != nil && group.UpdatedAt.Time != nil && group.UpdatedAt.Time.Before(*lastSeenAt.Time)) || slices.Contains(seenActivities, group.Group)
Marking notifications as seen
notificationFeed.markActivity(
request = MarkActivityRequest(
// Mark all notifications as seen...
markAllSeen = true,
// ...or only selected ones
markSeen = listOf(
/* group names to mark as seen */
)
)
)
await notificationFeed.markActivity({
// Mark all notifications as seen...
mark_all_seen: true,
// ...or only selected ones
mark_seen: [
/* group names to mark as seen */
],
});
await notificationFeed.markActivity(
request: const MarkActivityRequest(
// Mark all notifications as seen...
markAllSeen: true,
// ...or only selected ones
markSeen: [
/* group names to mark as seen */
],
),
);
await notificationFeed.markActivity({
// Mark all notifications as seen...
mark_all_seen: true,
// ...or only selected ones
mark_seen: [
/* group names to mark as seen */
],
user_id: "<user id>",
});
notificationFeed := client.Feeds().Feed("notification", "john")
_, err = notificationFeed.MarkActivity(context.Background(), &getstream.MarkActivityRequest{
// Mark all notifications as seen...
MarkAllSeen: getstream.PtrTo(true),
// ...or only selected ones
MarkSeen: []string{
// group names to mark as seen
},
UserID: getstream.PtrTo("john"),
})
if err != nil {
log.Fatal("Error:", err)
}
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)
}