# Export Data

You can export your app's feeds data as files: to keep a backup, move data to another Stream app, or load it into your own systems. An export runs in the background and writes [JSON Lines](https://jsonlines.org/) files in the same format the [importer](https://getstream.io/activity-feeds/docs/java/importing-data-feeds/) reads.

Exports are server-side only.

## Export the whole app

`exportFeedsApp` starts an export of every feeds data type in the app and returns a `task_id`:

```js label="Node.js"
const response = await client.feeds.exportFeedsApp();
console.log(response.task_id);
```

Both fields are optional:

| Field             | Type    | Description                                                                                                                                                                                                   |
| ----------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `types`           | array   | Export only these types, for example `["activity", "comment"]`. Empty or absent exports every type. `poll` and `poll_vote` need `activity` too, because polls are found through the activities that use them. |
| `include_deleted` | boolean | Defaults to `true`. Set it to `false` to leave out soft-deleted users, feed groups, feeds, collections, activities and comments.                                                                              |

The type names are those of the [import file format](https://getstream.io/activity-feeds/docs/java/importing-data-feeds/): `user`, `feed_group`, `feed_view`, `user_interest`, `collection`, `feed`, `poll`, `poll_vote`, `activity`, `follow`, `comment`, `reaction` and `bookmark`.

## Export one type

`exportFeedsByType` exports one of `activities`, `comments`, `reactions` or `follows`, optionally filtered. Without filters it exports the whole type:

```js label="Node.js"
const response = await client.feeds.exportFeedsByType({
  type: "comments",
  user_id: "jane",
  created_at_from: new Date("2026-01-01T00:00:00Z"),
  created_at_to: new Date("2026-02-01T00:00:00Z"),
});
console.log(response.task_id);
```

| Field             | Type      | Description                                                                                                                                                     |
| ----------------- | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `user_id`         | string    | Only rows of this user. For follows: the user's own follows, and other users' follows of the user's feeds.                                                      |
| `created_at_from` | timestamp | Only rows created at or after this time.                                                                                                                        |
| `created_at_to`   | timestamp | Only rows created before this time. Must be later than `created_at_from`.                                                                                       |
| `activity_ids`    | array     | `comments` and `reactions` only. Only the comments or reactions of these activities, replies and comment reactions included. At most 100 IDs, each listed once. |
| `include_deleted` | boolean   | Defaults to `false`. Set it to `true` to include soft-deleted activities and comments.                                                                          |

Only the filter combinations below are accepted. Any other combination is rejected with a 400 error that names the filter that isn't allowed:

| Type         | Accepted filters                                                                                    |
| ------------ | --------------------------------------------------------------------------------------------------- |
| `activities` | `user_id`, a `created_at` range, or both                                                            |
| `comments`   | `user_id`, a `created_at` range, `activity_ids`, `user_id` with a range, `activity_ids` with either |
| `reactions`  | `user_id`, `activity_ids`, `user_id` with a range, `activity_ids` with either                       |
| `follows`    | `user_id`, or `user_id` with a range                                                                |

`include_deleted: true` can't be combined with `user_id` on `activities` or `comments`, or with a `created_at` range on `comments` unless `activity_ids` is set too. Reactions and follows have no soft-deleted rows.

Other types are available only through the whole-app export, with `types`. A per-type export holds only that type. It doesn't carry the users, feeds or activities its rows point to, so use the whole-app export when you need a self-contained copy.

The whole-app export has no time filter. A `created_at` range is available only on the per-type export, with the combinations above.

## Get the files

Poll the task until its `status` is `completed`. The result holds a link to `manifest.json` and one link for every data file:

```js label="Node.js"
const task = await client.getTask({ id: response.task_id });
if (task.status === "completed") {
  console.log(task.result.url); // manifest.json
  for (const file of task.result.files) {
    console.log(file.path, file.url);
  }
}
```

```json
{
  "task_id": "9f65c213-673c-4689-adda-09b0efddf2d0",
  "status": "completed",
  "result": {
    "url": "https://.../manifest.json?...",
    "files": [
      {
        "path": ".../users/part-0001.jsonl",
        "url": "https://.../users/part-0001.jsonl?..."
      },
      {
        "path": ".../09-activities/part-0001.jsonl",
        "url": "https://.../09-activities/part-0001.jsonl?..."
      }
    ]
  }
}
```

The links are signed URLs that expire after 24 hours. Each poll of the task signs them again, so poll the task again when you need fresh links.

While the export is queued or running, the task is `pending`. A failed export reports its error.

Only one export runs per app at a time. Starting another while one is queued or running returns an error until the first one finishes, fails or is cancelled.

### Webhooks

When an export finishes, your app's webhook receives one of these events:

| Event                  | Sent when            | Payload                                                 |
| ---------------------- | -------------------- | ------------------------------------------------------- |
| `feeds.export.success` | The export completed | `task_id`, `manifest_path`, `started_at`, `finished_at` |
| `feeds.export.error`   | The export failed    | `task_id`, `error`, `started_at`, `finished_at`         |

The event is a notification: get the download links from the task.

### Cancel an export

`cancelExportFeeds` stops a queued or running export and deletes the files it already wrote. A cancelled export sends no webhook, and its task reports `canceled`.

```js label="Node.js"
await client.feeds.cancelExportFeeds({ task_id: response.task_id });
```

## Files

An export is a folder with a manifest and one sub-folder per type:

```text
manifest.json
users/part-0001.jsonl
02-feed_groups/part-0001.jsonl
03-feed_views/part-0001.jsonl
04-user_interests/part-0001.jsonl
05-collections/part-0001.jsonl
06-feeds/part-0001.jsonl
07-polls/part-0001.jsonl
08-poll_votes/part-0001.jsonl
09-activities/part-0001.jsonl
09-activities/part-0002.jsonl
10-follows/part-0001.jsonl
11-comments/part-0001.jsonl
12-reactions/part-0001.jsonl
13-bookmarks/part-0001.jsonl
```

- Each part file is under 300 MB. A type with no rows has no folder.
- The numbers in the folder names set the import order: a record only points to records in its own file or an earlier one. Keep the files and their order as they are.
- Each line is one record, `{"type": "<type>", "data": {...}}`, with your custom fields under `custom`:

```json
{
  "type": "activity",
  "data": {
    "created_at": "2026-10-20T11:44:14.761632Z",
    "custom": { "mood": "happy" },
    "fids": ["user:u1"],
    "id": "a1",
    "text": "Which day?",
    "type": "post",
    "updated_at": "2026-10-20T11:44:14.761632Z",
    "user_id": "u1",
    "visibility": "public"
  }
}
```

- Soft-deleted rows carry `deleted_at`.
- Counts and other fields Stream computes, such as `reaction_groups`, `latest_reactions` and comment depth, are not exported: they are rebuilt on import. Feed members and bookmark folders are not exported yet.
- Only polls used by an exported activity are exported, with their votes.

An export is a snapshot of rows by creation time: rows created after the export started are left out. Changes to existing rows during the export can show up in it.

### The manifest

`manifest.json` describes the export:

```json
{
  "format_version": 1,
  "app_id": 1234,
  "exported_at": "2026-10-15T12:00:00Z",
  "custom_format": "nested",
  "types": [
    {
      "type": "activity",
      "folder": "09-activities",
      "record_count": 1500000,
      "files": [
        {
          "path": "09-activities/part-0001.jsonl",
          "record_count": 750000,
          "bytes": 298123456
        }
      ]
    }
  ],
  "excluded_types": ["feed_member", "bookmark_folder"],
  "excluded_fields": { "activity": ["comment_count", "..."] },
  "caps_hit": [{ "type": "activity", "cap": "type", "limit": 1000000 }],
  "truncated": true,
  "filter": {
    "user_id": "jane",
    "types": ["activity"],
    "include_deleted": false
  }
}
```

| Field                               | Description                                                                                              |
| ----------------------------------- | -------------------------------------------------------------------------------------------------------- |
| `types`                             | Every exported type, with its folder, record count and files.                                            |
| `excluded_types`, `excluded_fields` | What the export leaves out by design.                                                                    |
| `caps_hit`, `truncated`             | Present only when a record limit stopped the export early. See [Record limits](#record-limits).          |
| `filter`                            | Present only on a partial export (per-type, filtered, or `include_deleted: false`): the filters it used. |

## Record limits

One export reads at most **1,000,000 records per type** and **5,000,000 records in total**. The limits are set when the export starts.

When a type reaches its limit, the export stops that type and continues with the next one. A comment thread is never split, so the comments folder can go slightly over the limit. When the export reaches the total limit, it stops the current type and skips the rest. The export still completes, and the manifest says what was left out:

- `truncated` is `true`;
- `caps_hit` lists each affected type, with `cap` set to `"type"` or `"job"` (the total limit) and the `limit` that was reached.

To export more, split the export with the per-type filters, for example by `created_at` range. If you need higher limits, contact [support](https://getstream.io/contact/support/).

## Importing an export

An export imports with the [importer](https://getstream.io/activity-feeds/docs/java/importing-data-feeds/). Upload the folder as it is, keeping the paths: the importer skips `manifest.json`, reads `users/` first and then the other folders in order.

Two import settings matter:

- `custom_format: "nested"`: export records keep custom fields under `custom`.
- `on_missing_reference: "warn"`: an export copies your data as it is, including references that already point to deleted rows, and a capped or filtered export leaves rows out. With `warn`, a record whose reference is missing still imports and is counted in the import result. With the default, one missing reference fails its whole file.

When you import an export of an app that was migrated from Feeds v2 into another app of your organization, the target app gets the same size limits as the source.

---

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