# Import Files

If you've just started using Stream, you might want to import data from your old infrastructure. Instead of using the APIs and creating your own import scripts, you can make use of our import feature.

Import files use the [JSON Lines](https://jsonlines.org/) format (one JSON object per line). Each line is an envelope that wraps a single record:

```json
{ "type": "<entity>", "data": { ... } }
```

Valid values for `type` are: `user`, `feed`, `feed_group`, `feed_view`, `activity`, `comment`, `reaction`, `bookmark`, `follow`, `collection`, `poll`, `poll_vote`, and `user_interest`. The full JSON Schemas are published at [GetStream/protocol](https://github.com/GetStream/protocol/tree/main/jsonschemas/feeds).

## The Process

The steps for importing data into your app are as follows:

Install the [Stream CLI](https://getstream.io/cli/docs/)

```bash
curl -fsSL https://getstream.io/cli.sh | bash
```

Set up your app

```bash
getstream login
getstream init
```

Upload the file and track the import task

```bash
getstream import feeds myfile.jsonl --watch
getstream import status <task-id>
getstream import list
```

See the [`getstream import` reference](https://getstream.io/cli/docs/commands/import/) for all flags.

## Users

Users are shared with chat and video. So if you already use those products you typically don't need to import users.
The structure for importing users is shown below:

**JSONL**

```json
{ "type": "user", "data": { "id": "myuserid", "role": "user", "custom": {} } }
```

## Feeds

This is how you can import feeds.

**JSONL**

```json label="JSON"
{
  "type": "feed",
  "data": {
    "group_id": "user",
    "id": "myuserid",
    "fid": "user:myuserid",
    "created_by_id": "myuserid",
    "name": "My Feed",
    "description": "A sample feed",
    "visibility": "public",
    "custom": {}
  }
}
```

`fid` is the fully-qualified feed id and is typically `"{group_id}:{id}"`.

## Follows

For follows specify the import in the following structure:

**JSONL**

```json label="JSON"
{
  "type": "follow",
  "data": {
    "source_fid": "timeline:123",
    "target_fid": "user:222",
    "source_created_by_id": "myuserid",
    "target_created_by_id": "user222",
    "push_preference": "none",
    "custom": {}
  }
}
```

`source_created_by_id` and `target_created_by_id` are the user IDs who created the source and target feeds, respectively. They're required for import (the live [Follow](https://getstream.io/activity-feeds/docs/dotnet-csharp/follows/#follow) API call doesn't need them, since it derives the creator from each feed automatically).

## Activities

Activities are the core records posted to one or more feeds:

**JSONL**

```json
{
  "type": "activity",
  "data": {
    "id": "activity-123",
    "type": "post",
    "user_id": "user-222",
    "fids": ["user:myuserid"],
    "text": "hello world",
    "visibility": "public",
    "custom": {}
  }
}
```

## Comments

For comments specify the import in the following structure:

**JSONL**

```json label="JSON"
{
  "type": "comment",
  "data": {
    "id": "comment-1",
    "object_type": "activity",
    "object_id": "activity-123",
    "user_id": "user-222",
    "text": "buy a gaming chair",
    "mentioned_user_ids": ["user-222"],
    "parent_id": null,
    "attachments": [],
    "custom": {}
  }
}
```

## Reactions

For reactions specify the import in the following structure:

**JSONL**

```json label="JSON"
{
  "type": "reaction",
  "data": {
    "user_id": "user-222",
    "activity_id": "activity-123",
    "type": "like",
    "custom": {}
  }
}
```

`activity_id` is required. Set `comment_id` as well when reacting to a comment on the activity.

## Polls

Polls are a shared entity — the same `poll` and `poll_vote` records are used by both Feeds and Chat. To attach a poll to an activity, import the `poll` first, then reference it from the activity via `poll_id`:

**JSONL**

```json
{
  "type": "poll",
  "data": {
    "id": "poll-123",
    "name": "Which feature should we build next?",
    "created_by_id": "user-222",
    "voting_visibility": "public",
    "enforce_unique_vote": true,
    "max_votes_allowed": 1,
    "options": [
      { "id": "option-1", "text": "Stories" },
      { "id": "option-2", "text": "Live video" },
      { "id": "option-3", "text": "Polls" }
    ],
    "custom": {}
  }
}
```

The `poll` fields are:

| name                         | type    | description                                                      | required |
| ---------------------------- | ------- | ---------------------------------------------------------------- | -------- |
| id                           | string  | unique poll ID (max 255 chars)                                   | ✓        |
| name                         | string  | the poll question / title                                        | ✓        |
| created_by_id                | string  | user ID of the poll creator                                      | ✓        |
| description                  | string  | poll description                                                 |          |
| voting_visibility            | string  | `public` or `anonymous` (defaults to `public`)                   |          |
| enforce_unique_vote          | boolean | only allow a single vote per user                                |          |
| max_votes_allowed            | integer | maximum votes per user, between `1` and `10`                     |          |
| allow_user_suggested_options | boolean | allow users to suggest new options                               |          |
| allow_answers                | boolean | allow free-form text answers                                     |          |
| is_closed                    | boolean | whether the poll is closed for voting                            |          |
| options                      | array   | poll options, each with an `id` (required), `text`, and `custom` |          |
| created_at                   | string  | creation time (defaults to import time)                          |          |
| updated_at                   | string  | last update time (defaults to `created_at`)                      |          |
| custom                       | object  | custom fields (up to 5 KiB)                                      |          |

To attach the poll to an activity, set `poll_id`:

**JSONL**

```json
{
  "type": "activity",
  "data": {
    "id": "activity-123",
    "type": "poll",
    "user_id": "user-222",
    "fids": ["user:myuserid"],
    "poll_id": "poll-123",
    "custom": {}
  }
}
```

## Poll Votes

A `poll_vote` is either a **vote** (pointing at an option via `option_id`) or an **answer** (free-form text via `answer_text`). The two are mutually exclusive — set `is_answer` to `true` with `answer_text` for an answer, otherwise provide `option_id` for a vote. Import votes after their poll.

**JSONL**

```json
{
  "type": "poll_vote",
  "data": {
    "id": "vote-1",
    "poll_id": "poll-123",
    "option_id": "option-1",
    "user_id": "user-222",
    "custom": {}
  }
}
```

The `poll_vote` fields are:

| name        | type    | description                                                           | required |
| ----------- | ------- | --------------------------------------------------------------------- | -------- |
| id          | string  | unique poll vote ID (max 255 chars)                                   | ✓        |
| poll_id     | string  | ID of the poll this vote belongs to                                   | ✓        |
| user_id     | string  | user ID who cast the vote                                             | ✓        |
| option_id   | string  | ID of the chosen option (**required for a vote**, omit for an answer) |          |
| is_answer   | boolean | set to `true` when this is a free-form answer                         |          |
| answer_text | string  | the free-form answer text (**required when `is_answer` is `true`**)   |          |
| created_at  | string  | creation time (defaults to import time)                               |          |
| updated_at  | string  | last update time (defaults to `created_at`)                           |          |

## User interests

A `user_interest` record seeds a user's manual [interests](https://getstream.io/activity-feeds/docs/dotnet-csharp/user-interests/), the tags used by the `interest` activity selector and `interest` ranking. One line carries the user's whole set:

**JSONL**

```json
{
  "type": "user_interest",
  "data": {
    "user_id": "user-222",
    "interests": [{ "tag": "cycling", "weight": 0.8 }, { "tag": "jazz" }]
  }
}
```

The `user_interest` fields are:

| name       | type   | description                                                                             | required |
| ---------- | ------ | --------------------------------------------------------------------------------------- | -------- |
| user_id    | string | user ID, must exist in the import file or in your app                                   | ✓        |
| interests  | array  | 1 to 50 interests, each with a `tag` (required, max 255 chars) and an optional `weight` | ✓        |
| created_at | string | creation time (defaults to import time)                                                 |          |
| updated_at | string | last update time (defaults to `created_at`)                                             |          |

Validation matches the API: tags are trimmed and lower-cased, a tag repeated within one record keeps the last weight, and `weight` is a number between `-1.0` (dislike) and `1.0` (like) that defaults to `1.0`. Imported interests are stored with `source` set to `manual`, so the reaction-based recompute never overwrites them.

Import one `user_interest` line per user. A second line for the same user in one file is reported as a duplicate. The importer does not enforce the 50-tag limit per user against interests already in your app, so keep the total within the limit yourself when importing into an app with existing data.

## Putting it all together

A single import file mixes entity types — one record per line:

**JSONL**

```json
{"type":"user","data":{"id":"myuserid","role":"user","custom":{}}}
{"type":"feed","data":{"group_id":"user","id":"myuserid","fid":"user:myuserid","created_by_id":"myuserid","custom":{}}}
{"type":"activity","data":{"id":"activity-123","type":"post","user_id":"myuserid","fids":["user:myuserid"],"text":"hello world"}}
{"type":"comment","data":{"id":"comment-1","object_type":"activity","object_id":"activity-123","user_id":"myuserid","text":"nice!"}}
{"type":"reaction","data":{"user_id":"myuserid","activity_id":"activity-123","type":"like"}}
{"type":"poll","data":{"id":"poll-123","name":"Which feature next?","created_by_id":"myuserid","options":[{"id":"option-1","text":"Stories"},{"id":"option-2","text":"Polls"}]}}
{"type":"activity","data":{"id":"activity-456","type":"poll","user_id":"myuserid","fids":["user:myuserid"],"poll_id":"poll-123"}}
{"type":"poll_vote","data":{"id":"vote-1","poll_id":"poll-123","option_id":"option-1","user_id":"myuserid"}}
{"type":"user_interest","data":{"user_id":"myuserid","interests":[{"tag":"cycling","weight":0.8},{"tag":"jazz"}]}}
```

---

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