# Collections

## Overview

Collections provide a way to attach data to activities that is shared between these activities and can be updated in a single place.
A typical use case is to track the status of an activity such as `started`, `completed`, or `failed`. Instead of having to update multiple activities when the status changes you can create a collection and add it to the activities that needs this data. When the status changes you only need to update the collection.

A collection consists of a `name`, an `id` and some data. You can think of the `name` as a namespace for the collection. A **collection reference** is a string that looks like `<name>:<id>` and is the unique identifier for the collection.

>
> **Info:** Data stored in collections can not be used in custom ranking or aggregation.
>

>
> **Info:** The batch endpoints supports up to 100 collections in a single request.
> Automatic deduplication will be applied, keeping the last occurrence only.
>

## Creating Collections

The example below shows how to create a collection.

```js label="JavaScript"
const response = await client.createCollections({
  collections: [
    {
      name: "movies",
      id: "lord_of_the_rings",
      custom: {
        title: "Lord of the Rings",
        genre: "fantasy",
        rating: 9,
      },
    },
  ],
});
```

```js label="Node.js"
const response = await client.feeds.createCollections({
  collections: [
    {
      name: "movies",
      id: "lord_of_the_rings",
      custom: {
        title: "Lord of the Rings",
        genre: "fantasy",
        rating: 9,
      },
    },
  ],
});
```

### Overview of the collection model

#### CollectionResponse

| Name | Type | Description | Constraints |
| --- | --- | --- | --- |
| `created_at` | `number` | When the collection was created | - |
| `custom` | `object` | Custom data for the collection | - |
| `id` | `string` | Unique identifier for the collection within its name | Required |
| `name` | `string` | Name/type of the collection | Required |
| `updated_at` | `number` | When the collection was last updated | - |
| `user_id` | `string` | ID of the user who owns this collection | - |

## Updating Collections

The example below shows how to update a collection.

```js label="JavaScript"
const response = await client.updateCollections({
  collections: [
    {
      name: "movies",
      id: "lord_of_the_rings",
      custom: {
        title: "Lord of the Rings",
        genre: "fantasy",
        rating: 9.5,
      },
    },
  ],
});
```

```js label="Node.js"
const response = await client.feeds.updateCollections({
  collections: [
    {
      name: "movies",
      id: "lord_of_the_rings",
      custom: {
        title: "Lord of the Rings",
        genre: "fantasy",
        rating: 9.5,
      },
    },
  ],
});
```

## Upserting Collections

The upsert endpoint creates a collection if it does not exist, or updates it if it already exists. This is useful when you want to ensure a collection is in a specific state without checking for its existence first.

>
> **Info:** The upsert collections endpoint is server-side only.
>

```js label="Node.js"
serverClient.feeds.upsertCollections({
  collections: [
    {
      name: "movies",
      id: "lord_of_the_rings",
      custom: {
        title: "Lord of the Rings",
        genre: "fantasy",
        rating: 9.5,
      },
    },
  ],
});
```

## Deleting Collections

The example below shows how to delete a collection.

```js label="JavaScript"
const response = await client.deleteCollections({
  collection_refs: ["movies:lord_of_the_rings"],
});
```

```js label="Node.js"
const response = await client.feeds.deleteCollections({
  collection_refs: ["movies:lord_of_the_rings"],
});
```

## Reading Collections

The example below shows how to read a collection.

```js label="JavaScript"
const response = await client.readCollections({
  collection_refs: ["movies:lord_of_the_rings"],
});
```

```js label="Node.js"
const response = await client.feeds.readCollections({
  collection_refs: ["movies:lord_of_the_rings"],
});
```

## Query Collections

Retrieve collection entries that match a filter, with sorting and cursor-based pagination. This is useful when you don't know the exact IDs of the entries you want (unlike `readCollections`, which fetches by reference). For example, listing every entry in a `movies` collection, or all entries owned by a given user.

`queryCollections` requires the **read collections** permission. When called client-side, results are automatically scoped to the calling user: only the user's own entries and global (no-owner) entries are returned, and any `user_id` filter is ignored.

```js label="JavaScript"
const response = await client.queryCollections({
  filter: {
    name: "movies",
  },
  sort: [{ field: "created_at", direction: -1 }],
  limit: 10,
});

// response.collections -> matching entries
// response.next        -> cursor for the next page (undefined on the last page)
```

```js label="Node.js"
const response = await client.feeds.queryCollections({
  filter: {
    name: "movies",
  },
  sort: [{ field: "created_at", direction: 1 }],
  limit: 25,
});
```

### Parameters

| Name     | Type   | Description                                                                               |
| -------- | ------ | ----------------------------------------------------------------------------------------- |
| `filter` | object | Query filter. See the filterable fields below. Omit or pass `{}` to match all entries.    |
| `sort`   | array  | Sort clauses, applied in order. Each is `{ field, direction }`. Defaults to newest first. |
| `limit`  | int    | Maximum number of entries to return per page. Defaults to `10`. Maximum is `100`.         |
| `next`   | string | Pagination cursor pointing to the next page (from a previous response's `next`).          |
| `prev`   | string | Pagination cursor pointing to the previous page. Cannot be combined with `next`.          |

### Filterable fields

Filters use Stream's standard [query syntax](https://getstream.io/docs/platform/query-syntax-operators/). A bare value is an equality match (`{ "name": "movies" }`); operators are expressed as nested objects (`{ "created_at": { "$gte": "2024-01-01T00:00:00Z" } }`).

| Field        | Type   | Supported operators                        |
| ------------ | ------ | ------------------------------------------ |
| `id`         | string | `$eq`, `$in`                               |
| `name`       | string | `$eq`, `$in`                               |
| `user_id`    | string | `$eq`, `$in` (ignored on client-side auth) |
| `created_at` | date   | `$eq`, `$gt`, `$gte`, `$lt`, `$lte`        |
| `updated_at` | date   | `$eq`, `$gt`, `$gte`, `$lt`, `$lte`        |

### Sorting

`sort` accepts one or more clauses of the form `{ field, direction }`, where `direction` is `1` for ascending and `-1` for descending. Sortable fields are `created_at`, `updated_at`, and `id`. When `sort` is omitted, entries are returned by `created_at` descending (newest first).

### Pagination

`queryCollections` is cursor-paginated. Each response includes a `next` cursor when more entries are available; pass it back as `next` to fetch the following page. `next` is absent on the last page. Keep the `filter` and `sort` identical across page requests.

```javascript
let next;
const all = [];
do {
  const page = await client.queryCollections({
    filter: { name: "movies" },
    limit: 100,
    next,
  });
  all.push(...page.collections);
  next = page.next;
} while (next);
```

### Response

| Field         | Type   | Description                           |
| ------------- | ------ | ------------------------------------- |
| `collections` | array  | Matching collection entries.          |
| `next`        | string | Cursor for the next page, if any.     |
| `prev`        | string | Cursor for the previous page, if any. |
| `duration`    | string | Server-side processing time.          |

Each entry in `collections` has the following shape:

| Field        | Type   | Description                                    |
| ------------ | ------ | ---------------------------------------------- |
| `name`       | string | Name/type of the collection.                   |
| `id`         | string | Unique identifier within the collection name.  |
| `custom`     | object | Custom data attached to the entry.             |
| `user_id`    | string | Owner of the entry (empty for global entries). |
| `created_at` | date   | When the entry was created.                    |
| `updated_at` | date   | When the entry was last updated.               |

## Activities and Enrichment

Collections are added to activities in the form of **collection references**. An activity can reference up to 10 collections.

>
> **Info:** Support for adding collections directly when creating activities is coming soon.
>

### Adding Collections to an Activity

```js label="JavaScript"
const response = await feed.addActivity({
  type: "post",
  text: "I love this movie!",
  collection_refs: ["movies:lord_of_the_rings"],
});
```

```js label="Node.js"
const response = await client.feeds.addActivity({
    type: 'post',
    feeds: [...],
    text: 'I love this movie!',
    collection_refs: ['movies:lord_of_the_rings'],
});
```

### Enrichment

When you have added collection references to your activities these will automatically be enriched with the collection data when reading feeds.

```js label="JavaScript"
// Create a collection
await client.createCollections({
  collections: [
    {
      name: "movies",
      id: "lord_of_the_rings",
      custom: {
        title: "Lord of the Rings",
        genre: "fantasy",
        rating: 9,
      },
    },
  ],
});

// Add the reference to an activity
const feed = client.feed("user", "jack");
await feed.addActivity({
  type: "post",
  text: "I love this movie!",
  collection_refs: ["movies:lord_of_the_rings"],
});

// Read the feed and see the enriched collection data
const response = await feed.getOrCreate({});
console.log(response);
/*
{
    "activities": [
        {
            "type": "post",
            "text": "I love this movie!",
            "collections": {
                "movies:lord_of_the_rings": {
                    "name": "movies",
                    "id": "lord_of_the_rings",
                    "custom": {
                        "title": "Lord of the Rings",
                        "genre": "fantasy",
                        "rating": 9
                    },
                    "user_id": "jack",
                    "created_at": "2025-01-01T00:00:00.000Z",
                    "updated_at": "2025-01-01T00:00:00.000Z",
                    "status": "ok"
                }
            }
        }
    ],
    ...
}
*/
```

```js label="Node.js"
// Create a collection
await client.feeds.createCollections({
  collections: [
    {
      name: "movies",
      id: "lord_of_the_rings",
      custom: {
        title: "Lord of the Rings",
        genre: "fantasy",
        rating: 9,
      },
    },
  ],
});

// Add the reference to an activity
const feed = client.feeds.feed("user", "jack");
await feed.addActivity({
  type: "post",
  text: "I love this movie!",
  collection_refs: ["movies:lord_of_the_rings"],
  user_id: "jack",
});

// Read the feed and see the enriched collection data
const response = await feed.getOrCreate({ user_id: "jack" });
console.log(response);
/*
{
    "activities": [
        {
            "type": "post",
            "text": "I love this movie!",
            "collections": {
                "movies:lord_of_the_rings": {
                    "name": "movies",
                    "id": "lord_of_the_rings",
                    "custom": {
                        "title": "Lord of the Rings",
                        "genre": "fantasy",
                        "rating": 9
                    },
                    "user_id": "jack",
                    "created_at": "2025-01-01T00:00:00.000Z",
                    "updated_at": "2025-01-01T00:00:00.000Z",
                    "status": "ok"
                }
            }
        }
    ],
    ...
}
*/
```

>
> **Info:** The enrichment is a best effort process. Missing or deleted collections will not be enriched but will be retured with a status of `notfound`
>

---

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