# Activity Metrics

Activity metrics let you track custom counters like views, clicks, and impressions for activities. These metrics are stored on the activity and can be used in [ranking expressions](https://getstream.io/activity-feeds/docs/flutter/custom-ranking/) to build engagement-driven feeds.

<Admonition type="info">

Activity metrics require a **paid Feeds plan**. Free-tier applications cannot use this feature. Paid (self-service) plans get the default metrics listed below. Enterprise plans can additionally configure up to **10 custom metrics** per app.

</Admonition>

## Tracking Metrics

Track one or more metric events in a single request. Each event specifies an activity, a metric name, and an optional delta (defaults to 1). Events are independently rate-limited per user per activity per metric.

```js label="JavaScript"
const response = await client.trackActivityMetrics({
  events: [
    { activity_id: "activity_123", metric: "views" },
    { activity_id: "activity_123", metric: "clicks", delta: 2 },
  ],
});

for (const result of response.results) {
  console.log(
    `${result.activity_id}/${result.metric}: allowed=${result.allowed}`,
  );
}
```

<Admonition type="info">

Server-side calls must include `user_id`. Client-side calls use the authenticated user's ID automatically.

</Admonition>

## Default Metrics

All paid plans include the following built-in metrics:

| Metric        | Max per hour per user per activity |
| ------------- | ---------------------------------: |
| `views`       |                                 10 |
| `clicks`      |                                  5 |
| `impressions` |                                 50 |

These defaults are always available. Enterprise apps can override their rate limits or disable them by setting the limit to `0` via [custom metrics configuration](#custom-metrics-configuration).

<Admonition type="info">

Metric names must be alphanumeric with underscores or dashes only, and a maximum of 64 characters (e.g. `views`, `link_clicks`, `video-watch-time`).

</Admonition>

## Custom Metrics Configuration

Enterprise plans can configure up to **10 custom metrics** per app using the `updateApp` API. Custom metrics are merged on top of the defaults:

- **Add a new metric**: Include it in `activity_metrics_config` with a rate limit value.
- **Override a default's rate limit**: Include the default metric name with a new limit value.
- **Disable a default metric**: Set its value to `0`.
- **Unmentioned defaults are preserved**: Only metrics you explicitly include in the config are affected.

```js label="JavaScript"
// Add custom metrics and override the default "views" limit
await client.updateApp({
  activity_metrics_config: {
    shares: 5, // new custom metric: 5 per hour per user per activity
    saves: 10, // new custom metric: 10 per hour
    views: 20, // override default views limit from 10 to 20
    impressions: 0, // disable the default impressions metric
  },
});
```

The resulting effective config for this app would be:

| Metric        | Limit | Source                       |
| ------------- | ----: | ---------------------------- |
| `views`       |    20 | overridden from default (10) |
| `clicks`      |     5 | default (unchanged)          |
| `impressions` |     — | disabled by custom config    |
| `shares`      |     5 | custom                       |
| `saves`       |    10 | custom                       |

You can read back the current custom config via `getApp`:

```js label="JavaScript"
const app = await client.getApp();
console.log(app.app.activity_metrics_config);
// { shares: 5, saves: 10, views: 20, impressions: 0 }
```

To clear all custom overrides and revert to defaults, set an empty config:

```js label="JavaScript"
await client.updateApp({ activity_metrics_config: {} });
```

<Admonition type="info">

Custom metrics configuration is available on **Enterprise plans only**. Paid self-service plans use the default metrics. The `activity_metrics_config` returned by `getApp` only contains your custom overrides — default metrics that have not been overridden are not listed.

</Admonition>

## Delta Values

The `delta` field controls how much the metric counter changes:

- **Positive delta**: Increments the metric (e.g. `delta: 5` adds 5)
- **Negative delta**: Decrements the metric (e.g. `delta: -1` subtracts 1)
- **Default**: If `delta` is omitted, it defaults to `1`

<Admonition type="warning">

The **absolute value** of delta counts against the rate limit. For example, `delta: 100` counts as 100 against the limit, not just 1.

</Admonition>

## Batching

You can track up to **100 events** in a single request. Events can target different activities and different metrics. Each event is independently rate-limited.

```js label="JavaScript"
// Track metrics for multiple activities in one request
const response = await client.trackActivityMetrics({
  events: [
    { activity_id: "activity_1", metric: "views" },
    { activity_id: "activity_1", metric: "clicks" },
    { activity_id: "activity_2", metric: "views" },
    { activity_id: "activity_3", metric: "impressions", delta: 5 },
  ],
});
```

## Rate Limiting

Each metric is rate-limited per user (or IP) per activity per metric within a 1-hour sliding window. When the limit is exceeded, the event is rejected and `allowed` is set to `false` in the response — this is not an error, just a signal that the event was not counted.

Always check the `allowed` field in the response:

```js label="JavaScript"
const response = await client.trackActivityMetrics({
  events: [{ activity_id: "activity_123", metric: "views" }],
});

for (const result of response.results) {
  if (!result.allowed) {
    console.log(`Rate limited: ${result.activity_id}/${result.metric}`);
  }
}
```

## Reading Metrics

Metrics are included in the activity response as a `metrics` object:

```js label="JavaScript"
// metrics is available on any activity response
const activity = await client.getActivity({ id: "activity_123" });
console.log(activity.metrics);
// { views: 42, clicks: 15, impressions: 200 }

// Access individual metrics
const views = activity.metrics?.views ?? 0;
const clicks = activity.metrics?.clicks ?? 0;
```

The raw JSON response looks like this:

```json
{
  "id": "activity_123",
  "type": "post",
  "text": "Hello world",
  "metrics": {
    "views": 42,
    "clicks": 15,
    "impressions": 200
  }
}
```

<Admonition type="info">

Metrics are synced from the buffer to the database every **5 minutes**.

</Admonition>

## Using Metrics in Ranking

Metrics can be referenced in ranking expressions to build engagement-driven feeds. Use the `metrics` object in your ranking score expression:

```js label="JavaScript"
// Create a feed group ranked by engagement metrics
const response = await serverClient.feeds.createFeedGroup({
  id: "trending",
  ranking: {
    type: "expression",
    score: "metrics.views * 2 + metrics.clicks * 3",
    defaults: {
      metrics: {
        views: 0,
        clicks: 0,
      },
    },
  },
  activity_selectors: [{ type: "following" }],
});
```

<Admonition type="info">

When using metrics in ranking expressions, you **must** provide defaults for each metric. Activities without any tracked metrics need default values for the expression to evaluate correctly.

</Admonition>

### Ranking Expression Examples

```go
// Simple view-based ranking
metrics.views

// Weighted engagement score
metrics.views * 2 + metrics.clicks * 3 + metrics.impressions * 0.1

// Combined with popularity and time decay
decay_linear(time) * (popularity + metrics.views * 0.5)

// Conditional boost for popular content
metrics.views > 100 ? popularity * 2 : popularity
```

## Limitations

- **Paid plan required**: Activity metrics are not available on the free tier
- **Custom metrics (Enterprise only)**: Up to 10 custom metrics per app; self-service plans use defaults only
- **Metric names**: Alphanumeric characters, dashes, and underscores only; maximum 64 characters
- **Sync delay**: ~5 minutes between tracking and availability in responses/ranking
- **No filtering**: Metrics cannot be used in query filters (only in ranking expressions)
- **Rate limit windows**: Rate limits use 1-hour time windows, not permanent deduplication
- **Batch size**: Maximum 100 events per request


---

This page was last updated at 2026-08-07T20:37:29.606Z.

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