Follow and Unfollow

Follow

The source feed should have a group that has “following” activity selector enabled, for example the built-in timeline group. The target feed should have a group that has “current” activity selector enabled, for example the built-in user group.

const timeline = client.feed("timeline", "john");
await timeline.getOrCreate();

// Follow a user
await timeline.follow("user:tom");

// Follow a stock
await timeline.follow("stock:apple");

// Follow with more fields
await timeline.follow("stock:apple", {
  push_preference: "all",
  custom: {
    reason: "investment",
  },
});

// Follow without Feed instance
await client.follow({
  source: "timeline:alice",
  target: "user:tom",
});

Trying to follow a feed that is already followed will result in an error. You can also use the getOrCreateFollows endpoint that provides an idempotent batch follow mechanism.

Unfollow

When unfollowing a feed, all previous activities of that feed are removed from the timeline.

Trying to unfollow a feed that is not followed, will result in an error. You can also use the getOrCreateUnfollows endpoint that provides an idempotent batch unfollow mechanism.

const timeline = client.feed("timeline", "john");
await timeline.unfollow("user:tom");

// Unfollow without Feed instance
await client.unfollow({
  source: "timeline:alice",
  target: "user:tom",
});

Querying Follows

const myTimeline = client.feed("timeline", "john");
await myTimeline.getOrCreate();

// Do I follow a list of feeds
const response = await client.queryFollows({
  filter: {
    source_feed: myTimeline.feed,
    target_feed: { $in: ["user:sara", "user:adam"] },
  },
});

console.log(response.follows);

const userFeed = client.feed("user", "john");
await userFeed.getOrCreate();

// Paginating through followers for a feed - won't store followers in state
const firstPage = await userFeed.queryFollowers({
  limit: 20,
});

// Next page - won't store followers in state
const secondPage = await userFeed.queryFollowers({
  limit: 20,
  next: firstPage.next,
});

// or load when reading feed - will store followers in state
await feed.getOrCreate({
  followers_pagination: {
    limit: 10,
  },
});

// and then load next pages (or first if followers are not yet loaded) - will store followers in state
await feed.loadNextPageFollowers({ limit: 10 });

console.log(feed.state.getLatestValue().followers);

// Filter by source - feeds that I follow - won't store followings in state
await myTimeline.queryFollowing({ limit: 10 });

// or load when reading feed - will store followings in state
await feed.getOrCreate({
  following_pagination: {
    limit: 10,
  },
});

// and then load next pages (or first if followings are not yet loaded) - will store followings in state
await feed.loadNextPageFollowing({ limit: 10 });

console.log(feed.state.getLatestValue().following);

Follows Queryable Built-In Fields

nametypedescriptionsupported operationsexample
source_feedstring or list of stringsThe feed ID that is following$in, $eq{ source_feed: { $eq: 'messaging:general' } }
target_feedstring or list of stringsThe feed ID being followed$in, $eq{ target_feed: { $in: [ 'sports:news', 'tech:updates' ] } }
statusstring or list of stringsThe follow status$in, $eq{ status: { $in: [ 'accepted', 'pending', 'rejected' ] } }
created_atstring, must be formatted as an RFC3339 timestampThe time the follow relationship was created$eq, $gt, $gte, $lt, $lte{ created_at: { $gte: '2023-12-04T09:30:20.45Z' } }

Follow Requests

Some apps require the user’s approval for following them.

const saraFeed = saraClient.feed("user", uuidv4());
await saraFeed.getOrCreate({
  // You need to set followers visibility to have follow requests
  data: { visibility: "followers" },
});

const adamTimeline = adamClient.feed("timeline", uuidv4());
await adamTimeline.getOrCreate();

const followRequest = await adamTimeline.follow(saraFeed.feed);

console.log(followRequest.follow.status); // pending

await saraClient.acceptFollow({
  source: adamTimeline.feed,
  target: saraFeed.feed,
  // Optionally provide role
  follower_role: "feed_member",
});

await saraClient.rejectFollow({
  source: adamTimeline.feed,
  target: saraFeed.feed,
});

Push Preferences on Follow

Understanding the difference between push_preference, skip_push and create_notification_activity:

When following a feed, you can set push_preference to control push notifications for future activities from that feed:

  • all - Receive push notifications for all activities from the followed feed
  • none (default) - Don’t receive push notifications for activities from the followed feed

The skip_push controls whether the follow action itself triggers a notification.

The create_notification_activity controls whether the follow action creates an activity on the source feed author’s notification feed.

Note: You usually don’t want to set skip_push and create_notification_activity true at the same time, for more information see the Push Overview page

// Scenario 1: Follow a user and receive notifications for their future activities
await timeline.follow("user:alice", {
  push_preference: "all", // You'll get push notifications for Alice's future posts
});

// Scenario 2: Follow a user but don't get notifications for their activities
await timeline.follow("user:bob", {
  push_preference: "none", // You won't get push notifications for Bob's future posts
});

// Scenario 3: Follow a user silently
await timeline.follow("user:charlie", {
  skip_push: true, // Charlie won't get a "you have a new follower" notification
  push_preference: "all", // But you'll still get notifications for Charlie's future posts
});

// Scenario 4: Silent follow with no future notifications
await timeline.follow("user:diana", {
  skip_push: true, // Diana won't know you followed her
  push_preference: "none", // And you won't get notifications for her posts
});

// Scenario 5: Follow a user and create notification activity for Charile
await timeline.follow("user:charlie", {
  skip_push: true, // Charlie won't get a "you have a new follower" notification
  create_notification_activity: true, // Charlie's notification feed will have a new activity
  push_preference: "all", // But you'll still get notifications for Charlie's future posts
});

Built-in fields of follows

FollowResponse

NameTypeDescriptionConstraints
created_atnumberWhen the follow relationship was createdRequired
customobjectCustom data for the follow relationship-
follower_rolestringRole of the follower (source user) in the follow relationshipRequired
push_preferencestring (all, none)Push preference for notificationsRequired
request_accepted_atnumberWhen the follow request was accepted-
request_rejected_atnumberWhen the follow request was rejected-
source_feedFeedResponseSource feed objectRequired
statusstring (accepted, pending, rejected)Status of the follow relationshipRequired
target_feedFeedResponseTarget feed objectRequired
updated_atnumberWhen the follow relationship was last updatedRequired

Follow Suggestions

Stream provides intelligent follow suggestions to help users discover feeds they might want to follow based on their activity and social graph.

// Get follow suggestions for a user
const suggestions = await client.getFollowSuggestions({
  feed_group_id: "user",
  limit: 10,
});

console.log("Algorithm used:", suggestions.algorithm_used);
console.log("Duration:", suggestions.duration);

suggestions.suggestions.forEach((suggestion) => {
  console.log("Suggested feed:", suggestion.feed);
  console.log("Name:", suggestion.name);
  console.log("Description:", suggestion.description);
  console.log("Follower count:", suggestion.follower_count);
  console.log("Recommendation score:", suggestion.recommendation_score);
  console.log("Reason:", suggestion.reason);
  console.log("Algorithm scores:", suggestion.algorithm_scores);
});

Response Fields

The follow suggestions response includes:

  • suggestions: Array of suggested feeds to follow
    • feed: Feed identifier
    • name: Feed name
    • description: Feed description
    • visibility: Feed visibility setting
    • member_count: Number of members
    • follower_count: Number of followers
    • following_count: Number of feeds this feed follows
    • created_at: When the feed was created
    • updated_at: When the feed was last updated
    • recommendation_score: Combined recommendation score (0-1)
    • reason: Human-readable reason for the suggestion
    • algorithm_scores: Individual algorithm scores
  • algorithm_used: The algorithm used to generate suggestions
  • duration: Request processing time

Algorithm Types

Stream’s follow suggestions use a sophisticated multi-algorithm approach with weighted scoring:

  • popularity (Weight: 0.3): Based on follower count and engagement

    • Calculates normalized follower count relative to the most popular feed in your app
    • Score = min(follower_count / max_follower_count, 1.0)
    • Helps surface trending and popular content
  • friend-of-friend (Weight: 0.7): Based on social connections and mutual follows

    • Analyzes how many of your followed feeds also follow the suggested feed
    • Score = mutual_follows / your_total_follows
    • Leverages social proof and network effects
  • combined: Uses multiple algorithms with weighted scoring

    • Final score = (popularity_score × 0.3 + friend_of_friend_score × 0.7) / total_weight
    • Provides balanced recommendations combining popularity and social relevance

Note: Additional algorithms will be added in future releases to provide even more sophisticated recommendations.

Scoring System

The recommendation system uses a sophisticated scoring mechanism:

  1. Individual Algorithm Scores: Each algorithm calculates a score from 0.0 to 1.0
  2. Weighted Combination: Scores are combined using configurable weights
  3. Normalization: Final scores are normalized to ensure fair comparison
  4. Filtering: Only feeds with positive combined scores are included
  5. Ranking: Results are sorted by combined score in descending order

Features

  • Excludes feeds already followed by the user
  • Excludes user’s own feeds
  • Sophisticated algorithm to find feeds to follow

Batch follow & unfollow

getOrCreateFollows/getOrCreateUnfollows endpoints allow creating a maximum of 100 follow/unfollow at once.

These are idempotent endpoints (as opposed to follow and unfollow), trying to follow/unfollow a feed that’s already/not yet followed won’t cause errors.

const response = await client.getOrCreateFollows({
  follows: [
    {
      source: timeline.feed,
      target: feed.feed,
      // Optional
      push_preference: "all",
      custom: {
        reason: "investment",
      },
    },
    {
      source: timeline.feed,
      target: feed2.feed,
    },
  ],
});

console.log("Created follows:", response.created);
console.log("Follows:", response.follows);

await client.getOrCreateUnfollows({
  follows: [
    {
      source: timeline.feed,
      target: feed.feed,
    },
  ],
});

console.log("Follows that were removed:", response.follows);