import StreamCore
import StreamFeeds
// Initialize the client
let client = FeedsClient(
apiKey: APIKey("<your_api_key>"),
user: User(id: "john"),
token: UserToken("<user_token>"),
tokenProvider: nil // used to refresh expiring tokens
)
// Create a feed (or get its data if exists)
let feed = client.feed(group: "user", id: "john")
try await feed.getOrCreate()
// Add activity
let activity = try await feed.addActivity(
request: .init(
text: "Hello, Stream Feeds!",
type: "post"
)
)
Quick Start
Stream lets you build activity feeds at scale. The largest apps on Stream have over 100 M+ users. V3 keeps that scalability while giving you more flexibility over the content shown in your feed.
What’s new in V3
For-You feed: Most modern apps combine a “For You” feed with a regular “Following” feed. V3 introduces activity selectors so you can:
- surface popular activities
- show activities near the user
- match activities to a user’s interests
- mix-and-match these selectors to build an engaging personalized feed.
Performance: 20–30 % faster flat feeds. Major speedups for aggregation & ranking (full benchmarks coming soon)
Client-side SDKs: Swift/Kotlin/React/Flutter & React Native are in progress
Activity filtering: Filter activity feeds with almost no hit to performance
Comments: Voting, ranking, threading, images, URL previews, @mentions & notifications. Basically all the features of Reddit style commenting systems.
Advanced feed features: Activity expiration • visibility controls • feed visibility levels • feed members • bookmarking • follow-approval flow • stories support.
Search & queries: Activity search, query activities, and query feeds endpoints.
Modern essentials: Permissions • OpenAPI spec • GDPR endpoints • realtime WebSocket events • push notifications • “own capabilities” API.
Coming soon: User-engagement stats • Campaign API • V2 → V3 migration tools.
Feeds V3 roadmap
Feeds V3 comes with amazing new features, however it’s not yet ready for production usage. It’s currently under heavy development with occasional breaking chages. If you plan to go live after end of September, we recommend Feeds V3. Otherwise, Feeds V2 is the safer choice for now.
What’s coming for feeds V3?
- Android and Flutter SDKs to extend the existing JavaScript, React, React Native and iOS SDKs
- Python, Java, PHP, Ruby and .NET SDKs to extend the existing Go and Node SDKs
- Dashboard support (in the meantime you can use server-side SDKs for admin tasks, for example, creating feed groups)
- Migration system from V2 to V3
- Benchmarking
Server side VS Client side
- Most API calls can be done client side
- Client side API calls use the permission system. Server side API calls have full access
Most apps will default to making API calls client side, and server side do the following:
- Updating feed groups or feed views for ranking and aggregation
- Syncing users
- Returning tokens for authenticating users
- Writing to feeds that don’t belong to the current user (since the user doens’t have access client side to do this)
Stream application
If you don’t have a Stream account already you can create one in our dashboard and start coding for free.
To explore the feeds API create a new application in the Stream Dashboard and select US Ohio for “Chat & Video” data storage location.
The API is accessible worldwide, but while in alpha, data storage is only available in US Ohio. Once Feeds v3 is ready for production usage, data storage will be available through the entire Stream global infrastructure.
To install one of the available SDKs, check out the Installation page of the selected technology.
Ready to try it out? Jump into the quick-start next to see how the API works.
Getting Started
// Initialize the client and connect
val client = FeedsClient(
context = context,
apiKey = StreamApiKey.fromString("<your_api_key>"),
user = User(id = "john"),
tokenProvider = object : StreamTokenProvider {
override suspend fun loadToken(userId: StreamUserId): StreamToken {
return StreamToken.fromString("<user_token>")
}
}
)
val connectResult: Result<StreamConnectedUser> = client.connect()
// Create a feed (or get its data if it exists)
val feed = client.feed(group = "user", id = "john")
feed.getOrCreate()
// Add an activity
val result: Result<ActivityData> = feed.addActivity(
request = FeedAddActivityRequest(
type = "post",
text = "Hello, Stream Feeds!",
)
)
import { FeedsClient } from "@stream-io/feeds-client";
const client = new FeedsClient("<API key>");
await client.connectUser({ id: "john" }, "<user token>");
// Create a feed (or get its data if exists)
const feed = client.feed("user", "john");
// Subscribe to WebSocket events for state updates
await feed.getOrCreate({ watch: true });
// Add activity
await feed.addActivity({
text: "Hello, Stream Feeds!",
type: "post",
});
import { FeedsClient } from "@stream-io/feeds-react-sdk";
const client = new FeedsClient("<API key>");
await client.connectUser({ id: "john" }, "<user token>");
// Create a feed (or get its data if exists)
const feed = client.feed("user", "john");
// Subscribe to WebSocket events for state updates
await feed.getOrCreate({ watch: true });
// Add activity
await feed.addActivity({
text: "Hello, Stream Feeds!",
type: "post",
});
import { FeedsClient } from "@stream-io/feeds-react-native-sdk";
const client = new FeedsClient("<API key>");
await client.connectUser({ id: "john" }, "<user token>");
// Create a feed (or get its data if exists)
const feed = client.feed("user", "john");
// Subscribe to WebSocket events for state updates
await feed.getOrCreate({ watch: true });
// Add activity
await feed.addActivity({
text: "Hello, Stream Feeds!",
type: "post",
});
import io.getstream.services.framework.StreamSDKClient;
import io.getstream.services.FeedsImpl;
import io.getstream.services.Feed;
import io.getstream.services.framework.StreamHTTPClient;
import io.getstream.models.*;
StreamSDKClient client = new StreamSDKClient("<API key>", "<API secret>");
FeedsImpl feedsClient = new FeedsImpl(new StreamHTTPClient("<API key>", "<API secret>"));
// Create a feed (or get its data if exists)
Feed feed = new Feed("user", "john", feedsClient);
GetOrCreateFeedRequest feedRequest = GetOrCreateFeedRequest.builder().userID("john").build();
feed.getOrCreate(feedRequest);
// Add activity
AddActivityRequest activity = AddActivityRequest.builder()
.type("post")
.text("Hello, Stream Feeds!")
.userID("john")
.build();
feedsClient.addActivity(activity).execute();
from getstream import Stream
client = Stream(api_key="<API key>", api_secret="<API secret>")
# Create a feed (or get its data if exists)
feed = client.feeds.feed("user", "john")
feed.get_or_create(user_id="john")
# Add activity
response = client.feeds.add_activity(
type="post",
feeds=[feed.get_feed_identifier()],
text="Hello, Stream Feeds!",
user_id="john"
)
using GetStream;
var client = new FeedsV3Client("<API key>");
// Create a feed (or get its data if exists)
var feed = client.Feed("user", "john");
// Subscribe to WebSocket events for state updates
await feed.GetOrCreateFeedAsync(new GetOrCreateFeedRequest { Watch = true });
// Add activity
await client.AddActivityAsync(new AddActivityRequest
{
Text = "Hello, Stream Feeds!",
Type = "post"
});
import (
"context"
"github.com/GetStream/getstream-go/v3"
)
client, err := getstream.NewClient("api_key", "api_secret")
if err != nil {
log.Fatal(err)
}
feedsClient := client.Feeds()
// Create a feed (or get its data if exists)
_, err = feedsClient.GetOrCreateFeed(context.Background(), "user", "john", &getstream.GetOrCreateFeedRequest{
UserID: getstream.PtrTo("john"),
})
// Add activity
response, err := feedsClient.AddActivity(context.Background(), &getstream.AddActivityRequest{
Type: "post",
Text: "Hello, Stream Feeds!",
UserID: "john",
})
use GetStream\ClientBuilder;
use GetStream\FeedsV3Client;
$client = ClientBuilder::fromEnv()->build();
$feedsClient = ClientBuilder::fromEnv()->buildFeedsClient();
// Create a feed (or get its data if exists)
$feed = $feedsClient->feed("user", "john");
$feed->getOrCreate(["user_id" => "john"]);
// Add activity
$response = $feedsClient->addActivity([
"type" => "post",
"text" => "Hello, Stream Feeds!",
"user_id" => "john",
]);
⚠️ If getOrCreate
request fails with “please consider switching to permissions v2” error, please update your Stream application to use our new permission system
Key Concepts
Activities
Activities are the core content units in Stream Feeds. They can represent posts, photos, videos, polls, and any custom content type you define.
Feeds
Feeds are collections of activities. They can be personal feeds, timeline feeds, notification feeds, or custom feeds for your specific use case.
Real-time Updates
Stream Feeds provides real-time updates through WebSocket connections, ensuring your app stays synchronized with the latest content.
Social Features
Built-in support for reactions, comments, bookmarks, and polls makes it easy to build engaging social experiences.
Common Use Cases
Social Media Feed
// Create a timeline feed
let timeline = client.feed(group: "timeline", id: "john")
try await timeline.getOrCreate()
// Add a reaction to activity
_ = try await timeline.addReaction(
activityId: "activity_123",
request: .init(type: "like")
)
// Add a comment to activity
_ = try await timeline.addComment(
request: .init(
comment: "Great post!",
objectId: "activity_123",
objectType: "activity"
)
)
// Add a reaction to comment
let activity = client.activity(for: "activity_123", in: FeedId(group: "timeline", id: "john"))
try await activity.addCommentReaction(
commentId: "comment_123",
request: .init(type: "love")
)
val timeline = client.feed(group = "timeline", id = "john")
timeline.getOrCreate()
// Add a reaction to an activity
val addReactionResult: Result<FeedsReactionData> = timeline.addReaction(
activityId = "activity_123",
request = AddReactionRequest(type = "like")
)
// Add a comment to an activity
val addCommentResult: Result<CommentData> = timeline.addComment(
request = ActivityAddCommentRequest(
comment = "Great post!",
activityId = "activity_123",
)
)
// Add a reaction to a comment
val addCommentReactionResult: Result<FeedsReactionData> = timeline.addCommentReaction(
commentId = "comment_456",
request = AddCommentReactionRequest(type = "like")
)
// Create a timeline feed
const timeline = client.feed("timeline", "john");
await timeline.getOrCreate();
// Add a reaction to activity
await client.addReaction({
activity_id: "activity_123",
type: "like",
});
// Add a comment to activity
await client.addComment({
object_id: "activity_123",
object_type: "activity",
comment: "Great post!",
});
// Add a reaction to comment
await client.addCommentReaction({
id: "comment_123",
type: "love",
});
import io.getstream.services.framework.StreamSDKClient;
import io.getstream.services.FeedsImpl;
import io.getstream.services.Feed;
import io.getstream.services.framework.StreamHTTPClient;
import io.getstream.models.*;
StreamSDKClient client = new StreamSDKClient("<API key>", "<API secret>");
FeedsImpl feedsClient = new FeedsImpl(new StreamHTTPClient("<API key>", "<API secret>"));
// Create a feed (or get its data if exists)
Feed feed = new Feed("user", "john", feedsClient);
GetOrCreateFeedRequest feedRequest = GetOrCreateFeedRequest.builder().userID("john").build();
feed.getOrCreate(feedRequest);
// Add activity
AddActivityRequest activity = AddActivityRequest.builder()
.type("post")
.text("Hello, Stream Feeds!")
.userID("john")
.build();
feedsClient.addActivity(activity).execute();
// Please refer to the JavaScript examples above for the API structure
from getstream import Stream
client = Stream(api_key="<API key>", api_secret="<API secret>")
# Create a feed (or get its data if exists)
feed = client.feeds.feed("user", "john")
feed.get_or_create(user_id="john")
# Add activity
response = client.feeds.add_activity(
type="post",
feeds=[feed.get_feed_identifier()],
text="Hello, Stream Feeds!",
user_id="john"
)
using GetStream;
var client = new FeedsV3Client("<API key>");
// Create a feed (or get its data if exists)
var feed = client.Feed("user", "john");
// Subscribe to WebSocket events for state updates
await feed.GetOrCreateFeedAsync(new GetOrCreateFeedRequest { Watch = true });
// Add activity
await client.AddActivityAsync(new AddActivityRequest
{
Text = "Hello, Stream Feeds!",
Type = "post"
});
// Please refer to the JavaScript examples above for the API structure
Notification Feed
// Create a notification feed
let notifications = client.feed(group: "notification", id: "john")
try await notifications.getOrCreate()
// Mark notifications as read
try await notifications.markActivity(request: .init(markAllRead: true))
// Create a notification feed
val notifications = client.feed(group = "notification", id = "john")
notifications.getOrCreate()
// Mark notifications as read
notifications.markActivity(
request = MarkActivityRequest(markAllRead = true)
)
// Create a notification feed
const notifications = client.feed("notification", "john");
await notifications.getOrCreate();
// Mark notifications as read
await notifications.markActivity({
mark_all_read: true,
});
import io.getstream.services.framework.StreamSDKClient;
import io.getstream.services.FeedsImpl;
import io.getstream.services.Feed;
import io.getstream.services.framework.StreamHTTPClient;
import io.getstream.models.*;
StreamSDKClient client = new StreamSDKClient("<API key>", "<API secret>");
FeedsImpl feedsClient = new FeedsImpl(new StreamHTTPClient("<API key>", "<API secret>"));
// Create a notification feed
Feed notifications = new Feed("notification", "john", feedsClient);
GetOrCreateFeedRequest feedRequest = GetOrCreateFeedRequest.builder().userID("john").build();
notifications.getOrCreate(feedRequest);
using GetStream;
var client = new FeedsV3Client("<API key>");
// Create a notification feed
var notifications = client.Feed("notification", "john");
await notifications.GetOrCreateFeedAsync(new GetOrCreateFeedRequest { Watch = true });
import (
"context"
"github.com/GetStream/getstream-go/v3"
)
client, err := getstream.NewClient("api_key", "api_secret")
if err != nil {
log.Fatal(err)
}
feedsClient := client.Feeds()
// Create a notification feed
_, err = feedsClient.GetOrCreateFeed(context.Background(), "notification", "john", &getstream.GetOrCreateFeedRequest{
UserID: getstream.PtrTo("john"),
})
use GetStream\ClientBuilder;
use GetStream\FeedsV3Client;
$client = ClientBuilder::fromEnv()->build();
$feedsClient = ClientBuilder::fromEnv()->buildFeedsClient();
// Create a notification feed
$notifications = $feedsClient->feed("notification", "john");
$notifications->getOrCreate(["user_id" => "john"]);
Polls
// Create a poll
let feedId = FeedId(group: "user", id: "john")
let feed = client.feed(for: feedId)
let activityData = try await feed.createPoll(
request: .init(
name: "What's your favorite color?",
options: [
PollOptionInput(text: "Red"),
PollOptionInput(text: "Blue"),
PollOptionInput(text: "Green")
]
),
activityType: "poll"
)
// Vote on a poll
let activity = client.activity(for: activityData.id, in: feedId)
_ = try await activity.castPollVote(
request: .init(
vote: .init(
optionId: "option_456"
)
)
)
// Create a poll
val feedId = FeedId(group = "user", id = "john")
val feed = client.feed(fid = feedId)
val request = CreatePollRequest(
name = "What's your favorite color?",
options = listOf(
PollOptionInput(text = "Red"),
PollOptionInput(text = "Blue"),
PollOptionInput(text = "Green")
)
)
val poll: Result<ActivityData> = feed.createPoll(
request = request,
activityType = "poll"
)
// Vote on a poll
val activity = client.activity(
activityId = poll.getOrNull()?.id ?: "",
fid = feedId
)
val votes: Result<PollVoteData?> = activity.castPollVote(
request = CastPollVoteRequest(
vote = VoteData(optionId = "option_456")
)
)
// Create a poll
const feed = client.feed("user", "john");
const poll = await client.createPoll({
name: "What is your favorite color?",
options: [{ text: "Red" }, { text: "Blue" }, { text: "Green" }],
});
// Attach it to an activity
const activity = await feed.addActivity({
text: "What is your favorite color?",
type: "poll",
poll_id: poll.poll.id,
});
// Vote
await client.castPollVote({
poll_id: poll.poll.id,
activity_id: activity.activity.id,
vote: {
option_id: poll.poll.options[0].id,
},
});
import io.getstream.services.framework.StreamSDKClient;
import io.getstream.services.FeedsImpl;
import io.getstream.services.Feed;
import io.getstream.services.framework.StreamHTTPClient;
import io.getstream.models.*;
StreamSDKClient client = new StreamSDKClient("<API key>", "<API secret>");
FeedsImpl feedsClient = new FeedsImpl(new StreamHTTPClient("<API key>", "<API secret>"));
// Create a feed (or get its data if exists)
Feed feed = new Feed("user", "john", feedsClient);
GetOrCreateFeedRequest feedRequest = GetOrCreateFeedRequest.builder().userID("john").build();
feed.getOrCreate(feedRequest);
// Add activity
AddActivityRequest activity = AddActivityRequest.builder()
.type("post")
.text("Hello, Stream Feeds!")
.userID("john")
.build();
feedsClient.addActivity(activity).execute();
// Please refer to the JavaScript examples above for the API structure
from getstream import Stream
client = Stream(api_key="<API key>", api_secret="<API secret>")
# Create a feed (or get its data if exists)
feed = client.feeds.feed("user", "john")
feed.get_or_create(user_id="john")
# Note: Python SDK doesn't have built-in poll creation methods
# You would need to implement poll creation using custom activities
import (
"context"
"github.com/GetStream/getstream-go/v3"
)
client, err := getstream.NewClient("api_key", "api_secret")
if err != nil {
log.Fatal(err)
}
feedsClient := client.Feeds()
// Create a poll
pollResponse, err := feedsClient.CreatePoll(context.Background(), &getstream.CreatePollRequest{
Name: getstream.PtrTo("What's your favorite Go feature?"),
Options: []getstream.PollOptionInput{
{Text: "Goroutines"},
{Text: "Channels"},
{Text: "Interfaces"},
},
})
// Add activity with poll
pollActivity, err := feedsClient.AddActivity(context.Background(), &getstream.AddActivityRequest{
Text: getstream.PtrTo("Check out this poll!"),
Type: "post",
UserID: "john",
Poll: pollResponse.Data.Id,
})
// Cast a vote on a poll
_, err = feedsClient.CastPollVote(context.Background(), pollActivity.Data.Activity.ID, pollResponse.Data.Id, &getstream.CastPollVoteRequest{
Vote: &getstream.PollVoteInput{
OptionID: pollResponse.Data.Options[0].ID,
},
})
use GetStream\ClientBuilder;
use GetStream\FeedsV3Client;
$client = ClientBuilder::fromEnv()->build();
$feedsClient = ClientBuilder::fromEnv()->buildFeedsClient();
// Create a poll
$poll = new GeneratedModels\CreatePollRequest(
name: 'Poll',
description: 'What is your favorite color?',
userID: 'john',
options: [
new GeneratedModels\PollOptionInput("Red"),
new GeneratedModels\PollOptionInput("Blue"),
]
);
$pollResponse = $client->createPoll($poll);
$pollData = $pollResponse->getData();
$pollId = $pollData->poll->id;
// Create activity with the poll
$pollActivity = new GeneratedModels\AddActivityRequest(
type: 'poll',
feeds: ['user:john'],
pollID: $pollId,
text: 'What is your favorite color?',
userID: 'john'
);
$response = $feedsV3Client->addActivity($pollActivity);
</codetabs-item>
<codetabs-item value="csharp" label="C#">
```csharp
using GetStream;
var client = new FeedsV3Client("<API key>");
// Create a feed (or get its data if exists)
var feed = client.Feed("user", "john");
// Subscribe to WebSocket events for state updates
await feed.GetOrCreateFeedAsync(new GetOrCreateFeedRequest { Watch = true });
// Add activity
await client.AddActivityAsync(new AddActivityRequest
{
Text = "Hello, Stream Feeds!",
Type = "post"
});
// Please refer to the JavaScript examples above for the API structure
Advanced Features
Custom Activity Types
Create custom activity types to represent your app’s specific content:
let workoutActivity = try await feed.addActivity(
request: .init(
custom: [
"distance": 5.2,
"duration": 1800,
"calories": 450
],
text: "Just finished my run",
type: "workout"
)
)
val workoutActivity: Result<ActivityData> = feed.addActivity(
request = FeedAddActivityRequest(
custom = mapOf(
"distance" to 5.2,
"duration" to 1800,
"calories" to 450
),
text = "Just finished my run",
type = "workout"
)
)
const feed = client.feed("user", "john");
await feed.addActivity({
type: "workout",
text: "Just finished my run",
custom: {
distance: 5.2,
duration: 1800,
calories: 450,
},
});
import io.getstream.services.framework.StreamSDKClient;
import io.getstream.services.FeedsImpl;
import io.getstream.services.Feed;
import io.getstream.services.framework.StreamHTTPClient;
import io.getstream.models.*;
StreamSDKClient client = new StreamSDKClient("<API key>", "<API secret>");
FeedsImpl feedsClient = new FeedsImpl(new StreamHTTPClient("<API key>", "<API secret>"));
// Create a feed (or get its data if exists)
Feed feed = new Feed("user", "john", feedsClient);
GetOrCreateFeedRequest feedRequest = GetOrCreateFeedRequest.builder().userID("john").build();
feed.getOrCreate(feedRequest);
// Add activity
AddActivityRequest activity = AddActivityRequest.builder()
.type("post")
.text("Hello, Stream Feeds!")
.userID("john")
.build();
feedsClient.addActivity(activity).execute();
// Please refer to the JavaScript examples above for the API structure
from getstream import Stream
client = Stream(api_key="<API key>", api_secret="<API secret>")
# Create a feed (or get its data if exists)
feed = client.feeds.feed("user", "john")
feed.get_or_create(user_id="john")
# Add custom activity
response = client.feeds.add_activity(
type="workout",
feeds=[feed.get_feed_identifier()],
text="Just finished my run",
user_id="john",
custom={
"distance": 5.2,
"duration": 1800,
"calories": 450
}
)
import (
"context"
"github.com/GetStream/getstream-go/v3"
)
client, err := getstream.NewClient("api_key", "api_secret")
if err != nil {
log.Fatal(err)
}
feedsClient := client.Feeds()
// Create a feed (or get its data if exists)
_, err = feedsClient.GetOrCreateFeed(context.Background(), "user", "john", &getstream.GetOrCreateFeedRequest{
UserID: getstream.PtrTo("john"),
})
// Add custom activity
response, err := feedsClient.AddActivity(context.Background(), &getstream.AddActivityRequest{
Type: "workout",
Text: "Just finished my run",
UserID: "john",
Custom: map[string]interface{}{
"distance": 5.2,
"duration": 1800,
"calories": 450,
},
})
use GetStream\ClientBuilder;
use GetStream\FeedsV3Client;
$client = ClientBuilder::fromEnv()->build();
$feedsClient = ClientBuilder::fromEnv()->buildFeedsClient();
// Create a feed (or get its data if exists)
$feed = $feedsClient->feed("user", "john");
$feed->getOrCreate(["user_id" => "john"]);
// Add custom activity
$response = $feedsClient->addActivity([
"type" => "workout",
"text" => "Just finished my run",
"user_id" => "john",
"custom" => [
"distance" => 5.2,
"duration" => 1800,
"calories" => 450,
],
]);
using GetStream;
var client = new FeedsV3Client("<API key>");
// Create a feed (or get its data if exists)
var feed = client.Feed("user", "john");
// Subscribe to WebSocket events for state updates
await feed.GetOrCreateFeedAsync(new GetOrCreateFeedRequest { Watch = true });
// Add activity
await client.AddActivityAsync(new AddActivityRequest
{
Text = "Hello, Stream Feeds!",
Type = "post"
});
// Please refer to the JavaScript examples above for the API structure
Real-time Updates with State Layer
@MainActor class FeedViewModel: ObservableObject {
private var cancellables = Set<AnyCancellable>()
private let feed: Feed
@Published var activities: [ActivityData] = []
init(feed: Feed) {
self.feed = feed
setupRealtimeUpdates()
}
private func setupRealtimeUpdates() {
feed.state.$activities
.sink { [weak self] activities in
self?.activities = activities
}
.store(in: &cancellables)
}
}
class FeedViewModel(private val feed: Feed) : ViewModel() {
val activities: StateFlow<List<ActivityData>> = feed.state.activities
init {
setupRealtimeUpdates()
}
private fun setupRealtimeUpdates() {
viewModelScope.launch {
val result: Result<FeedData> = feed.getOrCreate()
// handle result
}
}
}
const feed = client.feed("user", "john");
// Subscribe to WebSocket events for state updates
await feed.getOrCreate({ watch: true });
feed.state.subscribe((state) => {
// Called everytime the state changes
console.log(state);
});
// or if you only want to observe part of the state
feed.state.subscribeWithSelector(
(state) => ({
activities: state.activities,
}),
(state, prevState) => {
console.log(state.activities, prevState?.activities);
},
);
// Current state
console.log(feed.state.getLatestValue());