The example below shows how to create an activity and add it to a feed.
// Add an activity to 1 feedlet activity = try await feed.addActivity( request: .init( text: "hello world", type: "post" ))// Add an activity to multiple feedslet multiFeedActivity = try await client.addActivity( request: .init( feeds: ["user:1", "stock:apple"], text: "apple stock will go up", type: "post" ))
// Add an activity to 1 feedval activity: Result<ActivityData> = feed.addActivity( request = FeedAddActivityRequest( text = "hello world", type = "post" ))// Add an activity to multiple feedsval multiFeedActivity: Result<ActivityData> = client.addActivity( request = AddActivityRequest( fids = listOf("user:1", "stock:apple"), text = "apple stock will go up", type = "post" ))
// Add an activity to 1 feedconst response = await feed.addActivity({ type: "post", text: "apple stock will go up",});console.log(response.activity);//...or multiple feedsconst response = await client.addActivity({ feeds: ["user:1", "stock:apple"], type: "post", text: "apple stock will go up",});
// Add an activity to 1 feedconst response = await feed.addActivity({ type: "post", text: "apple stock will go up",});console.log(response.activity);//...or multiple feedsconst response = await client.addActivity({ feeds: ["user:1", "stock:apple"], type: "post", text: "apple stock will go up",});
// Add an activity to 1 feedconst response = await feed.addActivity({ type: "post", text: "apple stock will go up",});console.log(response.activity);//...or multiple feedsconst response = await client.addActivity({ feeds: ["user:1", "stock:apple"], type: "post", text: "apple stock will go up",});
// Add an activity to 1 feed or multiple feedsconst response = await client.feeds.addActivity({ feeds: ["user:1", "stock:apple"], type: "post", text: "apple stock will go up", // Provide user id, the owner of the activity user_id: "<user id>",});console.log(response.activity);
// Add an activity to 1 feedfinal activity = await feed.addActivity( request: const FeedAddActivityRequest(text: 'Hello world', type: 'post'),);// Add an activity to multiple feedsfinal multiFeedActivity = await feed.addActivity( request: const FeedAddActivityRequest( feeds: ['user:1', 'stock:apple'], text: 'apple stock will go up', type: 'post', ),);
AddActivityRequest activity = AddActivityRequest.builder() .type("post") .feeds(List.of(testFeedId)) .text("This is a test activity from Java SDK") .userID(testUserId) .build();AddActivityResponse response = feeds.addActivity(activity).execute().getData();
// Add an activity to 1 feed or multiple feeds$activity = new GeneratedModels\AddActivityRequest( type: 'post', feeds: ['user:1', 'stock:apple'], text: 'apple stock will go up', userID: '<user id>', custom: (object)[ 'test_field' => 'test_value', 'timestamp' => time() ]);$response = $feedsClient->addActivity($activity);
var activity = new AddActivityRequest{ Type = "post", Text = "This is a test activity from .NET SDK", UserID = _testUserId, Feeds = new List<string> { $"user:{_testFeedId}" }};var response = await _feedsV3Client.AddActivityAsync(activity);
response = self.client.feeds.add_activity( type="post", feeds=[self.test_feed.get_feed_identifier()], text="This is a test activity from Python SDK", user_id=self.test_user_id, custom={ "test_field": "test_value", "timestamp": int(datetime.now().timestamp()), },)
# Add an activity to 1 feedactivity_request = GetStream::Generated::Models::AddActivityRequest.new( type: 'post', text: 'hello world', user_id: 'user123', feeds: ['user:user123'])response = client.feeds.add_activity(activity_request)# Add an activity to multiple feedsmulti_feed_request = GetStream::Generated::Models::AddActivityRequest.new( type: 'post', text: 'apple stock will go up', user_id: 'user123', feeds: ['user:1', 'stock:apple'])response = client.feeds.add_activity(multi_feed_request)
Adding activities uses upsert logic. If the activity already exists, it will be updated with the new data.
You do not need to call getOrCreate on a feed before adding an activity to it. If you add an activity to a feed that does not exist yet, the feed is created automatically. This applies to both add activity and add activities (batch) requests.
The above example was quite simple. Here are a few more examples:
AddActivityRequest activity = AddActivityRequest.builder() .type("video") .feeds(List.of(testFeedId)) .text("Check out this amazing video!") .userID(testUserId) .build();AddActivityResponse response = feeds.addActivity(activity).execute().getData();
$activity = new GeneratedModels\AddActivityRequest( type: 'post', feeds: ['user:1'], text: 'look at NYC', userID: '<user id>', attachments: [ new GeneratedModels\Attachment( imageUrl: 'https://example.com/image.png', type: 'image', custom: (object)[] ) ]);$response = $feedsClient->addActivity($activity);
var activity = new AddActivityRequest{ Type = "video", Text = "Check out this amazing video!", UserID = _testUserId, Feeds = new List<string> { $"user:{_testFeedId}" } // Note: Video attachments would be added here in a real scenario};var response = await _feedsV3Client.AddActivityAsync(activity);
When creating an activity it's possible to set parent_id, setting this field will increase the share_count of the parent activity. This feature lets you implement "retweets".
When reading an activity with parent id set, you can access the parent activity with activity.parent.
let activity = try await feed.addActivity( request: .init( text: "Couldn't agree more", parentId: "<activity to share>", type: "post" ))
val activity: Result<ActivityData> = feed.addActivity( request = FeedAddActivityRequest( text = "Couldn't agree more", type = "post", parentId = "<activity to share>" ))
// Add an activity to 1 feed or multiple feedsconst response = await client.feeds.addActivity({ feeds: ["user:1"], text: `Couldn't agree more!`, parent_id: activityToShare.id, // Provide user id, the owner of the activity user_id: "<user id>",});console.log(response.activity?.parent);
final activity = await feed.addActivity( request: const FeedAddActivityRequest(text: 'Couldn\'t agree more!', type: 'post', parentId: '<activity to share>'),);
Total activity size (including built-in and custom fields) must not exceed 10KB (the limit is checked when writing activities, not when reading them). If your use case requires a larger payload, collections help attach data to activities without increasing activity size.
When creating an activity, you can provide a visibility to the activity:
Please note that activity visibility is not the same as feed visibility
public: marks the activity as public - everyone who can view feed content, can see it
private: marks the activity as private - only feed owner can see it
tag:mytag: marks the activity as only visible to followers/members with the permission to see this tag
This visibility system is very flexible and allows you to build:
Apps like Patreon where only certain levels of users can see your content
Apps like Strava where it's possible to share your activity with nobody, everyone or your followers
let privateActivity = try await feed.addActivity( request: .init( text: "Premium content", type: "post", visibility: .tag, visibilityTag: "premium" ))// Premium users can see full activity, others a preview
val privateActivity: Result<ActivityData> = feed.addActivity( request = FeedAddActivityRequest( text = "Premium content", type = "post", visibility = AddActivityRequest.Visibility.Tag, visibilityTag = "premium" ))// Premium users can see full activity, others a preview
feed.addActivity({ type: "post", text: "Premium content", visibility: "tag", visibility_tag: "premium",});// Premium users can see full activity, others a preview
feed.addActivity({ type: "post", text: "Premium content", visibility: "tag", visibility_tag: "premium",});// Premium users can see full activity, others a preview
feed.addActivity({ type: "post", text: "Premium content", visibility: "tag", visibility_tag: "premium",});// Premium users can see full activity, others a preview
// Premium users can see full activity, others a previewfinal privateActivity = await feed.addActivity( request: const FeedAddActivityRequest( text: 'Premium content', type: 'post', visibility: AddActivityRequestVisibility.tag, visibilityTag: 'premium', ),);
client.feeds.addActivity({ feeds: ["user:1"], type: "post", text: "Premium content", visibility: "tag", visibility_tag: "premium", user_id: "<user id>",});// Premium users can see full activity, others a preview
response, err := client.Feeds().AddActivity(context.Background(), &getstream.AddActivityRequest{ Feeds: []string{"user:alice"}, Type: "post", Text: getstream.PtrTo("Premium content"), Visibility: getstream.PtrTo("tag"), VisibilityTag: getstream.PtrTo("premium"), UserID: getstream.PtrTo("alice"),})if err != nil { log.Fatal("Error adding activity:", err)}log.Printf("Activity added successfully: %+v", response)// Premium users can see full activity, others a preview
$feedsClient->addActivity( new \GetStream\GeneratedModels\AddActivityRequest( feeds: ['user:1'], type: 'post', text: 'Premium content', visibility: 'tag', visibilityTag: 'premium', userID: '<user id>' ));// Premium users can see full activity, others a preview
client.feeds.add_activity( feeds=["user:1"], type="post", text="Premium content", visibility="tag", visibility_tag="premium", user_id="<user id>")# Premium users can see full activity, others a preview
await client.AddActivityAsync(new AddActivityRequest{ Feeds = new List<string> { "user:1" }, Type = "post", Text = "Premium content", Visibility = "tag", VisibilityTag = "premium", UserID = "<user id>"});// Premium users can see full activity, others a preview
A partial update can be used to set or unset specific fields and leave other fields unchanged. AKA a patch style update. Both set and unset can be used in the same request. The dotted-notation is also available for both set and unset for the custom field.
You can set run_activity_processors to true to run activity processors on the updated activity. Processors will only run if the activity text and/or attachments are changed. This flag defaults to false.
Interest tags behavior:
If run_activity_processors is true: New tags generated from text/image are appended to existing tags.
If run_activity_processors is false or not set: Existing tags are preserved unchanged.
// Partially set some fieldsvar response = try await feed.updateActivityPartial( id: "123", request: .init( set: ["text": "Japan has over 6,800 islands."] ))print("Activity edited at: \(response.activity.editedAt ?? "never")")// Partially unset some fieldsresponse = try await feed.updateActivityPartial( id: "123", request: .init( unset: ["custom.color"] ))print("Activity edited at: \(response.activity.editedAt ?? "never")")
// Partially set some fieldsvar response = feed.updateActivityPartial( id = "123", request = UpdateActivityPartialRequest( set = mapOf("text" to "Japan has over 6,800 islands.") ))println("Activity edited at: ${response.activity.editedAt}")// Partially unset some fieldsresponse = feed.updateActivityPartial( id = "123", request = UpdateActivityPartialRequest( unset = listOf("custom.color") ))println("Activity edited at: ${response.activity.editedAt}")
// Partially set some fieldsconst response = await client.updateActivityPartial({ id: activity.id, set: { text: "Japan has over 6,800 islands.", }, run_activity_processors: true, // Run processors if text/attachments changed});console.log(response.activity.edited_at);// Partially unset some fieldsconst response = await client.updateActivityPartial({ id: activity.id, unset: ["custom.color"],});console.log(response.activity.edited_at);
// Partially set some fieldsconst response = await client.updateActivityPartial({ id: activity.id, set: { text: "Japan has over 6,800 islands.", },});console.log(response.activity.edited_at);// Partially unset some fieldsconst response = await client.updateActivityPartial({ id: activity.id, unset: ["custom.color"],});console.log(response.activity.edited_at);
// Partially set some fieldsconst response = await client.updateActivityPartial({ id: activity.id, set: { text: "Japan has over 6,800 islands.", },});console.log(response.activity.edited_at);// Partially unset some fieldsconst response = await client.updateActivityPartial({ id: activity.id, unset: ["custom.color"],});console.log(response.activity.edited_at);
// Partially set some fieldsconst response = await client.feeds.updateActivityPartial({ id: activity.id, set: { text: "Japan has over 6,800 islands.", }, user_id: "<user id>", run_activity_processors: true, // Run processors if text/attachments changed});console.log(response.activity.edited_at);// Partially unset some fieldsconst response = await client.feeds.updateActivityPartial({ id: activity.id, unset: ["custom.color"], user_id: "<user id>",});console.log(response.activity.edited_at);
// Partially set some fieldsvar response = await feed.updateActivityPartial( id: '123', request: const UpdateActivityPartialRequest( set: {'text': 'Japan has over 6,800 islands.'}, ),);print('Activity edited at: ${response.activity.editedAt}');// Partially unset some fieldsresponse = await feed.updateActivityPartial( id: '123', request: const UpdateActivityPartialRequest( unset: ['custom.color'], ),);print('Activity edited at: ${response.activity.editedAt}');
You can perform partial updates on multiple activities in a single batch operation. This is more efficient than updating activities one by one when you need to update several activities at once.
// Update multiple activities with different changesvar partialUpdateRequest = new UpdateActivitiesPartialBatchRequest{ Changes = new List<UpdateActivityPartialChangeRequest> { new UpdateActivityPartialChangeRequest { ActivityID = activityId1, Set = new Dictionary<string, object> { ["text"] = "Updated text for activity 1", // Update main text ["likes"] = 25, // Update custom field ["status"] = "featured" // Update custom field }, Unset = new List<string> { "priority" } // Remove custom field }, new UpdateActivityPartialChangeRequest { ActivityID = activityId2, Set = new Dictionary<string, object> { ["text"] = "Updated text for activity 2", // Update main text ["likes"] = 15, // Update custom field ["status"] = "published" // Update custom field }, Unset = new List<string> { "views", "category" } // Remove multiple custom fields } }};var response = await _feedsV3Client.UpdateActivitiesPartialBatchAsync(partialUpdateRequest);Console.WriteLine($"Updated {response.Data.Activities.Count} activities");
This operation allows you to:
Update specific fields in multiple activities
Use both set (to update/add fields) and unset (to remove fields) operations
Process all changes in a single API call for better performance
Updatable Fields:
The following reserved fields can be updated:
text - Activity text content
attachments - Media attachments
visibility - Activity visibility level
visibility_tag - Visibility tag
expires_at - Expiration timestamp
filter_tags - Filter tags for querying
interest_tags - Interest tags
collection_refs - Collection references
feeds - Feeds the activity belongs to
mentioned_user_ids - Mentioned users
poll_id - Associated poll
Any custom fields (stored in custom object)
Note: The type field cannot be changed after an activity is created. Other immutable fields include id, user_id, created_at, etc.
This example shows how to fully update an activity:
You can set run_activity_processors to true to run activity processors on the updated activity. Processors will only run if the activity text and/or attachments are changed. This flag defaults to false.
Interest tags behavior:
If run_activity_processors is false or not set, and no interest_tags are provided: interest_tags are set to an empty array.
If run_activity_processors is true and no interest_tags are sent: New tags are generated from text/image (replaces existing tags).
If run_activity_processors is true and interest_tags are sent: Generated tags are merged with the provided tags.
You can modify existing tags by providing them in the request before updating.
// NOTE: updateActivity does a full replace of the activity.// Use updateActivityPartial if you only want to update specific fields.let response = try await feed.updateActivity( id: "123", request: .init(text: "Updated text"))print("Activity edited at: \(response.activity.editedAt ?? "never")")
// Update an activity// NOTE: updateActivity does a full replace of the activity.// Use updateActivityPartial if you only want to update specific fields.val response = feed.updateActivity( id = "123", request = UpdateActivityRequest(text = "Updated text"))println("Activity edited at: ${response.activity.editedAt}")
// NOTE: updateActivity does a full replace of the activity.// Use updateActivityPartial if you only want to update specific fields.const response = await client.updateActivity({ id: "123", text: "Updated text", run_activity_processors: true, // Run processors if text/attachments changed});console.log(response.activity.edited_at);
// NOTE: updateActivity does a full replace of the activity.// Use updateActivityPartial if you only want to update specific fields.const response = await client.updateActivity({ id: "123", text: "Updated text",});console.log(response.activity.edited_at);
// NOTE: updateActivity does a full replace of the activity.// Use updateActivityPartial if you only want to update specific fields.const response = await client.updateActivity({ id: "123", text: "Updated text",});console.log(response.activity.edited_at);
// NOTE: updateActivity does a full replace of the activity.// Use updateActivityPartial if you only want to update specific fields.const response = await client.feeds.updateActivity({ id: "123", text: "Updated text", user_id: "<user id>", run_activity_processors: true, // Run processors if text/attachments changed});console.log(response.activity.edited_at);
// NOTE: updateActivity does a full replace of the activity.// Use updateActivityPartial if you only want to update specific fields.final response = await feed.updateActivity( id: '123', request: const UpdateActivityRequest(text: 'Updated text'),);print('Activity edited at: ${response.activity.editedAt}');
updateRequest := &getstream.UpdateActivityRequest{ Text: stringPtr("Updated text"), UserID: stringPtr("john"),}// NOTE: UpdateActivity does a full replace of the activity.// Use UpdateActivityPartial if you only want to update specific fields.response, err := client.Feeds().UpdateActivity(context.Background(), "123", updateRequest)if err != nil { log.Fatal("Error updating activity:", err)}log.Println("Activity edited at:", response.Activity.EditedAt)
UpdateActivityRequest updateRequest = UpdateActivityRequest.builder() .text("Updated activity text from Java SDK") .userID(testUserId) // Required for server-side auth .build();// NOTE: updateActivity does a full replace of the activity.// Use updateActivityPartial if you only want to update specific fields.UpdateActivityResponse response = feeds.updateActivity(activityId, updateRequest).execute().getData();System.out.println("Activity edited at: " + response.getActivity().getEditedAt());
// NOTE: updateActivity does a full replace of the activity.// Use updateActivityPartial if you only want to update specific fields.$response = $feedsClient->updateActivity( '123', new GeneratedModels\UpdateActivityRequest( text: 'Updated text', userID: '<user id>' ));echo 'Activity edited at: ' . $response->getActivity()->getEditedAt();
// NOTE: UpdateActivityAsync does a full replace of the activity.// Use UpdateActivityPartialAsync if you only want to update specific fields.var response = await _feedsV3Client.UpdateActivityAsync( activityId, new UpdateActivityRequest { Text = "Updated activity text from .NET SDK", UserID = _testUserId // Required for server-side auth });Console.WriteLine($"Activity edited at: {response.Activity.EditedAt}");
# NOTE: update_activity does a full replace of the activity.# Use update_activity_partial if you only want to update specific fields.response = self.client.feeds.update_activity( activity_id, text="Updated activity text from Python SDK", user_id=self.test_user_id, # Required for server-side auth)print(f"Activity edited at: {response['activity']['edited_at']}")
# NOTE: update_activity does a full replace of the activity.# Use update_activity_partial if you only want to update specific fields.update_request = GetStream::Generated::Models::UpdateActivityRequest.new( text: 'Updated activity text from Ruby SDK', user_id: 'user123')response = client.feeds.update_activity(activity_id, update_request)puts "Activity edited at: #{response.activity.edited_at}"
If an activity was soft-deleted, it can be restored using the restore endpoint. Only the activity owner can restore their own activities (for client-side requests). Hard-deleted activities cannot be restored.
When an activity is restored a feeds.activity.restored event is sent to all feeds the activity is part of. There is no default event handler in client-side SDKs but it's possible to add a custom handler.
When implementing an activity details page on the client-side it may be important to receive updates for the activity. The activity state is automatically updated on HTTP requests initiated from the client. To receive real-time updates (to receive updates from other users, for example someone else liked this post), you have to watch the feed the activity belongs to (or one of the feeds, in case it belongs to multiple feeds).
When fetching an activity that belongs to multiple feeds activity.current_feed will be empty. If you wish to display feed information alongside the activity, you have to fetch any of the containing feeds with a separate API call.
const activityWithStateUpdates = client.activityWithStateUpdates(activityId);await activityWithStateUpdates.get({ // Optionally fetch comments too comments: { limit: 10, depth: 2, },});// Subscribe to state updatesactivityWithStateUpdates.state.subscribe((state) => { console.log(state.activity); console.log(state.comments_by_entity_id); // True if activity is being fetched console.log(state.is_loading);});// Comment paginationactivityWithStateUpdates.loadNextPageActivityComments;activityWithStateUpdates.loadNextPageCommentReplies;// Optionally start watching the feed// If activity belongs to multiple feeds, it's up to you to choose which feed to watchconst fid = activityWithStateUpdates.currentState.activity!.feeds[0];const [group, id] = fid.split(':');const feed = client.feed(group, id);let shouldWatch = false;if (!feed.currentState.watch) { await feed.getOrCreate({ watch: true, limit: 0, followers_pagination: { limit: 0 }, following_pagination: { limit: 0 }, });}// When leaving the page...// dispose activity, this avoids refetching the activity if WebSocket reconnectsactivityWithStateUpdates.dispose();// you should stop watching the feed, unless your app has another component that watches the same feedif (shouldWatch) { await feed.stopWatching();}// If you don't care about state updates, no need to call activityWithStateUpdatesawait client.getActivity({ id: activityId,});
The Stream API allows you to post an activity to multiple feeds, the maximum is 25.
When an activity is posted to multiple feeds, activity.current_feed field is only set when reading a feed (feed.getOrCreate):
When reading with current selector, it's set to the feed we're reading
For any other selector it'll be one of the feeds the activity is posted to
If the user follows one of the feeds, it's set to the followed feed (if more than one feed is followed, it'll be one of the feeds)
For all other API calls (for example queryActivities, getActivity) and WebSocket events it's not set
// Add an activity to 1 feedlet activity = try await feed.addActivity( request: .init( text: "hello world", type: "post" ))// Add an activity to multiple feedslet multiFeedActivity = try await client.addActivity( request: .init( feeds: ["user:1", "stock:apple"], text: "apple stock will go up", type: "post" ))
// Add an activity to 1 feedval activity: Result<ActivityData> = feed.addActivity( request = FeedAddActivityRequest( text = "hello world", type = "post" ))// Add an activity to multiple feedsval multiFeedActivity: Result<ActivityData> = client.addActivity( request = AddActivityRequest( fids = listOf("user:1", "stock:apple"), text = "apple stock will go up", type = "post" ))
// Add an activity to 1 feedconst response = await feed.addActivity({ type: "post", text: "apple stock will go up",});console.log(response.activity);//...or multiple feedsconst response = await client.addActivity({ feeds: ["user:1", "stock:apple"], type: "post", text: "apple stock will go up",});
// Add an activity to 1 feedconst response = await feed.addActivity({ type: "post", text: "apple stock will go up",});console.log(response.activity);//...or multiple feedsconst response = await client.addActivity({ feeds: ["user:1", "stock:apple"], type: "post", text: "apple stock will go up",});
// Add an activity to 1 feedconst response = await feed.addActivity({ type: "post", text: "apple stock will go up",});console.log(response.activity);//...or multiple feedsconst response = await client.addActivity({ feeds: ["user:1", "stock:apple"], type: "post", text: "apple stock will go up",});
// Add an activity to 1 feed or multiple feedsconst response = await client.feeds.addActivity({ feeds: ["user:1", "stock:apple"], type: "post", text: "apple stock will go up", // Provide user id, the owner of the activity user_id: "<user id>",});console.log(response.activity);
// Add an activity to 1 feedfinal activity = await feed.addActivity( request: const FeedAddActivityRequest(text: 'Hello world', type: 'post'),);// Add an activity to multiple feedsfinal multiFeedActivity = await feed.addActivity( request: const FeedAddActivityRequest( feeds: ['user:1', 'stock:apple'], text: 'apple stock will go up', type: 'post', ),);
AddActivityRequest activity = AddActivityRequest.builder() .type("post") .feeds(List.of(testFeedId)) .text("This is a test activity from Java SDK") .userID(testUserId) .build();AddActivityResponse response = feeds.addActivity(activity).execute().getData();
// Add an activity to 1 feed or multiple feeds$activity = new GeneratedModels\AddActivityRequest( type: 'post', feeds: ['user:1', 'stock:apple'], text: 'apple stock will go up', userID: '<user id>', custom: (object)[ 'test_field' => 'test_value', 'timestamp' => time() ]);$response = $feedsClient->addActivity($activity);
var activity = new AddActivityRequest{ Type = "post", Text = "This is a test activity from .NET SDK", UserID = _testUserId, Feeds = new List<string> { $"user:{_testFeedId}" }};var response = await _feedsV3Client.AddActivityAsync(activity);
response = self.client.feeds.add_activity( type="post", feeds=[self.test_feed.get_feed_identifier()], text="This is a test activity from Python SDK", user_id=self.test_user_id, custom={ "test_field": "test_value", "timestamp": int(datetime.now().timestamp()), },)
# Add an activity to 1 feedactivity_request = GetStream::Generated::Models::AddActivityRequest.new( type: 'post', text: 'hello world', user_id: 'user123', feeds: ['user:user123'])response = client.feeds.add_activity(activity_request)# Add an activity to multiple feedsmulti_feed_request = GetStream::Generated::Models::AddActivityRequest.new( type: 'post', text: 'apple stock will go up', user_id: 'user123', feeds: ['user:1', 'stock:apple'])response = client.feeds.add_activity(multi_feed_request)
Adding activities uses upsert logic. If the activity already exists, it will be updated with the new data.
You do not need to call getOrCreate on a feed before adding an activity to it. If you add an activity to a feed that does not exist yet, the feed is created automatically. This applies to both add activity and add activities (batch) requests.