Activity Feeds v3 is in beta — try it out!

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:

// 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);

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. 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.

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->addReaction("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"
));

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_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-in notification feed and create_notification_activity flag

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,
      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,
      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,
      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",
            },
          },
        },
      ],
    },
  ];
}

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?

Unread/unseen count is computed from the last 1000 activities of the feed, and aggregated into maximum 100 groups. This means that unread/unseen count will never be more than 100.

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:

use GetStream\GeneratedModels;

{
  "notificationStatus": {
    // Number of unread notifications
    "unread": 12,
    // Number of unseen notifications
    "unseen": 0,
    "lastSeenAt": // DateTime object with last seen timestamp,
    "seenActivities": [],
    "lastReadAt": // DateTime object with last read timestamp,
    "readActivities": ["activity123-reaction-2025-08-04"],
  }
}

// Check if a group is read/seen
$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['updated_at'] < $lastReadAt->getTimestamp() * 1e+9) || ($readActivities && in_array($group['group'], $readActivities));
$isSeen = ($lastSeenAt && $group['updated_at'] < $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);

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)
);
© Getstream.io, Inc. All Rights Reserved.