# External data

External data lets you add dynamic data to your ranking expressions. External data is provided when reading the feed, so you can provide user-specific data here.

Every `external.*` field that the score expression references needs a default in `ranking.defaults.external`, and creating or updating a feed group or feed view without one fails with `expression references fields without defaults: external.<field>`. The default is used when a read doesn't provide that field. Custom activity fields used in the expression, such as `stock` in the examples below, need a default too.

Fields in `external_ranking` replace the default with the same top-level key. A nested object you provide replaces the whole default object for that key; it is not merged key by key.

## Accessing external data in ranking expressions

| name       | description                                                                                                                                |
| ---------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| `external` | The `external_ranking` object provided when reading the feed. Nested fields can be accessed with `.` notation, for example: `external.foo` |

```go label="Go"
createRequest := &getstream.CreateFeedGroupRequest{
  ID: "myid",
  ActivitySelectors: getstream.PtrTo([]getstream.ActivitySelectorConfig{
    {
      Type: getstream.PtrTo("following"),
    },
  }),
  Ranking: &getstream.RankingConfig{
    Type:  getstream.PtrTo("expression"),
    Score: getstream.PtrTo("popularity * external.popularity_multiplier + share_count * external.share_multiplier"),
    Defaults: map[string]any{
      "external": map[string]any{
        "popularity_multiplier": 1,
        "share_multiplier":      1,
      },
    },
  },
}

response, err := client.Feeds().CreateFeedGroup(context.Background(), createRequest)
if err != nil {
  log.Fatal("Error creating feed group:", err)
}
```

## Providing external data when reading the feed

```go label="Go"
response, err := timeline.GetOrCreate(ctx, &getstream.GetOrCreateFeedRequest{
  ExternalRanking: getstream.PtrTo(map[string]any{
    "popularity_multiplier": 1,
    "share_multiplier":      100,
  }),
  UserID: getstream.PtrTo("john"),
})
if err != nil {
  log.Fatal("Error getting or creating timeline:", err)
}
```

## Arrays and Maps

Stream provides three powerful functions for working with arrays and maps in ranking expressions. These functions are particularly useful for creating dynamic, personalized ranking algorithms that can respond to external data.

**map_lookup(key, map)**

Looks up a single value from a map by key. If the key doesn't exist, it returns `0.0`. `key` must be a string and `map` an object, otherwise evaluating the expression fails.

```go label="Go"
createRequest := &getstream.CreateFeedGroupRequest{
  ID: "myid",
  ActivitySelectors: getstream.PtrTo([]getstream.ActivitySelectorConfig{
    {
      Type: getstream.PtrTo("following"),
    },
  }),
  Ranking: &getstream.RankingConfig{
    Type:  getstream.PtrTo("expression"),
    Score: getstream.PtrTo("popularity + map_lookup(stock, external.stock_boosts)"),
    Defaults: map[string]any{
      "stock": "",
      "external": map[string]any{
        "stock_boosts": map[string]any{},
      },
    },
  },
}
response, err := client.Feeds().CreateFeedGroup(context.Background(), createRequest)
if err != nil {
  log.Fatal("Error creating feed group:", err)
}
```

Providing external data when reading the feed

```go label="Go"
response, err := timeline.GetOrCreate(ctx, &getstream.GetOrCreateFeedRequest{
  ExternalRanking: getstream.PtrTo(map[string]any{
    "stock_boosts": map[string]any{
      "apple": 0.9,
      "tesla": 0.5,
      "microsoft": 0.3,
    },
  }),
  UserID: getstream.PtrTo("john"),
})
if err != nil {
  log.Fatal("Error getting or creating timeline:", err)
}
```

**sum_map_lookup(keys, map)**

Sums values from a map for multiple keys. Keys must be strings; missing keys and non-numeric values are skipped. This is useful when an activity has multiple tags or categories that should all contribute to the score.

```go label="Go"
response, err := client.Feeds().CreateFeedGroup(context.Background(), &getstream.CreateFeedGroupRequest{
  ID: "myid",
  ActivitySelectors: getstream.PtrTo([]getstream.ActivitySelectorConfig{
    {
      Type: getstream.PtrTo("following"),
    },
  }),
  Ranking: &getstream.RankingConfig{
    Type:  getstream.PtrTo("expression"),
    Score: getstream.PtrTo("popularity + sum_map_lookup(stocks, external.stock_boosts)"),
    Defaults: map[string]any{
      "stocks": []string{},
      "external": map[string]any{
        "stock_boosts": map[string]any{},
      },
    },
  },
})
if err != nil {
  log.Fatal("Error creating feed group:", err)
}
```

Providing external data when reading the feed

```go label="Go"
response, err := timeline.GetOrCreate(ctx, &getstream.GetOrCreateFeedRequest{
  ExternalRanking: getstream.PtrTo(map[string]any{
    "stock_boosts": map[string]any{
      "apple":     10.0,
      "tesla":     5.0,
      "microsoft": 2.0,
    },
  }),
  UserID: getstream.PtrTo("john"),
})
if err != nil {
  log.Fatal("Error:", err)
}

// score = popularity + 10.0 + 5.0 = popularity + 15.0 (for activity with stocks: ['apple', 'tesla'])
```

**in_array(needle, haystack)**

Checks if a value exists in an array. Returns `1.0` if found, `0.0` if not found. The comparison is exact, so the string `"1"` doesn't match the number `1`. Because the result is a number, compare it explicitly in conditions, for example `in_array(stock, external.watchlist) == 1`.

```go label="Go"
createRequest := &getstream.CreateFeedGroupRequest{
  ID: "myid",
  Ranking: &getstream.RankingConfig{
    Type:  getstream.PtrTo("expression"),
    Score: getstream.PtrTo("popularity + in_array(stock, external.watchlist) * 10"),
    Defaults: map[string]any{
      "stock": "",
      "external": map[string]any{
        "watchlist": []string{},
      },
    },
  },
}
response, err := client.Feeds().CreateFeedGroup(context.Background(), createRequest)
if err != nil {
  log.Fatal("Error creating feed group:", err)
}
```

Providing external data when reading the feed

```go label="Go"
response, err := timeline.GetOrCreate(context.Background(), &getstream.GetOrCreateFeedRequest{
  ExternalRanking: getstream.PtrTo(map[string]any{
    "watchlist": []string{"apple", "tesla", "microsoft"},
  }),
  UserID: getstream.PtrTo("john"),
})

if err != nil {
  log.Fatal("Error getting or creating timeline:", err)
}

// activities with stocks in the watchlist get a +10 boost, others get no boost.
```

### Practical Examples

**Stock Trading Feed:**

```js label="JavaScript"
score: "popularity + sum_map_lookup(stocks, external.stock_boosts) + in_array(market_sector, external.favorite_sectors) * 5";
```

**Content Categorization:**

```js label="JavaScript"
score: "popularity + in_array(category, external.preferred_categories) * 3 + map_lookup(category, external.category_weights)";
```

**Multi-tag Content:**

```js label="JavaScript"
score: "popularity + sum_map_lookup(tags, external.tag_boosts) + in_array(priority, external.high_priority_tags) * 10";
```

---

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