# Read/seen ranking

When `track_read` or `track_seen` is enabled on a feed group, you can use `is_read` and `is_seen` as variables in ranking expressions. On flat content feeds, enable them with `activity_marks`; notification feeds use their `notification` configuration. On content feeds, the ranking expression may only reference `is_read` or `is_seen` when the matching `activity_marks.track_read` or `activity_marks.track_seen` is enabled, otherwise creating or updating the feed group fails with `ranking by read/seen status requires notification config or matching activity_marks tracking`. Notification feeds can always rank by `is_read` and `is_seen`.

A mark only applies to activities created at or before the time it was set. On aggregated notification feeds, a group that is updated after it was marked counts as unread or unseen again.

>
> **Info:** Ranking by `is_read` / `is_seen` is only available on **Enterprise** plans. [Contact support](https://getstream.io/contact/) to enable this feature for your organization.
>

For example, a custom flat feed group that sorts by popularity and subtracts 100 points from activities the user has read:

```rb label="Ruby"
require 'getstream_ruby'

client.feeds.create_feed_group(
  GetStream::Generated::Models::CreateFeedGroupRequest.new(
    id: 'ranked_timeline',
    activity_marks: GetStream::Generated::Models::ActivityMarksConfig.new(
      track_seen: true,
      track_read: true
    ),
    activity_selectors: [
      GetStream::Generated::Models::ActivitySelectorConfig.new(type: 'following')
    ],
    ranking: GetStream::Generated::Models::RankingConfig.new(
      type: 'expression',
      score: 'popularity - (is_read ? 100 : 0)'
    )
  )
)
```

To rank unseen activities first instead (this does not include popularity):

```rb label="Ruby"
require 'getstream_ruby'

client.feeds.create_feed_group(
  GetStream::Generated::Models::CreateFeedGroupRequest.new(
    id: 'unseen_timeline',
    activity_marks: GetStream::Generated::Models::ActivityMarksConfig.new(
      track_seen: true,
      track_read: true
    ),
    activity_selectors: [
      GetStream::Generated::Models::ActivitySelectorConfig.new(type: 'following')
    ],
    ranking: GetStream::Generated::Models::RankingConfig.new(
      type: 'expression',
      score: 'is_seen ? 0 : 100'
    )
  )
)
```

The same expressions work on notification feeds, where marks may be resolved per activity or aggregated group.

## Example expressions

| Expression                                            | Effect                                   |
| ----------------------------------------------------- | ---------------------------------------- |
| `is_seen ? 0 : 100`                                   | Unseen activities ranked first           |
| `is_read ? 0 : 100`                                   | Unread activities ranked first           |
| `popularity - (is_read ? 10 : 0)`                     | Penalize read activities by 10 points    |
| `popularity - (is_read ? 10 : 0) - (is_seen ? 5 : 0)` | Penalize both read and seen activities   |
| `(is_seen ? 0 : 1000) + decay_linear(time)`           | Unseen activities first, with time decay |

## Aggregated feeds

On aggregated notification feeds, `is_read` and `is_seen` are resolved at the **group level**. All activities within an aggregated group share the same read/seen status. On **flat** feeds (content feeds with `activity_marks`, and flat notification feeds), they are resolved per activity. Content feeds with aggregation can't use `activity_marks`, so they can't rank by read/seen status.

>
> **Info:** Ranking by `is_read` / `is_seen` on content feeds requires matching `activity_marks.track_read` / `activity_marks.track_seen` configuration. `activity_marks` cannot be combined with notification, aggregation, or stories configuration.
>

---

For the most recent version of this documentation, visit [https://getstream.io/activity-feeds/docs/ruby/read-seen-ranking/](https://getstream.io/activity-feeds/docs/ruby/read-seen-ranking/).