Skip to main content

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.

Platform Support

FeatureiOSAndroidWebFlutterReact 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
ParameterTypeDescription
targetTypeAmityPostTargetTypeTarget type: user or community
targetIdstringUser ID or Community ID
roomIdstringLivestream room/channel ID
Optional
ParameterTypeDescription
textstringPost text content (livestream description)
titlestringPost title (livestream title)
productTags`AmityMediaProductTag[]null`Product tags with text position info
pinnedProductId`stringundefined`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)")
}

Update Product Tags

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

ParameterTypeRequiredDescription
postIdstringYesThe child room post ID
productTagsAmityMediaProductTag[]YesFull 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

ParameterTypeRequiredDescription
postIdstringYesThe child room post ID
productIdstringYesThe 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

ParameterTypeRequiredDescription
postIdstringYesThe 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

ParameterTypeRequiredDescription
roomIdstringYesThe livestream room ID
cohostIdstringYesThe user ID of the co-host
canManageProductTagsbooleanYesWhether 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

ParameterTypeRequiredDescription
locationstringYesPlacement identifier in format pageId/componentId/elementId (e.g. "livestream_page/product_tag_list/product_tag_item")
sourceTypeAnalyticsSourceTypeYesContent source type — POST or ROOM
sourceIdstringYesID 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

ParameterTypeRequiredDescription
locationstringYesPlacement identifier in format pageId/componentId/elementId (e.g. "livestream_page/product_tag_list/product_tag_item")
sourceTypeAnalyticsSourceTypeYesContent source type — POST or ROOM
sourceIdstringYesID 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
    )
}