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

>
> **Info:** 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:

```js label="Node.js"
const myFeedGroup = await serverClient.feeds.createFeedGroup({
  id: "myid",
  activity_selectors: [
    {
      type: "following",
    },
  ],
  ranking: { type: "recency" },
  activity_processors: [
    {
      type: "text_interest_tags",
    },
  ],
  activity_filter: {
    exclude_owner_activities: true,
  },
  custom: {
    description: "My custom feed group",
  },
});
```

>
> **Info:** Applications can't have more than 100 feed groups
>

>
> **Info:** 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:jimmy` hides activities where `user_id` is `jimmy`.
- Reading `user:amy` still shows activities fanned out from `user: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`, or `stories` built-in groups
- feed groups that also set `aggregation`, `notification`, or `stories` config

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.                                                                                                        |
| `activity_marks`        | Per-activity `is_seen` / `is_read` tracking for flat content 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).                                                                                                           |

## Activity marks on content feeds

Use `activity_marks` when a flat timeline or other content feed needs exact
per-activity `is_seen` / `is_read` values without turning the group into a
notification feed:

```js label="Node.js"
await serverClient.feeds.createFeedGroup({
  id: "ranked_timeline",
  activity_marks: {
    track_seen: true,
    track_read: true,
  },
  activity_selectors: [{ type: "following" }],
});
```

Each activity returned from the feed includes the enabled fields. Mark
activities with `mark_seen` or `mark_read` and explicit activity IDs.
`mark_all_seen` and `mark_all_read` are notification-only and are rejected on
content feeds, because older content can enter a personalized feed later.

```js label="Node.js"
await timeline.markActivity({
  mark_seen: [activity.id],
  user_id: userId,
});
```

`activity_marks` cannot be combined with `notification`, `aggregation`, or
stories configuration. A request may contain at most 100 unique activity IDs.
The service retains approximately the newest 1000 marks per user and feed.

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

```js label="Node.js"
await client.feeds.createFeedGroup({
  id: "premium",
  default_follower_role: "feed_member_viewer",
});
```

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](https://getstream.io/activity-feeds/docs/node/feed-and-activity-visibility/#custom-follower-roles) 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](https://getstream.io/activity-feeds/docs/node/feed-and-activity-visibility/#follower-permission-tiers) for the per-level breakdown.

## List feed groups

To list existing feed groups and their configurations:

```js label="Node.js"
await client.feeds.listFeedGroups();
```

## Activity Ranking

When ranking activities you can specify a ranking formula.

```js label="Node.js"
const response = await serverClient.feeds.createFeedGroup({
  id: "mytimeline",
  ranking: { type: "expression", score: "decay_linear(time) * popularity" },
  activity_selectors: [
    {
      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](https://getstream.io/activity-feeds/docs/node/activity-processors/) to add topic data to activities, [activity selectors](https://getstream.io/activity-feeds/docs/node/activity-selectors/) to pull in content from different sources, and ranks content you're likely to engage with higher in the feed.

```js label="Node.js"
const response = await serverClient.feeds.createFeedGroup({
  id: "mytimeline",
  // Run the activity processors to analyse topics for text & images
  activity_processors: [
    { type: "text_interest_tags" },
    { type: "image_interest_tags" },
  ],
  // Activity selectors change which activities are included in the feed
  // The default "following" selectors gets activities from the feeds you follow
  // The "popular" activity selectors includes the popular activities
  // And "interest" activities similar to activities you've engaged with in the past
  // You can use multiple selectors in 1 feed
  activity_selectors: [
    {
      type: "popular",
    },
    {
      type: "following",
    },
    {
      type: "interest",
    },
  ],
  // Rank for a user based on interest score
  // This calculates a score 0-1.0 of how well the activity matches the user's prior interest
  ranking: {
    type: "interest",
    score: "decay_linear(time) * interest_score * decay_linear(popularity)",
  },
});

const forYouFeed = client.feeds.feed("mytimeline", "thierry");
const response = await forYouFeed.getOrCreate({ user_id: "thierry" });
```

## 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:

```js label="Node.js"
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 Feed Example

The built-in `notification` feed comes with the necessary configurations:

```js label="JavaScript"
// Read notifications
await notificationFeed.getOrCreate({
  limit: 20,
});
console.log(notificationFeed.currentState.is_loading_activities);
console.log(notificationFeed.currentState.aggregated_activities);

await notificationFeed.getNextPage();
```

```js label="Node.js"
const notificationFeed = client.feed("notification", "jane");

// Read notifications
const notifications = (
  await notificationFeed.getOrCreate({
    limit: 20,
    user_id: "<user_id>",
  })
).aggregated_activities;
```

### Marking Notifications as Read

```js label="JavaScript"
await notificationFeed.markActivity({
  // Mark all notifications as read...
  mark_all_read: true,
  // ...or only selected ones
  mark_read: [
    /* group names to mark as read */
  ],
});
```

```js label="Node.js"
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>",
});
```

## Updating feed groups

It's possible to update any custom or built-in feed group:

Updates replace every field. Omitting `custom`, `ranking`, `aggregation`, `notification`, `activity_marks`, `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](https://getstream.io/activity-feeds/docs/node/changing-feed-visibility/), which also reconciles that feed's follows.

```js label="Node.js"
await client.feeds.updateFeedGroup({
  id: "<id of feed group to update>",
  // Fields to update
});
```

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

```js label="Node.js"
await serverClient.feeds.deleteFeedGroup({
  id: "<feed group id to delete>",
});
```

---

For the most recent version of this documentation, visit [https://getstream.io/activity-feeds/docs/node/feed-groups/](https://getstream.io/activity-feeds/docs/node/feed-groups/).