Activity Feeds V3 is in closed alpha — do not use it in production (just yet).

Aggregation

Aggregated feeds are helpful if you want to group activities. Here are some examples of what you can achieve using aggregated feeds:

  • ‘Eric followed 10 people’

  • ‘Julie and 14 others liked your photo’

This page shows what variables and syntax you can use to create aggregation expressions.

Creating aggregated feeds

You can create feed groups with aggregation using the API:

const aggregation_view = await serverClient.feeds.createFeedView({
  view_id: uuidv4(),
  // Group by activity type and day
  aggregation: { format: '{{ type }}-{{ time.strftime("%Y-%m-%d") }}' },
});

const myNotificationGrpup = await serverClient.feeds.createFeedGroup({
  feed_group_id: "myid",
  default_view_id: aggregation_view.feed_view.view_id,
  // Enable notification tracking
  notification: {
    track_read: true,
    track_seen: true,
  },
});

Reading aggregated feeds

When you insert an activity to an aggregated feed we apply the aggregation format. When reading the feed you can read the aggregated activities, together with the latest activities of that group.

Here is an example API response for reading an aggregated feed:

{
  "aggregated_activities": [
    {
      "activities": [
        /** latest non-aggregated activities of the group */
      ],
      "activity_count": 3, // how many activities are there in the group?
      "user_count": 2, // how many different users are in the group?
      "group": "reaction-2025-07-30", // reaction activities for the given day
      "score": 0, // score of the group, used when ranking is configured as well
      "created_at": 1753883232814202000,
      "updated_at": 1753883232814202000
    }
  ],
  "activities": [
    /** non-aggregated activities of the feed */
  ]
}

Aggregation Format Syntax

The following variables are available:

  • type: the type field of the activity (for example: follow)
  • time: creation time of the activity
  • user_id: the id of the user field of the activity
  • custom: the custom field of the activity, nested fields can be accessed with . notation, for example: custom.topic
  • target_id: Only defined for notification activities added with the create_notification_activity flag. The id of the activity which triggered the notification (activity.notification_context.target.id field)
  • id: the id of the activity (this is a unique field and turns off aggregation, you shouldn’t need to use this field in your aggregation expression)

Here are some common examples of aggregation formats:

  • Per user, activity type and day: {{ user_id }}_{{ type }}_{{ time.strftime("%Y-%m-%d") }}

  • Per activity type and day: {{ type }}_{{ time.strftime("%Y-%m-%d") }}

  • Follows by user, everything else grouped by verb and day

{% if verb == 'follow' %}
  {{ user_id }}_{{ type }}_{{ time.strftime("%Y-%m-%d") }}
{% else %}
  {{ type }}_{{ time.strftime("%Y-%m-%d") }}
{% endif %}

The aggregation rule uses the Jinja2 Template Syntax. Control structures like if and else are available, as well as these filters: int, lower and strftime to format the time variable. The options for strftime are documented here. Please get in touch with support if you need help with your aggregation formula.

Notification Feeds

Most often the reason you want to enable aggregation on a feed is to create a notification feed. To fully support this use-case, we also should be able to track which notifications are seen or read by users. To achieve this you can turn on notification tracking for feed groups.

Follow the Notification guide to learn more about notification feeds.

© Getstream.io, Inc. All Rights Reserved.