# Reactions

## Overview

You can react to both activities and comments. It's possible to configure any reaction types that best fit your app.

When adding reactions and the `enforce_unique` flag is set to `true`, the existing reaction of a user will be overridden with the new reaction. Use this flag if you want to ensure users have a single reaction per each comment/activity. The default value is `false`, in which case users can have multiple reactions. The response tells you whether the reaction was new or replaced an existing one — see [Reaction write outcome](#reaction-write-outcome).

```kotlin label="Kotlin"
// Add a reaction to an activity
val reaction: Result<FeedsReactionData> = feed.addActivityReaction(
    activityId = "activity_123",
    request = AddReactionRequest(
        custom = mapOf("emoji" to "❤️"),
        type = "like",
        // Optionally override existing reaction
        enforceUnique = true
    )
)

// Remove a reaction
val deleteResult: Result<FeedsReactionData> = feed.deleteActivityReaction(
    activityId = "activity_123",
    type = "like"
)
```

```java label="Java"
AddReactionRequest reactionRequest =
    AddReactionRequest.builder().type("like").userID(testUserId).build();

AddReactionResponse response =
    feeds.addReaction(activityId, reactionRequest).execute().getData();
```

### Overview of the reaction model

#### FeedsReactionResponse

| Name | Type | Description | Constraints |
| --- | --- | --- | --- |
| `activity_id` | `string` | - | Required |
| `comment_id` | `string` | - | - |
| `created_at` | `number` | - | Required |
| `custom` | `object` | - | - |
| `type` | `string` | - | Required |
| `updated_at` | `number` | - | Required |
| `user` | `UserResponse` | - | Required |

By default creating a reaction doesn't create an activity. Pass `create_notification_activity: true` to add a notification to the reacted-to user's notification feed — see [Reaction notification creation timing](https://getstream.io/activity-feeds/docs/android/notification-feeds/#reaction-notification-creation-timing) (added June 23, 2026). The response includes `notification_accepted` and `notification_task_id`; `notification_created` is deprecated and mirrors `notification_accepted`.

### Reaction write outcome

Adding a reaction returns what the write actually did, so you can keep your own reaction counters from a single request instead of reading state first or deleting before adding.

| field                    | type             | description                                                                                          |
| ------------------------ | ---------------- | ---------------------------------------------------------------------------------------------------- |
| `outcome`                | string           | One of `created`, `replaced`, `unchanged`. What the write did to this user's reaction on the target. |
| `previous_reaction_type` | string or `null` | The reaction type that was replaced. Non-null exactly when `outcome` is `replaced`.                  |
| `counter_delta`          | number           | The change to the number of reactions this user holds on the target: `1` or `0`.                     |

| `outcome`   | when                                                                               | `previous_reaction_type` | `counter_delta` |
| ----------- | ---------------------------------------------------------------------------------- | ------------------------ | --------------- |
| `created`   | A new reaction was written and nothing was replaced.                               | `null`                   | `1`             |
| `replaced`  | `enforce_unique` removed one or more of the user's other reaction types.           | the removed type         | `0`             |
| `unchanged` | The user already held this reaction type. Its `custom` data may have been updated. | `null`                   | `0`             |

`replaced` only happens with `enforce_unique: true` — without it nothing is ever removed.

To maintain a per-user reaction counter:

- **Adding**: increment when `outcome` is `created`, otherwise leave it alone. This is what `counter_delta` already gives you.
- **Deleting**: a successful delete always removed a row, so decrement on `2xx`. A `404` means there was nothing to remove. These endpoints never return `counter_delta: -1`.

Without `enforce_unique` a user can hold several reaction types on the same target, so `created` there means _this reaction type was newly added_, not _the user's first reaction on this target_. The two only coincide when `enforce_unique: true` is set, which is the mode to use if you are counting one reaction per user per target.

Re-sending the same reaction type with `enforce_unique: true` updates the existing reaction in place and preserves its original `created_at`.

When you read a feed the reactions are included. Here's an example:

```kotlin label="Kotlin"
feed.getOrCreate()
feed.state.activities.collect { activities ->
    // Last 15 reactions on the first activity
    println(activities.first().latestReactions)
    // Count of reactions by type
    println(activities.first().reactionGroups)
}
```

```java label="Java"
Map<String, Object> filter = new HashMap<>();
filter.put("reaction_type", "like");

QueryActivityReactionsRequest queryRequest =
    QueryActivityReactionsRequest.builder().limit(10).filter(filter).build();

QueryActivityReactionsResponse response =
    feeds.queryActivityReactions(activityId, queryRequest).execute().getData();
```

## Querying Reactions

You can query reactions to both activities and comments. Here are some examples:

```kotlin label="Kotlin"
// Query reactions to a specific activity
val activityReactionList = client.activityReactionList(
    ActivityReactionsQuery(
        activityId = "activity-123",
        filter = ActivityReactionsFilterField.reactionType.equal("like"),
    )
)
activityReactionList.get()
activityReactionList.state.reactions.collect { reactions ->
    // Handle activity reactions
}

// Query reactions to a specific comment
val commentReactionList = client.commentReactionList(
    CommentReactionsQuery(
        commentId = "comment-456",
        filter = CommentReactionsFilterField.reactionType.equal("like"),
    )
)
commentReactionList.get()
commentReactionList.state.reactions.collect { reactions ->
    // Handle comment reactions
}
```

## Batch query activity reactions

`batchQueryActivityReactions` fetches a single user's reactions across a whole page of activities in one call, so you can merge `own_reactions` back onto a cached page without calling [query reactions](#querying-reactions) once per activity. Pass up to 100 activity IDs per request. The response contains reaction rows only (no activity payloads) and supports the same `filter`, `sort`, and pagination options as the single-activity query.

On a client-side connection the returned reactions are always the connected user's. Server-side, pass `user_id` to choose whose reactions to fetch.

```js label="JavaScript"
// Fetch the connected user's reactions across a page of activities in one request.
const response = await client.batchQueryActivityReactions({
  activity_ids: activities.map((activity) => activity.id),
  // Optional: filter by reaction type, sort, or paginate with next/prev
  filter: { reaction_type: "like" },
});

// Merge response.reactions back onto the activities as own_reactions
console.log(response.reactions);
```

### Reaction queryable built-in fields

Both `batchQueryActivityReactions` and `batchQueryCommentReactions` accept these `filter` fields.

| name            | type                                              | description                                 | supported operations                | example                                                        |
| --------------- | ------------------------------------------------- | ------------------------------------------- | ----------------------------------- | -------------------------------------------------------------- |
| `reaction_type` | string or list of strings                         | The type of reaction                        | `$in`, `$eq`                        | `{ reaction_type: { $in: [ 'like', 'heart', 'thumbs_up' ] } }` |
| `user_id`       | string or list of strings                         | The ID of the user who created the reaction | `$in`, `$eq`                        | `{ user_id: { $eq: 'user_123' } }`                             |
| `created_at`    | string, must be formatted as an RFC3339 timestamp | The time the reaction was created           | `$eq`, `$gt`, `$gte`, `$lt`, `$lte` | `{ created_at: { $gte: '2023-12-04T09:30:20.45Z' } }`          |

## Batch query comment reactions

`batchQueryCommentReactions` is the comment counterpart of [batch query activity reactions](#batch-query-activity-reactions). It fetches a single user's reactions across up to 100 comments in one call, returning reaction rows only (no comment payloads). Use it to hydrate `own_reactions` for a whole comment thread without a request per comment. It accepts the same [filter fields](#reaction-queryable-built-in-fields), `sort`, and pagination options.

As with activities, a client-side connection returns the connected user's reactions; server-side, pass `user_id`.

```js label="JavaScript"
const response = await client.batchQueryCommentReactions({
  comment_ids: comments.map((comment) => comment.id),
  filter: { reaction_type: "like" },
});

console.log(response.reactions);
```

---

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