// 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);Activities
Creating Activities
The example below shows how to create an activity and add it to a feed.
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:
Image & Video
You can use Stream's CDN for storing and quickly accessing files attached to activities.
$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);Sharing activities
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.
$activity = new GeneratedModels\AddActivityRequest(
type: 'post',
feeds: ['user:1'],
text: 'Couldn\'t agree more!',
parentID: '<activity to share>',
userID: '<user id>',
);
$response = $feedsClient->addActivity($activity);Restricting comment replies
When creating an activity it's possible to set who can add comments. Possible options:
- Everyone (this is the default setting)
- Only people I follow
- Nobody
The activity author is always allowed to add comments regardless of the reply settings.
$activity = new GeneratedModels\AddActivityRequest(
type: 'post',
feeds: ['user:1'],
text: 'apple stock will go up',
userID: '<user id>',
restrictReplies: 'people_i_follow' // Options: 'everyone', 'people_i_follow', 'nobody'
);
$response = $feedsClient->addActivity($activity);Activity request options
The following options can be provided when creating an activity:
AddActivityRequest
| Name | Type | Description | Constraints |
|---|---|---|---|
attachments | Attachment[] | List of attachments for the activity | - |
collection_refs | string[] | Collections that this activity references | - |
copy_custom_to_notification | boolean | Whether to copy custom data to the notification activity (only applies when create_notification_activity is true) | - |
create_notification_activity | boolean | Whether to create notification activities for mentioned users | - |
custom | object | Custom data for the activity | - |
enrich_own_fields | boolean | - | - |
expires_at | string | Expiration time for the activity | - |
feeds | string[] | List of feeds to add the activity to with a default max limit of 25 feeds | Required |
filter_tags | string[] | Tags for filtering activities | - |
id | string | Optional ID for the activity | - |
interest_tags | string[] | Tags for indicating user interests | - |
location | ActivityLocation | Geographic location related to the activity | - |
mentioned_user_ids | string[] | List of users mentioned in the activity | - |
parent_id | string | ID of parent activity for replies/comments | - |
poll_id | string | ID of a poll to attach to activity | - |
restrict_replies | string (everyone, people_i_follow, nobody) | Controls who can add comments/replies to this activity. One of: everyone, people_i_follow, nobody | - |
search_data | object | Additional data for search indexing | - |
skip_enrich_url | boolean | Whether to skip URL enrichment for the activity | - |
skip_push | boolean | Whether to skip push notifications | - |
text | string | Text content of the activity | - |
type | string | Type of activity | Required |
user_id | string | ID of the user creating the activity | - |
visibility | string (public, private, tag) | Visibility setting for the activity. One of: public, private, tag | - |
visibility_tag | string | If visibility is 'tag', this is the tag name and is required | - |
Activity size limit
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.
Reading Activities with Enrichment
When you read activities from feeds, they are automatically enriched with additional data:
- Comments: The latest 5 top-level comments (replies not included by default)
- Reactions: Recent reactions and reaction counts
- User data: Information about the activity author
- Collections: Any collections the activity references
To load more comments, replies, or additional data, use the dedicated loading methods described in the Comments and Reactions sections.
Translation
Activity text can be translated on demand and projected when reading feeds and activities. See Translation for translate endpoints, read-time language projection, and the i18n object format.
Overview of All Activity Fields
ActivityResponse
| Name | Type | Description | Constraints |
|---|---|---|---|
attachments | Attachment[] | Media attachments for the activity | Required |
bookmark_count | integer | Number of bookmarks on the activity | Required |
collections | object | Enriched collection data referenced by this activity | Required |
comment_count | integer | Number of comments on the activity | Required |
comments | CommentResponse[] | Latest 5 comments of this activity (comment replies excluded) | Required |
created_at | number | When the activity was created | Required |
current_feed | FeedResponse | Feed context for this activity view. If an activity is added only to one feed, it's always set. If an activity is added to multiple feeds, it's only set when calling the GetOrCreateFeed endpoint. | - |
custom | object | Custom data for the activity | Required |
deleted_at | number | When the activity was deleted | - |
edited_at | number | When the activity was last edited | - |
expires_at | number | When the activity will expire | - |
feeds | string[] | List of feed IDs containing this activity | Required |
filter_tags | string[] | Tags for filtering | Required |
friend_reaction_count | integer | Total count of reactions from friends on this activity | - |
friend_reactions | FeedsReactionResponse[] | Reactions from users the current user follows or has mutual follows with | - |
hidden | boolean | If this activity is hidden by this user (using activity feedback) | Required |
id | string | Unique identifier for the activity | Required |
interest_tags | string[] | Tags for user interests | Required |
is_read | boolean | Whether this activity has been read. Only set for feed groups with notification config (track_seen/track_read enabled). | - |
is_seen | boolean | Whether this activity has been seen. Only set for feed groups with notification config (track_seen/track_read enabled). | - |
is_watched | boolean | - | - |
latest_reactions | FeedsReactionResponse[] | Recent reactions to the activity | Required |
location | ActivityLocation | Geographic location related to the activity | - |
mentioned_users | UserResponse[] | Users mentioned in the activity | Required |
metrics | object | - | - |
moderation | ModerationV2Response | Moderation information | - |
moderation_action | string | - | - |
notification_context | NotificationContext | Notification context data for the activity (if this is a reaction, comment, follow, etc.) | - |
own_bookmarks | BookmarkResponse[] | Current user's bookmarks for this activity | Required |
own_reactions | FeedsReactionResponse[] | Current user's reactions to this activity | Required |
parent | ActivityResponse | Parent activity (if this is a reply/comment) | - |
poll | PollResponseData | Poll attached to this activity | - |
popularity | integer | Popularity score of the activity | Required |
preview | boolean | If this activity is obfuscated for this user. For premium content where you want to show a preview | Required |
reaction_count | integer | Number of reactions to the activity | Required |
reaction_groups | object | Grouped reactions by type | Required |
restrict_replies | string (everyone, people_i_follow, nobody) | Controls who can add comments/replies to this activity. One of: everyone, people_i_follow, nobody | Required |
score | number | Ranking score for this activity | Required |
score_vars | object | Variable values used at ranking time. Only included when include_score_vars is enabled in enrichment options. | - |
search_data | object | Data for search indexing | Required |
selector_source | string | Which activity selector provided this activity (e.g., 'following', 'popular', 'interest'). Only set when using multiple activity selectors with ranking. | - |
share_count | integer | Number of times the activity was shared | Required |
text | string | Text content of the activity | - |
type | string | Type of activity | Required |
updated_at | number | When the activity was last updated | Required |
user | UserResponse | User who created the activity | Required |
visibility | string (public, private, tag) | Visibility setting for the activity. One of: public, private, tag | Required |
visibility_tag | string | If visibility is 'tag', this is the tag name | - |
Adding Many Activities
You can also batch add activities. Here's an example:
$activities = [
[
'feeds' => ['user:123'],
'id' => '1',
'type' => 'post',
'text' => 'hi',
'user_id' => '<user id>',
],
[
'feeds' => ['user:456'],
'id' => '2',
'type' => 'post',
'text' => 'hi',
'user_id' => '<user id>',
]
];
$response = $feedsClient->upsertActivities(
new GeneratedModels\UpsertActivitiesRequest(activities: $activities)
);Visibility Levels
When creating an activity, you can set a visibility level for 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 itprivate: marks the activity as private - only feed owner can see ittag: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
$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 previewFor all the details on tag visibility read the Membership levels guide.
Partial activity updates
A partial update can be used to set or unset specific fields and leave other fields unchanged (also known as 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_processorsistrue: New tags generated from text/image are appended to existing tags. - If
run_activity_processorsisfalseor not set: Existing tags are preserved unchanged.
// Partially set some fields
$response = $feedsClient->updateActivityPartial(
'123',
new GeneratedModels\UpdateActivityPartialRequest(
userID: '<user id>',
set: [
'text' => 'Japan has over 6,800 islands.',
]
)
);
echo 'Activity edited at: ' . $response->getActivity()->getEditedAt();
// Partially unset some fields
$response = $feedsClient->updateActivityPartial(
'123',
new GeneratedModels\UpdateActivityPartialRequest(
userID: '<user id>',
unset: ['custom.color']
)
);
echo 'Activity edited at: ' . $response->getActivity()->getEditedAt();Batch Partial Activity Updates
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 changes
var 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) andunset(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 contentattachments- Media attachmentsvisibility- Activity visibility levelvisibility_tag- Visibility tagexpires_at- Expiration timestampfilter_tags- Filter tags for queryinginterest_tags- Interest tagscollection_refs- Collection referencesfeeds- Feeds the activity belongs tomentioned_user_ids- Mentioned userspoll_id- Associated poll- Any custom fields (stored in
customobject)
Note: The type field cannot be changed after an activity is created. Other immutable fields include id, user_id, created_at, etc.
Updating Activities
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_processorsisfalseor not set, and nointerest_tagsare provided:interest_tagsare set to an empty array. - If
run_activity_processorsistrueand nointerest_tagsare sent: New tags are generated from text/image (replaces existing tags). - If
run_activity_processorsistrueandinterest_tagsare 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.
$response = $feedsClient->updateActivity(
'123',
new GeneratedModels\UpdateActivityRequest(
text: 'Updated text',
userID: '<user id>'
)
);
echo 'Activity edited at: ' . $response->getActivity()->getEditedAt();Deleting Activities
This example shows how to delete an activity:
// Delete an activity (soft delete sets deleted_at but retains the data, hard delete fully removes it)
$response = $feedsClient->deleteActivity('123', false);
// Batch delete activities
$response = $feedsClient->deleteActivities(
new GeneratedModels\DeleteActivitiesRequest(
ids: ['123', '456'],
userID: '<user id>',
hardDelete: false
)
);Restore Activity
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.
// Restore a soft-deleted activity
$response = $feedsClient->restoreActivity(
'123',
new GeneratedModels\RestoreActivityRequest(
userID: '<user id>'
)
);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.
Get activity
Fetching a single activity.
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 updates
activityWithStateUpdates.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 pagination
activityWithStateUpdates.loadNextPageActivityComments;
activityWithStateUpdates.loadNextPageCommentReplies;
// Optionally start watching the feed
// If activity belongs to multiple feeds, it's up to you to choose which feed to watch
const 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 the activity; this avoids refetching the activity if WebSocket reconnects
activityWithStateUpdates.dispose();
// You should stop watching the feed, unless your app has another component that watches the same feed
if (shouldWatch) {
await feed.stopWatching();
}
// If you don't care about state updates, no need to call activityWithStateUpdates
await client.getActivity({
id: activityId,
});Adding activities to multiple feeds
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
currentselector, 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 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);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.
Activity list controls - client-side SDKs
Client-side SDKs allow controlling when and where to add new activities as they arrive. By default, only activities added by the current user and matching the filter provided to getOrCreate are added to the list (this is how social media apps usually work), and these activities are added to the start of the list. But it's possible to override this behavior:
import { activityFilter } from "@stream-io/feeds-client";
const feed = client.feed("user", userId, {
onNewActivity: ({ activity, currentUser }) => {
const requestConfig = feed.currentState.last_get_or_create_request_config;
if (!activityFilter(activity, requestConfig)) return "ignore";
return activity.user.id === currentUser?.id ? "add-to-start" : "ignore";
},
});- Creating Activities
- Reading Activities with Enrichment
- Translation
- Overview of All Activity Fields
- Adding Many Activities
- Visibility Levels
- Partial activity updates
- Batch Partial Activity Updates
- Updating Activities
- Deleting Activities
- Restore Activity
- Get activity
- Adding activities to multiple feeds
- Activity list controls - client-side SDKs