# 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](/activity-feeds/docs/<framework>/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.

<codetabs>

<codetabs-item value="javascript" label="JavaScript">

```js
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}`,
  );
}
```

</codetabs-item>

<codetabs-item value="go" label="Go">

```go
trackResponse, err := feedsClient.TrackActivityMetrics(ctx, &getstream.TrackActivityMetricsRequest{
    Events: []getstream.TrackActivityMetricsEvent{
        {ActivityID: "activity_123", Metric: "views"},
        {ActivityID: "activity_123", Metric: "clicks", Delta: getstream.PtrTo(2)},
    },
    UserID: &userID,
})
if err != nil {
    log.Fatal("Error tracking activity metrics:", err)
}

for _, result := range trackResponse.Data.Results {
    fmt.Printf("%s/%s: allowed=%v\n", result.ActivityID, result.Metric, result.Allowed)
}
```

</codetabs-item>

<codetabs-item value="java" label="Java">

```java
TrackActivityMetricsResponse response = feeds.trackActivityMetrics(
    TrackActivityMetricsRequest.builder()
        .events(List.of(
            TrackActivityMetricsEvent.builder()
                .activityID("activity_123").metric("views").build(),
            TrackActivityMetricsEvent.builder()
                .activityID("activity_123").metric("clicks").delta(2).build()
        ))
        .userID("user_456")
        .build()
).execute().getData();

for (TrackActivityMetricsEventResult result : response.getResults()) {
    System.out.printf("%s/%s: allowed=%b%n",
        result.getActivityID(), result.getMetric(), result.getAllowed());
}
```

</codetabs-item>

<codetabs-item value="php" label="php">

```php
$response = $feedsClient->trackActivityMetrics(
    new GeneratedModels\TrackActivityMetricsRequest(
        events: [
            new GeneratedModels\TrackActivityMetricsEvent(
                activityID: 'activity_123',
                metric: 'views'
            ),
            new GeneratedModels\TrackActivityMetricsEvent(
                activityID: 'activity_123',
                metric: 'clicks',
                delta: 2
            ),
        ],
        userID: 'user_456'
    )
);

foreach ($response->getData()->results as $result) {
    printf(
        "%s/%s: allowed=%s\n",
        $result->activityID,
        $result->metric,
        $result->allowed ? 'true' : 'false'
    );
}
```

</codetabs-item>

<codetabs-item value="csharp" label="C#">

```csharp
var response = await _feedsV3Client.TrackActivityMetricsAsync(
    new TrackActivityMetricsRequest
    {
        Events = new List<TrackActivityMetricsEvent>
        {
            new TrackActivityMetricsEvent { ActivityID = "activity_123", Metric = "views" },
            new TrackActivityMetricsEvent { ActivityID = "activity_123", Metric = "clicks", Delta = 2 },
        },
        UserID = "user_456"
    }
);

foreach (var result in response.Data!.Results) {
    Console.WriteLine($"{result.ActivityID}/{result.Metric}: allowed={result.Allowed}");
}
```

</codetabs-item>

<codetabs-item value="python" label="Python">

```python
response = client.feeds.track_activity_metrics(
    events=[
        TrackActivityMetricsEvent(activity_id="activity_123", metric="views"),
        TrackActivityMetricsEvent(
            activity_id="activity_123",
            metric="clicks",
            delta=2,
        ),
    ],
    user_id="user_456",
)

for result in response.data.results:
    print(f"{result.activity_id}/{result.metric}: allowed={result.allowed}")
```

</codetabs-item>

<codetabs-item value="ruby" label="Ruby">

```ruby
track_request = GetStream::Generated::Models::TrackActivityMetricsRequest.new(
  events: [
    GetStream::Generated::Models::TrackActivityMetricsEvent.new(
      activity_id: 'activity_123',
      metric: 'views'
    ),
    GetStream::Generated::Models::TrackActivityMetricsEvent.new(
      activity_id: 'activity_123',
      metric: 'clicks',
      delta: 2
    )
  ],
  user_id: 'user_456'
)

response = client.feeds.track_activity_metrics(track_request)

response.results.each do |result|
  puts "#{result.activity_id}/#{result.metric}: allowed=#{result.allowed}"
end
```

</codetabs-item>

<codetabs-item value="nodejs" label="Node">

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

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

</codetabs-item>

</codetabs>

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

<codetabs>

<codetabs-item value="javascript" label="JavaScript">

```js
// 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
  },
});
```

</codetabs-item>

<codetabs-item value="go" label="Go">

```go
// Add custom metrics and override the default "views" limit
_, err = client.UpdateApp(ctx, &getstream.UpdateAppRequest{
    ActivityMetricsConfig: map[string]int{
        "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
    },
})
if err != nil {
    log.Fatal("Error updating app:", err)
}
```

</codetabs-item>

<codetabs-item value="java" label="Java">

```java
// Add custom metrics and override the default "views" limit
UpdateAppRequest request = UpdateAppRequest.builder()
    .activityMetricsConfig(Map.of(
        "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
    ))
    .build();
common.updateApp(request).execute();
```

</codetabs-item>

<codetabs-item value="php" label="php">

```php
// Add custom metrics and override the default "views" limit
$client->updateApp(new GeneratedModels\UpdateAppRequest(
    activityMetricsConfig: [
        '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
    ]
));
```

</codetabs-item>

<codetabs-item value="csharp" label="C#">

```csharp
// Add custom metrics and override the default "views" limit
await client.UpdateAppAsync(new UpdateAppRequest
{
    ActivityMetricsConfig = new Dictionary<string, int>
    {
        ["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
    }
});
```

</codetabs-item>

<codetabs-item value="python" label="Python">

```python
# Add custom metrics and override the default "views" limit
client.update_app(
    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
    }
)
```

</codetabs-item>

<codetabs-item value="ruby" label="Ruby">

```ruby
# Add custom metrics and override the default "views" limit
client.common.update_app(
  GetStream::Generated::Models::UpdateAppRequest.new(
    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
    }
  )
)
```

</codetabs-item>

<codetabs-item value="nodejs" label="Node">

```js
// Add custom metrics and override the default "views" limit (server-side)
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
  },
});
```

</codetabs-item>

</codetabs>

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

<codetabs>

<codetabs-item value="javascript" label="JavaScript">

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

</codetabs-item>

<codetabs-item value="go" label="Go">

```go
appResp, err := client.GetApp(ctx, &getstream.GetAppRequest{})
if err != nil {
    log.Fatal("Error getting app:", err)
}
fmt.Println(appResp.Data.App.ActivityMetricsConfig)
// map[shares:5 saves:10 views:20 impressions:0]
```

</codetabs-item>

<codetabs-item value="java" label="Java">

```java
GetAppResponse appResp = common.getApp().execute().getData();
Map<String, Integer> config = appResp.getApp().getActivityMetricsConfig();
System.out.println(config);
// {shares=5, saves=10, views=20, impressions=0}
```

</codetabs-item>

<codetabs-item value="php" label="php">

```php
$app = $client->getApp();
print_r($app->getData()->app->activityMetricsConfig);
// Array ( [shares] => 5 [saves] => 10 [views] => 20 [impressions] => 0 )
```

</codetabs-item>

<codetabs-item value="csharp" label="C#">

```csharp
var appResp = await client.GetAppAsync();
var config = appResp.Data!.App.ActivityMetricsConfig;

Console.WriteLine(System.Text.Json.JsonSerializer.Serialize(config));
// {"shares":5,"saves":10,"views":20,"impressions":0}
```

</codetabs-item>

<codetabs-item value="python" label="Python">

```python
app = client.get_app()
print(app.data.app.activity_metrics_config)
# {"shares": 5, "saves": 10, "views": 20, "impressions": 0}
```

</codetabs-item>

<codetabs-item value="ruby" label="Ruby">

```ruby
app = client.common.get_app
puts app.app.activity_metrics_config.inspect
# {"shares"=>5, "saves"=>10, "views"=>20, "impressions"=>0}
```

</codetabs-item>

<codetabs-item value="nodejs" label="Node">

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

</codetabs-item>

</codetabs>

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

<codetabs>

<codetabs-item value="javascript" label="JavaScript">

```js
await client.updateApp({ activity_metrics_config: {} });
```

</codetabs-item>

<codetabs-item value="go" label="Go">

```go
_, err = client.UpdateApp(ctx, &getstream.UpdateAppRequest{
    ActivityMetricsConfig: map[string]int{},
})
```

</codetabs-item>

<codetabs-item value="java" label="Java">

```java
common.updateApp(UpdateAppRequest.builder()
    .activityMetricsConfig(Map.of())
    .build()).execute();
```

</codetabs-item>

<codetabs-item value="php" label="php">

```php
$client->updateApp(new GeneratedModels\UpdateAppRequest(
    activityMetricsConfig: []
));
```

</codetabs-item>

<codetabs-item value="csharp" label="C#">

```csharp
await client.UpdateAppAsync(new UpdateAppRequest
{
    ActivityMetricsConfig = new Dictionary<string, int>()
});
```

</codetabs-item>

<codetabs-item value="python" label="Python">

```python
client.update_app(activity_metrics_config={})
```

</codetabs-item>

<codetabs-item value="ruby" label="Ruby">

```ruby
client.common.update_app(
  GetStream::Generated::Models::UpdateAppRequest.new(
    activity_metrics_config: {}
  )
)
```

</codetabs-item>

<codetabs-item value="nodejs" label="Node">

```js
await client.updateApp({ activity_metrics_config: {} });
```

</codetabs-item>

</codetabs>

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

<codetabs>

<codetabs-item value="javascript" label="JavaScript">

```js
// 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 },
  ],
});
```

</codetabs-item>

<codetabs-item value="go" label="Go">

```go
// Track metrics for multiple activities in one request
trackResponse, err := feedsClient.TrackActivityMetrics(ctx, &getstream.TrackActivityMetricsRequest{
    Events: []getstream.TrackActivityMetricsEvent{
        {ActivityID: "activity_1", Metric: "views"},
        {ActivityID: "activity_1", Metric: "clicks"},
        {ActivityID: "activity_2", Metric: "views"},
        {ActivityID: "activity_3", Metric: "impressions", Delta: getstream.PtrTo(5)},
    },
    UserID: &userID,
})
if err != nil {
    log.Fatal("Error tracking activity metrics:", err)
}
```

</codetabs-item>

<codetabs-item value="java" label="Java">

```java
// Track metrics for multiple activities in one request
TrackActivityMetricsResponse response = feeds.trackActivityMetrics(
    TrackActivityMetricsRequest.builder()
        .events(List.of(
            TrackActivityMetricsEvent.builder()
                .activityID("activity_1").metric("views").build(),
            TrackActivityMetricsEvent.builder()
                .activityID("activity_1").metric("clicks").build(),
            TrackActivityMetricsEvent.builder()
                .activityID("activity_2").metric("views").build(),
            TrackActivityMetricsEvent.builder()
                .activityID("activity_3").metric("impressions").delta(5).build()
        ))
        .userID("user_456")
        .build()
).execute().getData();
```

</codetabs-item>

<codetabs-item value="php" label="php">

```php
// Track metrics for multiple activities in one request
$response = $feedsClient->trackActivityMetrics(
    new GeneratedModels\TrackActivityMetricsRequest(
        events: [
            new GeneratedModels\TrackActivityMetricsEvent(activityID: 'activity_1', metric: 'views'),
            new GeneratedModels\TrackActivityMetricsEvent(activityID: 'activity_1', metric: 'clicks'),
            new GeneratedModels\TrackActivityMetricsEvent(activityID: 'activity_2', metric: 'views'),
            new GeneratedModels\TrackActivityMetricsEvent(
                activityID: 'activity_3',
                metric: 'impressions',
                delta: 5
            ),
        ],
        userID: 'user_456'
    )
);
```

</codetabs-item>

<codetabs-item value="csharp" label="C#">

```csharp
// Track metrics for multiple activities in one request
var response = await _feedsV3Client.TrackActivityMetricsAsync(
    new TrackActivityMetricsRequest
    {
        Events = new List<TrackActivityMetricsEvent>
        {
            new TrackActivityMetricsEvent { ActivityID = "activity_1", Metric = "views" },
            new TrackActivityMetricsEvent { ActivityID = "activity_1", Metric = "clicks" },
            new TrackActivityMetricsEvent { ActivityID = "activity_2", Metric = "views" },
            new TrackActivityMetricsEvent { ActivityID = "activity_3", Metric = "impressions", Delta = 5 },
        },
        UserID = "user_456"
    }
);
```

</codetabs-item>

<codetabs-item value="python" label="Python">

```python
# Track metrics for multiple activities in one request
response = client.feeds.track_activity_metrics(
    events=[
        TrackActivityMetricsEvent(activity_id="activity_1", metric="views"),
        TrackActivityMetricsEvent(activity_id="activity_1", metric="clicks"),
        TrackActivityMetricsEvent(activity_id="activity_2", metric="views"),
        TrackActivityMetricsEvent(
            activity_id="activity_3",
            metric="impressions",
            delta=5,
        ),
    ],
    user_id="user_456",
)
```

</codetabs-item>

<codetabs-item value="ruby" label="Ruby">

```ruby
# Track metrics for multiple activities in one request
track_request = GetStream::Generated::Models::TrackActivityMetricsRequest.new(
  events: [
    GetStream::Generated::Models::TrackActivityMetricsEvent.new(activity_id: 'activity_1', metric: 'views'),
    GetStream::Generated::Models::TrackActivityMetricsEvent.new(activity_id: 'activity_1', metric: 'clicks'),
    GetStream::Generated::Models::TrackActivityMetricsEvent.new(activity_id: 'activity_2', metric: 'views'),
    GetStream::Generated::Models::TrackActivityMetricsEvent.new(
      activity_id: 'activity_3',
      metric: 'impressions',
      delta: 5
    )
  ],
  user_id: 'user_456'
)

response = client.feeds.track_activity_metrics(track_request)
```

</codetabs-item>

<codetabs-item value="nodejs" label="Node">

```js
// Track metrics for multiple activities in one request (server-side)
const response = await client.feeds.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 },
  ],
  user_id: "user_456",
});
```

</codetabs-item>

</codetabs>

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

<codetabs>

<codetabs-item value="javascript" label="JavaScript">

```js
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}`);
  }
}
```

</codetabs-item>

<codetabs-item value="go" label="Go">

```go
trackResponse, err := feedsClient.TrackActivityMetrics(ctx, &getstream.TrackActivityMetricsRequest{
    Events: []getstream.TrackActivityMetricsEvent{
        {ActivityID: "activity_123", Metric: "views"},
    },
    UserID: &userID,
})
if err != nil {
    log.Fatal("Error tracking activity metrics:", err)
}

for _, result := range trackResponse.Data.Results {
    if !result.Allowed {
        fmt.Printf("Rate limited: %s/%s\n", result.ActivityID, result.Metric)
    }
}
```

</codetabs-item>

<codetabs-item value="java" label="Java">

```java
TrackActivityMetricsResponse response = feeds.trackActivityMetrics(
    TrackActivityMetricsRequest.builder()
        .events(List.of(
            TrackActivityMetricsEvent.builder()
                .activityID("activity_123").metric("views").build()
        ))
        .userID("user_456")
        .build()
).execute().getData();

for (TrackActivityMetricsEventResult result : response.getResults()) {
    if (!result.getAllowed()) {
        System.out.printf("Rate limited: %s/%s%n",
            result.getActivityID(), result.getMetric());
    }
}
```

</codetabs-item>

<codetabs-item value="php" label="php">

```php
$response = $feedsClient->trackActivityMetrics(
    new GeneratedModels\TrackActivityMetricsRequest(
        events: [
            new GeneratedModels\TrackActivityMetricsEvent(
                activityID: 'activity_123',
                metric: 'views'
            ),
        ],
        userID: 'user_456'
    )
);

foreach ($response->getData()->results as $result) {
    if (!$result->allowed) {
        printf("Rate limited: %s/%s\n", $result->activityID, $result->metric);
    }
}
```

</codetabs-item>

<codetabs-item value="csharp" label="C#">

```csharp
var response = await _feedsV3Client.TrackActivityMetricsAsync(
    new TrackActivityMetricsRequest
    {
        Events = new List<TrackActivityMetricsEvent>
        {
            new TrackActivityMetricsEvent { ActivityID = "activity_123", Metric = "views" },
        },
        UserID = "user_456"
    }
);

foreach (var result in response.Data!.Results) {
    if (!result.Allowed) {
        Console.WriteLine($"Rate limited: {result.ActivityID}/{result.Metric}");
    }
}
```

</codetabs-item>

<codetabs-item value="python" label="Python">

```python
response = client.feeds.track_activity_metrics(
    events=[TrackActivityMetricsEvent(activity_id="activity_123", metric="views")],
    user_id="user_456",
)

for result in response.data.results:
    if not result.allowed:
        print(f"Rate limited: {result.activity_id}/{result.metric}")
```

</codetabs-item>

<codetabs-item value="ruby" label="Ruby">

```ruby
response = client.feeds.track_activity_metrics(
  GetStream::Generated::Models::TrackActivityMetricsRequest.new(
    events: [
      GetStream::Generated::Models::TrackActivityMetricsEvent.new(
        activity_id: 'activity_123',
        metric: 'views'
      )
    ],
    user_id: 'user_456'
  )
)

response.results.each do |result|
  puts "Rate limited: #{result.activity_id}/#{result.metric}" unless result.allowed
end
```

</codetabs-item>

<codetabs-item value="nodejs" label="Node">

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

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

</codetabs-item>

</codetabs>

## Reading Metrics

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

<codetabs>

<codetabs-item value="javascript" label="JavaScript">

```js
// 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;
```

</codetabs-item>

<codetabs-item value="go" label="Go">

```go
// metrics is available on any activity response
activityResponse, err := feedsClient.GetActivity(ctx, "activity_123", &getstream.GetActivityRequest{})
if err != nil {
    log.Fatal("Error getting activity:", err)
}

activity := activityResponse.Data.Activity
fmt.Println(activity.Metrics)
// map[views:42 clicks:15 impressions:200]

// Access individual metrics
views := activity.Metrics["views"]   // 42
clicks := activity.Metrics["clicks"] // 15
```

</codetabs-item>

<codetabs-item value="java" label="Java">

```java
// metrics is available on any activity response
GetActivityResponse response = feeds.getActivity("activity_123").execute().getData();
ActivityResponse activity = response.getActivity();

Map<String, Integer> metrics = activity.getMetrics();
System.out.println(metrics);
// {views=42, clicks=15, impressions=200}

// Access individual metrics
int views = metrics.getOrDefault("views", 0);   // 42
int clicks = metrics.getOrDefault("clicks", 0); // 15
```

</codetabs-item>

<codetabs-item value="php" label="php">

```php
// metrics is available on any activity response
$response = $feedsClient->getActivity('activity_123');
$activity = $response->getData()->activity;

print_r($activity->metrics);
// Array ( [views] => 42 [clicks] => 15 [impressions] => 200 )

// Access individual metrics
$views = $activity->metrics['views'] ?? 0;
$clicks = $activity->metrics['clicks'] ?? 0;
```

</codetabs-item>

<codetabs-item value="csharp" label="C#">

```csharp
// metrics is available on any activity response
var response = await _feedsV3Client.GetActivityAsync("activity_123");
var activity = response.Data!.Activity;

Console.WriteLine(System.Text.Json.JsonSerializer.Serialize(activity.Metrics));
// {"views":42,"clicks":15,"impressions":200}

// Access individual metrics
var views = activity.Metrics["views"];
var clicks = activity.Metrics["clicks"];
```

</codetabs-item>

<codetabs-item value="python" label="Python">

```python
# metrics is available on any activity response
response = client.feeds.get_activity("activity_123")
activity = response.data.activity

print(activity.metrics)
# {"views": 42, "clicks": 15, "impressions": 200}

# Access individual metrics
views = activity.metrics.get("views", 0)
clicks = activity.metrics.get("clicks", 0)
```

</codetabs-item>

<codetabs-item value="ruby" label="Ruby">

```ruby
# metrics is available on any activity response
response = client.feeds.get_activity('activity_123')
activity = response.activity

puts activity.metrics.inspect
# {"views"=>42, "clicks"=>15, "impressions"=>200}

# Access individual metrics
views = activity.metrics['views'] || 0
clicks = activity.metrics['clicks'] || 0
```

</codetabs-item>

<codetabs-item value="nodejs" label="Node">

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

</codetabs-item>

</codetabs>

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:

<codetabs>

<codetabs-item value="javascript" label="JavaScript">

```js
// 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" }],
});
```

</codetabs-item>

<codetabs-item value="go" label="Go">

```go
// Create a feed group ranked by engagement metrics
scoreFormula := "metrics.views * 3 + metrics.clicks * 2 + popularity"
createResponse, err := feedsClient.CreateFeedGroup(ctx, &getstream.CreateFeedGroupRequest{
    ID: "trending",
    ActivitySelectors: []getstream.ActivitySelectorConfig{
        {Type: "following"},
    },
    Ranking: &getstream.RankingConfig{
        Type:  "expression",
        Score: &scoreFormula,
        Defaults: map[string]any{
            "metrics": map[string]any{
                "views":  0.0,
                "clicks": 0.0,
            },
        },
    },
})
if err != nil {
    log.Fatal("Error creating feed group:", err)
}
```

</codetabs-item>

<codetabs-item value="java" label="Java">

```java
// Create a feed group ranked by engagement metrics
CreateFeedGroupResponse response = feeds.createFeedGroup(
    CreateFeedGroupRequest.builder()
        .id("trending")
        .activitySelectors(List.of(
            ActivitySelectorConfig.builder().type("following").build()
        ))
        .ranking(RankingConfig.builder()
            .type("expression")
            .score("metrics.views * 3 + metrics.clicks * 2 + popularity")
            .defaults(Map.of(
                "metrics", Map.of(
                    "views", 0.0,
                    "clicks", 0.0
                )
            ))
            .build())
        .build()
).execute().getData();
```

</codetabs-item>

<codetabs-item value="nodejs" label="Node">

```js
// 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" }],
});
```

</codetabs-item>

</codetabs>

<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-04-03T17:20:23.724Z.

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