Product Tagging
Livestream posts support product tagging, allowing hosts and authorized co-hosts to showcase products from your catalog during a live broadcast. This guide covers pinning and unpinning products on a room post so viewers can see featured items prominently during the stream.
Child Post ID Required: Both pinProduct and unpinProduct operate on the child room post (not the parent post). Use the child post ID returned when the room post was created.
| Feature | iOS | Android | Web | Flutter | React Native |
|---|
| Create Room Post | ✅ | ✅ | ✅ | ❌ | ❌ |
| Update Product Tags | ✅ | ✅ | ✅ | ❌ | ❌ |
| Pin Product | ✅ | ✅ | ✅ | ❌ | ❌ |
| Unpin Product | ✅ | ✅ | ✅ | ❌ | ❌ |
| Update Co-host Permissions | ✅ | ✅ | ✅ | ❌ | ❌ |
| Mark Product as Viewed | ✅ | ✅ | ✅ | ❌ | ❌ |
| Mark Product as Clicked | ✅ | ✅ | ✅ | ❌ | ❌ |
Create Room Post
Creates a room post that references a livestream room, with optional product tagging and pre-pinned product. This is the entry point for attaching products to a livestream before or at the time of broadcast.
Parameters
Required
| Parameter | Type | Description |
|---|
targetType | AmityPostTargetType | Target type: user or community |
targetId | string | User ID or Community ID |
roomId | string | Livestream room/channel ID |
Optional
| Parameter | Type | Description | |
|---|
text | string | Post text content (livestream description) | |
title | string | Post title (livestream title) | |
productTags | `AmityMediaProductTag[] | null` | Product tags with text position info |
pinnedProductId | `string | undefined` | Product ID to pre-pin at creation (must be in productTags) |
Code Examples
let postRepository = AmityPostRepository(client: AmityUIKitManager.client)
// Basic room post
let builder = AmityRoomPostBuilder(roomId: "room_abc123", text: "Welcome to my livestream!")
builder.setTitle("My Livestream")
do {
let post = try await postRepository.createRoomPost(
builder,
targetId: "community_abc123",
targetType: .community,
metadata: nil,
mentionees: nil
)
print("Post created: \(post.postId)")
} catch {
print("Failed to create room post: \(error)")
}
// Room post with product tags and a pre-pinned product
do {
let productTag = AmityProductTag(productId: "prod_001", text: "Nike Air Max", index: 38, length: 12)
let post = try await postRepository.createRoomPost(
builder,
targetId: "community_abc123",
targetType: .community,
metadata: nil,
mentionees: nil,
productTags: [productTag],
pinnedProductId: "prod_001"
)
print("Post created with pinned product: \(post.postId)")
} catch {
print("Failed to create room post: \(error)")
}
Replaces the entire productTags array on a livestream post. Pass an empty array to clear all tags. If the currently pinned product is not included in the new array, it is automatically unpinned.
Complete Replacement: This operation fully replaces existing product tags. To add a single product, fetch the current productTags array first, append the new item, then call updateProductTags with the merged array.
Parameters
| Parameter | Type | Required | Description |
|---|
postId | string | Yes | The child room post ID |
productTags | AmityMediaProductTag[] | Yes | Full replacement array of product tags. Pass [] to clear all |
Code Examples
let postRepository = AmityPostRepository(client: AmityUIKitManager.client)
// Replace all product tags
do {
let updatedPost = try await postRepository.updateProductTags(
postId: "childPostId_abc123",
productTags: [
AmityProductTag(productId: "prod_001"),
AmityProductTag(productId: "prod_002")
]
)
print("Tags updated: \(updatedPost.productTags.count)")
} catch {
print("Failed to update product tags: \(error)")
}
// Clear all product tags
do {
let updatedPost = try await postRepository.updateProductTags(
postId: "childPostId_abc123",
productTags: []
)
print("All tags cleared. pinnedProductId: \(String(describing: updatedPost.pinnedProductId))")
} catch {
print("Failed to clear product tags: \(error)")
}
Pin Product
Pins a product to a livestream post, making it prominently displayed to all viewers. The product must already exist in the post’s productTags array. Pinning a new product automatically replaces any currently pinned product — no need to unpin first.
Parameters
| Parameter | Type | Required | Description |
|---|
postId | string | Yes | The child room post ID |
productId | string | Yes | The product ID to pin. Must exist in the post’s productTags array |
Code Examples
let postRepository = AmityPostRepository(client: AmityUIKitManager.client)
do {
let updatedPost = try await postRepository.pinProduct(
postId: "childPostId_abc123",
productId: "prod_001"
)
print("Product pinned. pinnedProductId: \(String(describing: updatedPost.pinnedProductId))")
} catch {
print("Failed to pin product: \(error)")
}
Unpin Product
Removes the pinned product from a livestream post. The product remains in the productTags array but is no longer prominently displayed. This operation is idempotent — calling it when no product is pinned succeeds without error.
Parameters
| Parameter | Type | Required | Description |
|---|
postId | string | Yes | The child room post ID |
Code Examples
let postRepository = AmityPostRepository(client: AmityUIKitManager.client)
do {
let updatedPost = try await postRepository.unpinProduct(postId: "childPostId_abc123")
print("Product unpinned. pinnedProductId: \(String(describing: updatedPost.pinnedProductId))")
} catch {
print("Failed to unpin product: \(error)")
}
Update Co-host Permissions
Allows the room host to grant or revoke a co-host’s ability to manage product tags during a livestream. When canManageProductTags is true, the co-host can pin/unpin products and update the tagged product list. This operation is host-only and takes effect immediately.
Parameters
| Parameter | Type | Required | Description |
|---|
roomId | string | Yes | The livestream room ID |
cohostId | string | Yes | The user ID of the co-host |
canManageProductTags | boolean | Yes | Whether the co-host can pin/unpin products and update product tags |
Code Examples
do {
let updatedRoom = try await roomService.updateCohostPermissions(
roomId: "room_abc123",
cohostId: "cohost_456",
canManageProductTags: true
)
print("Permissions updated: \(updatedRoom?.roomId ?? "")")
} catch {
print("Failed to update co-host permissions: \(error)")
}
Mark Product as Viewed
Tracks when a user views a product that has been tagged in content. This is a fire-and-forget analytics call — it queues an impression event locally and syncs it to the backend in the background. Events are automatically deduplicated per product per source, so viewing the same product multiple times in the same post or room only records one impression.
Deduplication: The event key is {productId}.view.{sourceType}.{sourceId} . The same product viewed in a different post or room creates a separate event.
Parameters
| Parameter | Type | Required | Description |
|---|
location | string | Yes | Placement identifier in format pageId/componentId/elementId (e.g. "livestream_page/product_tag_list/product_tag_item") |
sourceType | AnalyticsSourceType | Yes | Content source type — POST or ROOM |
sourceId | string | Yes | ID of the source content (e.g. postId or roomId) |
Code Examples
// product is an AmityProduct instance obtained from a tagged product list.
// Call markAsViewed when a product card becomes visible on screen.
func handleProductViewed(_ product: AmityProduct, postId: String) {
product.analytics.markAsViewed(
location: "\(pageId)/\(componentId)/\(elementId)", // format: "pageId/componentId/elementId"
sourceType: .post, // AmityAnalyticsSourceType: .post, .room, etc.
sourceId: postId // postId or roomId
)
}
Mark Product as Clicked
Tracks when a user clicks on a product link to visit the product page or external URL. Like markAsViewed, this is fire-and-forget with automatic deduplication — clicking the same product in the same source only records one click event.
Deduplication: The event key is {productId}.linkClicked.{sourceType}. {sourceId}. Clicking the same product in a different source creates a separate event.
Parameters
| Parameter | Type | Required | Description |
|---|
location | string | Yes | Placement identifier in format pageId/componentId/elementId (e.g. "livestream_page/product_tag_list/product_tag_item") |
sourceType | AnalyticsSourceType | Yes | Content source type — POST or ROOM |
sourceId | string | Yes | ID of the source content (e.g. postId or roomId) |
Code Examples
// product is an AmityProduct instance obtained from a tagged product list.
// Call markAsClicked when a user taps a product card to open its URL.
func handleProductTapped(_ product: AmityProduct, postId: String) {
product.analytics.markAsClicked(
location: "\(pageId)/\(componentId)/\(elementId)", // format: "pageId/componentId/elementId"
sourceType: .post, // AmityAnalyticsSourceType: .post, .room, etc.
sourceId: postId // postId or roomId
)
}