// 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);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:
Notification configuration
The notification config includes several settings that control how activities are tracked and deduplicated:
- TrackSeen: When enabled, tracks which activities (or aggregated groups) have been seen by the user
- TrackRead: When enabled, tracks which activities (or aggregated groups) have been read by the user
- DeduplicationWindow: Controls how duplicate notifications are handled on feed groups with notification config
- Empty string (
"") = always deduplicate (default behavior) - Duration string (e.g.,
"24h","7d") = time-based deduplication window
- Empty string (
You can enable track_seen / track_read on custom feed groups (including flat feeds) and the built-in notification group. Built-in groups like timeline and user do not accept notification config via update. Aggregation and notification tracking are independent. See Ranking by read/seen status for using these fields in ranking expressions.
Note: Comments are not deduplicated. Each comment will create a separate notification activity, regardless of the deduplication window setting.
The built-in notification feed group has deduplication enabled by default (always deduplicate). To change the deduplication window, you need to update the notification feed group.
Aggregation format
The built-in notification feed uses the following aggregation format: "{{ target_id }}_{ 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. For more information see the Flat notifications section.
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.
It's important to note that target_id is only defined if a notification is created by Stream API. If you want to extend or replace this behavior by adding notification activities from your own application, you most likely need to extend the default aggregation format. You can see all supported fields and syntax for aggregation in the Aggregation guide.
Manual Activity Addition to Notification Feeds
You can directly add activities to notification feeds for complete control over notifications (only available server-side):
use GetStream\GeneratedModels;
// Add a custom notification directly to a user's notification feed
$feedsClient->addActivity(new GeneratedModels\AddActivityRequest(
feeds: ["notification:john"], // Target user's notification feed
type: "milestone", // Custom activity type
text: "You've reached 1000 followers!",
userID: "<user id>",
custom: (object)[
"milestone_type" => "followers",
"count" => 1000,
]
));
// Add activity to custom notification feed group
$feedsClient->addActivity(new GeneratedModels\AddActivityRequest(
feeds: ["alerts:john"], // Custom notification feed group
type: "system_alert",
text: "Your subscription expires in 3 days",
userID: "<user id>"
));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. The following actions are supported and will automatically create notification activities for the target user depending on the action.
| Action | Trigger User | Target User (Recipient) | Notification Type | Notification Text | Deduplicated? | Notes |
|---|---|---|---|---|---|---|
| React to Activity | User who reacts (e.g., Bob) | Activity author (e.g., Alice) | reaction | {user} reacted to your activity | ✅ Yes | Multiple reactions from the same user on the same activity are deduplicated within the deduplication window |
| React to Comment | User who reacts (e.g., Charlie) | Comment author (e.g., Bob) | comment_reaction | {user} reacted to your comment | ✅ Yes | Multiple reactions from the same user on the same comment are deduplicated within the deduplication window. The activity author does NOT receive a notification for reactions |
| Comment on Activity | User who comments (e.g., Bob) | Activity author (e.g., Alice) | comment | {user} commented on your activity | ❌ No | Each comment creates a new notification; not deduplicated |
| Reply to Comment | User who replies (e.g., Charlie) | Comment author (e.g., Bob) | comment_reply | {user} replied to your comment | ❌ No | Each reply creates a new notification; not deduplicated. The activity author does NOT receive a notification for replies |
| Follow User | User who follows (e.g., Bob) | User being followed (e.g., Alice) | follow | {user} started following you | ✅ Yes | Multiple follows/unfollows from the same user are deduplicated within the deduplication window |
| Mention in Activity | User who creates activity with mention (e.g., Alice) | Mentioned user (e.g., Bob) | mention | {user} mentioned you in an activity | ✅ Yes | Multiple mentions from the same user on the same activity are deduplicated within the deduplication window |
| Mention in Comment | User who creates comment with mention (e.g., Charlie) | Mentioned user (e.g., Bob) | comment_mention | {user} mentioned you in a comment | ✅ Yes | Multiple mentions from the same user on the same comment are deduplicated within the deduplication window. The activity author does NOT receive a notification for mentions (they already get a comment notification) |
| Update Activity (add mentions) | User who updates activity (e.g., Alice) | Mentioned user (e.g., Bob) | mention | {user} mentioned you in an activity | ✅ Yes | When mentions are added via UpdateActivity or UpdateActivityPartial with handle_mention_notifications=true, mention notifications are automatically created for newly mentioned users. Multiple mentions from the same user on the same activity are deduplicated within the deduplication window |
| Update Comment (add mentions) | User who updates comment (e.g., Charlie) | Mentioned user (e.g., Bob) | comment_mention | {user} mentioned you in a comment | ✅ Yes | When mentions are added via UpdateComment with handle_mention_notifications=true, comment_mention notifications are automatically created for newly mentioned users. Multiple mentions from the same user on the same comment are deduplicated within the deduplication window. The activity author does NOT receive a notification for mentions (they already get a comment notification) |
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>.
use GetStream\GeneratedModels;
// Eric follows Jane
$feedsClient->follow(new GeneratedModels\FollowRequest(
source: "user:eric",
target: "user:jane",
createNotificationActivity: true // When true Jane's notification feed will be updated with follow activity
));
// Eric comments on Jane's activity
$feedsClient->addComment(new GeneratedModels\AddCommentRequest(
comment: "Agree!",
objectID: "janeActivity.id", // This would be the actual activity ID
objectType: "activity",
createNotificationActivity: true, // When true Jane's notification feed will be updated with comment activity
userID: "eric"
));
// Eric reacts to Jane's activity
$feedsClient->addActivityReaction("janeActivity.id", new GeneratedModels\AddReactionRequest( // This would be the actual activity ID
type: "like",
createNotificationActivity: true, // When true Jane's notification feed will be updated with reaction activity
userID: "eric"
));
// Eric reacts to a comment posted to Jane's activity by Sara
$feedsClient->addCommentReaction("saraComment.id", new GeneratedModels\AddCommentReactionRequest( // This would be the actual comment ID
type: "like",
createNotificationActivity: true, // When true Sara's notification feed will be updated with comment reaction activity
userID: "eric"
));Updating mentions in activities and comments
You can also create or remove mention notifications when updating activities or comments. This flag defaults to false for all endpoints it's supported in.
use GetStream\GeneratedModels;
// Alice updates her activity to mention Bob
$feedsClient->updateActivity(activityId, new GeneratedModels\UpdateActivityRequest(
text: "Hey @Bob check this out!",
mentionedUserIDs: ["bob"],
handleMentionNotifications: true, // When true, Bob will receive a mention notification
userID: "alice"
));
// Alice updates her comment to mention Charlie
$feedsClient->updateComment(commentId, new GeneratedModels\UpdateCommentRequest(
comment: "Hey @Charlie!",
mentionedUserIDs: ["charlie"],
handleMentionNotifications: true, // When true, Charlie will receive a comment_mention notification
userID: "alice"
));
// Alice removes mentions from her activity
$feedsClient->updateActivity(activityId, new GeneratedModels\UpdateActivityRequest(
text: "Updated text without mentions",
mentionedUserIDs: [],
handleMentionNotifications: true, // When true, mention notifications for removed users are deleted
userID: "alice"
));Commenting and reacting to your own activities and comments will not create notification activities.
Reaction notification creation timing
Added June 23, 2026. Reaction notification creation for activity and comment reactions now runs asynchronously in the background. The API response includes new fields — see below.
For activity reactions and comment reactions only, notification creation runs asynchronously in the background. The reaction is saved immediately; a background worker creates the notification activity shortly after. Comments, follows, and mentions are unchanged — they still create notification activities synchronously when create_notification_activity is true.
The request is unchanged: pass create_notification_activity: true on addActivityReaction / addCommentReaction (or the server-side equivalents). Self-reactions still never create notifications.
Response fields
| Field | Type | Meaning |
|---|---|---|
notification_accepted | bool | Whether notification creation was accepted (enqueued). Does not guarantee the notification was eventually created — the worker may still skip it (for example if the reaction was deleted before the worker ran). |
notification_task_id | string | ID of the background task. Returned for all callers when notification creation was accepted. |
notification_created | bool | Deprecated. See Deprecated: notification_created below. |
Example response:
{
"reaction": { "type": "like", "user_id": "eric" },
"notification_accepted": true,
"notification_task_id": "feeds:reaction_notification_create:abc123",
"notification_created": true
}Deprecated: notification_created
Deprecated (June 23, 2026): notification_created is deprecated in favor of notification_accepted. It is still returned for backward compatibility and currently has the same value as notification_accepted (true when the notification task was enqueued, false for a self-reaction or when enqueue failed). It will be removed in a future API version.
Migrate existing integrations to use notification_accepted instead of notification_created.
Previously, notification_created indicated that the notification activity was created synchronously before the API response was returned. With async reaction notifications, that synchronous behavior no longer applies to reactions — the field now mirrors notification_accepted only as a compatibility shim.
The target user's notification feed may update a moment after the reaction response. If your UI reads the notification feed immediately after adding a reaction, allow for brief eventual consistency or poll the notification feed.
For server-side integrations, poll task completion with GET /tasks/{id} using the returned notification_task_id (substitute notification_task_id for task_id in the examples below):
// Example of monitoring the status of an async task
// The logic is same for all async tasks
$response = _ // Result of a Stream async API request
$taskId = $response->getData()->taskID;
// you need to poll this endpoint
$taskResponse = $client->getTask($taskId);
echo $taskResponse->getData()->status === 'completed';GET /tasks/{id} is a server-side API. Client-side SDK users receive notification_task_id in the reaction response but cannot call the tasks API with user tokens — rely on notification feed updates or real-time events instead.
Deleting notification activities
When you add notification activities with create_notification_activity you can also have the API automatically remove these when the corresponding trigger entity is deleted.
This is done by using the flag delete_notification_activity which defaults to false for all endpoints it's supported in.
| Action | Notification Type Deleted | Notes |
|---|---|---|
| Remove Activity Reaction | reaction | Only the notification activity created by the user removing the reaction is deleted. Other users' reaction notifications remain unaffected. |
| Remove Comment Reaction | comment_reaction | Only the notification activity created by the user removing the reaction is deleted. Other users' comment reaction notifications remain unaffected. |
| Delete Comment | comment | Deletes the comment notification for the activity author. |
| Delete Comment | comment_mention | When a comment with mentions is deleted, comment_mention notifications are deleted for all mentioned users if delete_notification_activity=true. |
| Unfollow User | follow | Only the notification activity created by the user performing the unfollow is deleted. |
| Delete Activity | mention | When an activity with mentions is deleted, mention notifications are deleted for all mentioned users if delete_notification_activity=true. |
| Delete Activities (batch) | mention | When multiple activities with mentions are deleted via DeleteActivities with delete_notification_activity=true, mention notifications are deleted for all mentioned users across all deleted activities. |
| Update Activity (remove mentions) | mention | When mentions are removed via UpdateActivity or UpdateActivityPartial with handle_mention_notifications=true, mention notifications are automatically removed for users no longer mentioned. |
| Update Comment (remove mentions) | comment_mention | When mentions are removed via UpdateComment with handle_mention_notifications=true, comment_mention notifications are automatically removed for users no longer mentioned. |
Deletion Scope: The deletion behavior differs depending on the action:
- Reactions and Follows: Only the notification activity created by the specific user performing the deletion is removed. Other users' notifications remain unaffected.
- Activities and Comments with Mentions: When an activity or comment containing mentions is deleted, notification activities are removed for all mentioned users if
delete_notification_activity=true.
use GetStream\GeneratedModels;
// Eric unfollows Jane
$feedsClient->unfollow(new GeneratedModels\UnfollowRequest(
source: "user:eric",
target: "user:jane",
deleteNotificationActivity: true // When true the corresponding notification activity will be removed from Jane's notification feed
));
// Eric removes his comment
$feedsClient->deleteComment("commentId", new GeneratedModels\DeleteCommentRequest( // This would be the actual comment ID
deleteNotificationActivity: true, // When true the corresponding notification activity will be removed from Jane's notification feed
userID: "eric"
));
// Eric removes his activity reaction
$feedsClient->deleteActivityReaction("janeActivity.id", new GeneratedModels\DeleteReactionRequest( // This would be the actual activity ID
type: "like",
deleteNotificationActivity: true, // When true the corresponding notification activity will be removed from Jane's notification feed
userID: "eric"
));
// Eric removes his comment reaction
$feedsClient->deleteCommentReaction("saraComment.id", new GeneratedModels\DeleteCommentReactionRequest( // This would be the actual comment ID
type: "like",
deleteNotificationActivity: true, // When true the corresponding notification activity will be removed from Jane's notification feed
userID: "eric"
));
// Eric deletes his activity with mentions
$feedsClient->deleteActivity("activityId", new GeneratedModels\DeleteActivityRequest( // This would be the actual activity ID
deleteNotificationActivity: true, // When true, mention notifications for all mentioned users will be removed
userID: "eric"
));
// Eric deletes multiple activities with mentions (batch operation)
$feedsClient->deleteActivities(new GeneratedModels\DeleteActivitiesRequest(
activityIDs: ["activityId1", "activityId2"],
deleteNotificationActivity: true, // When true, mention notifications for all mentioned users will be removed
userID: "eric"
));Trigger and Target
The trigger is the entity that triggered the creation of the notification activity. This can be a follow, a reaction or a comment. When the trigger is a comment the comment data is available on the trigger object, this allows for deep linking back to the comment that triggered the notification.
The target is the receiver of the trigger action. This can be a feed (e.g., a user's feed), an activity or a comment. When the target is an activity the activity data will be present in the target object. When the target is a comment the parent activity data and comment data will be present in the target object.
Example: A reply to a comment will include the data of the reply comment in the trigger and the parent comment and activity in the target.
{
"target": {
"id": "<activity_id>",
"type": "<activity_type>",
"user_id": "<activity_user_id>",
"comment": {
"id": "<parent_comment_id>",
"comment": "this is the parent comment",
"user_id": "<parent_comment_user_id>"
}
},
"trigger": {
"text": "<comment_user_id> replied to your comment",
"type": "comment_reply",
"comment": {
"id": "<comment_id>",
"user_id": "<comment_user_id>",
"comment": "this is the reply comment"
}
}
}Aggregating on comments and activities
By default the built-in notification feed aggregates on activities only with the aggregation format {{ target_id }}-{{ type }}-{{ time.strftime('%Y-%m-%d') }}.
If you want to aggregate on comments and activities alike you need to update the aggregation format to
{% if comment_id %}
{{ comment_id }}_{{ type }}_{{ time.strftime('%Y-%m-%d') }}
{% else %}
{{ target_id }}_{{ type }}_{{ time.strftime('%Y-%m-%d') }}
{% endif %}This lets you show notifications like
- Your comment has 5 new likes or
- Your comment has 3 new replies
Reading notification activities
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;This is what Jane's notification feed looks like after the above interactions (only relevant fields shown):
- Three aggregated activity groups:
<activity id>-comment-2025-08-04<activity id>-reaction-2025-08-04<feed id>-follow-2025-08-04
notification_contexthas information about the activity/action that triggered the notification- Please note that
notification_contextfield is only defined if you're using the built-innotificationfeed andcreate_notification_activityflag
- Please note that
When reading notifications, every aggregated activity group contains at most 100 activities (can be configured with aggregation group limit). The user_count field is also computed from the last n activities, defined by aggregation group limit.
If a group has more activities than the limit, user_count_truncated will be set to true, signaling that user_count may not be accurate. This enables creating notifications like "100+ people commented on your post". The activity_count field is always accurate, even if the group has more activities than the limit.
Example API response:
{
aggregated_activities: [
{
activity_count: 1,
user_count: 1,
user_count_truncated: false,
is_seen: true,
is_read: false,
group: "activity123-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",
attachments: [
{
type: "image",
image_url: "https://...",
},
],
},
},
// Other activity fields
},
],
},
{
activity_count: 1,
user_count: 1,
is_seen: true,
is_read: true,
group: "activity123-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,
is_seen: false,
is_read: false,
group: "jane-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",
},
},
},
],
},
{
activity_count: 1,
user_count: 1,
is_seen: false,
is_read: false,
group: "comment456-comment_reply-2025-08-04",
activities: [
{
type: "comment_reply",
user: {
id: "charlie",
name: "Charlie"
},
notification_context: {
target: {
id: "8966090a-30bf-4fe2-b8bc-b0fe36200e56",
user_id: "alice",
type: "post",
text: "Ask too matter formed county wicket oppose talent",
comment: {
id: "comment456",
user_id: "bob",
comment: "Great post! I totally agree with this."
}
},
trigger: {
type: "comment_reply",
text: "Charlie replied to your comment"
}
}
}
]
},
];
}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 (track_seen / track_read), the server stamps is_seen and is_read directly on each aggregated activity group. This means you no longer need to compute read/seen status client-side — just use the boolean fields from the response.
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.
Server-side is_seen / is_read
Each aggregated activity group includes is_seen and is_read boolean fields when tracking is enabled:
{
"aggregated_activities": [
{
"group": "activity123-comment-2025-08-04",
"is_seen": true,
"is_read": false,
"activity_count": 1,
"activities": [...]
}
]
}The is_seen and is_read fields are only present when track_seen / track_read are enabled on the feed group. When tracking is disabled, these fields are omitted from the response.
To check if a notification group is read or seen, simply read the fields:
$group = $response->getData()->aggregatedActivities[0];
$isRead = $group->isRead;
$isSeen = $group->isSeen;The server uses a hybrid algorithm to determine these values:
- Check if the group ID is in the seen/read ID lists
- Fall back to timestamp comparison (
updated_at < last_seen_at) for entries beyond the list cap
This makes the server-side fields more reliable than client-side computation, especially for feeds with many notifications. Client-side SDKs will update is_seen and is_read flags from notification WebSocket events.
Non-aggregated feeds: If aggregation is turned off but notification tracking is still enabled, is_seen and is_read are stamped on each individual activity. When aggregation is on, activities inside a group inherit the group's is_seen / is_read value.
Unread/unseen counts
The notification_status in the response also includes unread and unseen counts. These are computed from the last 1000 activities, aggregated into a maximum of 100 groups. This means unread/unseen counts will never exceed 100.
{
"notification_status": {
"unread": 12,
"unseen": 0,
"last_seen_at": "2025-08-04T12:00:00Z",
"last_read_at": "2025-08-04T11:30:00Z",
"seen_activities": [], // deprecated — use is_seen on each group instead
"read_activities": ["activity123-reaction-2025-08-04"] // deprecated — use is_read on each group instead
}
}You can access notification_status (including unread and unseen) as follows:
$response = $notificationFeed->getOrCreate(new GeneratedModels\GetOrCreateFeedRequest(userID: "john"));
$status = $response->getData()->notificationStatus;
$unread = $status?->unread ?? 0;
$unseen = $status?->unseen ?? 0;Legacy: read_activities / seen_activities arrays
Deprecated: The read_activities and seen_activities arrays in the notification_status response are deprecated. Use the is_read and is_seen fields on each aggregated activity group instead.
For backward compatibility, the notification_status response still includes read_activities and seen_activities ID lists. However, these lists are capped at 100 entries, which means they may be incomplete for feeds with many notifications. The server-side is_seen / is_read fields do not have this limitation.
Legacy: Client-side computation
Deprecated: The following client-side pattern is deprecated. Use the server-side is_read / is_seen fields instead.
Older SDK versions required computing read/seen status client-side using timestamps and ID lists:
// Deprecated — use $group->isRead / $group->isSeen instead
$group = $response->getData()->aggregatedActivities[0];
$lastSeenAt = $response->getData()->notificationStatus->lastSeenAt;
$seenActivities = $response->getData()->notificationStatus->seenActivities;
$lastReadAt = $response->getData()->notificationStatus->lastReadAt;
$readActivities = $response->getData()->notificationStatus->readActivities;
$isRead = ($lastReadAt && $group->updatedAt->getTimestamp() * 1e+9 < $lastReadAt->getTimestamp() * 1e+9) || ($readActivities && in_array($group->group, $readActivities));
$isSeen = ($lastSeenAt && $group->updatedAt->getTimestamp() * 1e+9 < $lastSeenAt->getTimestamp() * 1e+9) || ($seenActivities && in_array($group->group, $seenActivities));Marking notifications as seen
use GetStream\GeneratedModels;
$notificationFeed = $feedsClient->feed("notification", "john");
$notificationFeed->markActivity(new GeneratedModels\MarkActivityRequest(
// Mark all notifications as seen...
markAllSeen: true,
// ...or only selected ones
markSeen: [
// group names to mark as seen
],
userID: "john"
));Marking notifications as read
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);Ranking by read/seen status
When track_read or track_seen is enabled, you can use is_read and is_seen in ranking expressions to prioritize unread or unseen content. See Ranking by read/seen status for details and examples.
Realtime events
Two events are emitted when mark operations are performed:
feeds.notification_feed.updated— contains the updatednotification_status. For aggregated notification feeds it also contains the updated/added groups withis_seenandis_readflags.feeds.activity.added- contains the new activities for flat notifications
Clients can subscribe to these realtime events to update the UI without re-fetching the feed.
Because feeds.notification_feed.updated is broadcast to all watchers, its notification_status is always computed from the feed group's activity_selectors (whatever they are configured to), not from any custom view's selectors. If you read a notification feed through a view whose selectors differ from the group's, the counts in this event can differ from what the view-scoped read returns.
Pagination
Pagination for notification (aggregated) feeds work the same way as it works for any other feed:
$feed = $feedsClient->feed('user', 'jack');
$feedResponse1 = $feed->getOrCreateFeed(
new GeneratedModels\GetOrCreateFeedRequest(userID: "jack", limit: 10)
);
$feedResponse2 = $feed->getOrCreateFeed(
new GeneratedModels\GetOrCreateFeedRequest(userID: "jack", limit: 10, next: $feedResponse1->getData()->next)
);Flat notification feeds
If you disable aggregation on a notification feed group while keeping notification tracking enabled, you get a flat notification feed: every notification is a single activity in the feed (no grouping). Unread and unseen counts then refer to individual activities rather than aggregated groups.
Unread/unseen count is computed from latest 100 activities. This means unread/unseen count will never exceed 100.
Turning off aggregation
Call updateFeedGroup server-side on the built-in notification feed group to turn off aggregation:
use GetStream\GeneratedModels\UpdateFeedGroupRequest;
use GetStream\GeneratedModels\NotificationConfig;
use GetStream\GeneratedModels\PushNotificationConfig;
$feedsClient->updateFeedGroup('notification', new UpdateFeedGroupRequest(
notification: new NotificationConfig(
trackRead: true,
trackSeen: true
),
pushNotification: new PushNotificationConfig(
enablePush: true,
pushTypes: [], // list notification types here, e.g. ['follow', 'comment', 'mention']
)
));Turning off aggregation won't remove existing notification activities; they'll be returned in the activities array.
Reading and paginating flat notifications
Load the first page with getOrCreate, then pass the returned next cursor on subsequent requests (or use your SDK’s pagination helpers). With aggregation off, use the activities list—not aggregated_activities.
$notificationFeed = $feedsClient->feed('notification', $userId);
$first = $notificationFeed->getOrCreate(
new GeneratedModels\GetOrCreateFeedRequest(userID: $userId, limit: 20)
);
$page1 = $first->getData()->activities;
$second = $notificationFeed->getOrCreate(
new GeneratedModels\GetOrCreateFeedRequest(
userID: $userId,
limit: 20,
next: $first->getData()->next,
)
);Per-activity is_seen and is_read
On flat notification feeds, each activity includes is_seen and is_read (when track_seen / track_read are enabled on the group). Use these booleans directly on each activity instead of on an aggregated group.
$activity = $first->getData()->activities[0];
$isRead = $activity->isRead;
$isSeen = $activity->isSeen;Marking flat notifications as seen or read
The API is the same as for aggregated notification feeds (markActivity), but you pass activity IDs in mark_seen / mark_read instead of aggregation group strings. mark_all_seen and mark_all_read still mark every notification on the feed.
$notificationFeed->markActivity(new GeneratedModels\MarkActivityRequest(
markSeen: [$activity->id],
userID: $userId,
));