Extra Data

Extra Data is additional information that can be added to the default data of Stream. It is a dictionary of key-value pairs that can be attached to messages, users, channels, and most domain models in the SDK.

On Android, Extra Data is represented as Map<String, Any>. This allows you to store strings, numbers, booleans, lists, and nested maps.

Adding Extra Data

You can add extra data when creating or updating messages, users, channels, and other models.

Messages

Add extra data when creating a message:

val message: Message = Message(
    cid = "messaging:general",
    text = "Check out this product!",
    extraData = mapOf(
        "priority" to "high",
        "product_id" to "12345",
        "price" to 29.99,
    ),
)

channelClient.sendMessage(message).enqueue { result ->
    when (result) {
        is Result.Success -> { /* Message sent */ }
        is Result.Failure -> { /* Handle error */ }
    }
}

Users

Add extra data when updating the current user:

val user: User = User(
    id = "user-id",
    name = "John Doe",
    extraData = mapOf(
        "email" to "john.doe@example.com",
        "phone" to "+1234567890",
        "preferences" to mapOf(
            "notifications" to true,
            "theme" to "dark",
        ),
    ),
)

chatClient.updateUser(user).enqueue { result ->
    when (result) {
        is Result.Success -> { /* User updated */ }
        is Result.Failure -> { /* Handle error */ }
    }
}

Channels

Add extra data when creating a channel:

chatClient.createChannel(
    channelType = "messaging",
    channelId = "travel-group",
    memberIds = listOf("user-1", "user-2"),
    extraData = mapOf(
        "name" to "Travel Planning",
        "destination" to "Paris",
        "trip_date" to "2026-06-15",
    ),
).enqueue { result ->
    when (result) {
        is Result.Success -> { /* Channel created */ }
        is Result.Failure -> { /* Handle error */ }
    }
}

Reading Extra Data

All domain models have an extraData property of type Map<String, Any>. You need to cast values to the expected type when reading:

// Reading simple values
val email: String? = user.extraData["email"] as? String
val priority: String? = message.extraData["priority"] as? String
val price: Double? = message.extraData["price"] as? Double
val productId: String? = message.extraData["product_id"] as? String

// Reading nested maps
val preferences: Map<String, Any>? = user.extraData["preferences"] as? Map<String, Any>
val theme: String? = preferences?.get("theme") as? String
val notificationsEnabled: Boolean? = preferences?.get("notifications") as? Boolean

Type-Safe Extensions

For cleaner code, create extension properties on the models you use:

val User.email: String?
    get() = extraData["email"] as? String

val User.phone: String?
    get() = extraData["phone"] as? String

val Message.priority: String?
    get() = extraData["priority"] as? String

val Message.productId: String?
    get() = extraData["product_id"] as? String

val Channel.destination: String?
    get() = extraData["destination"] as? String

Then use them directly:

// Clean access via extensions
val email: String = user.email ?: ""
val priority: String = message.priority ?: "normal"
val destination: String = channel.destination ?: "Unknown"

Advanced Example

For complex data structures, create data classes with conversion functions:

data class BookingInfo(
    val flightNumber: String,
    val departureDate: String,
    val price: Double,
    val passengers: List<Passenger>,
)

data class Passenger(
    val name: String,
    val age: Int,
)

// Convert to extraData map
fun BookingInfo.toExtraData(): Map<String, Any> = mapOf(
    "flight_number" to flightNumber,
    "departure_date" to departureDate,
    "price" to price,
    "passengers" to passengers.map { passenger ->
        mapOf(
            "name" to passenger.name,
            "age" to passenger.age,
        )
    },
)

// Parse from extraData map
fun Map<String, Any>.toBookingInfo(): BookingInfo? {
    val flightNumber: String = this["flight_number"] as? String ?: return null
    val departureDate: String = this["departure_date"] as? String ?: return null
    val price: Double = this["price"] as? Double ?: return null
    val passengersData: List<Map<String, Any>> =
        (this["passengers"] as? List<*>)?.filterIsInstance<Map<String, Any>>() ?: emptyList()

    val passengers: List<Passenger> = passengersData.mapNotNull { data ->
        val name: String = data["name"] as? String ?: return@mapNotNull null
        val age: Int = (data["age"] as? Number)?.toInt() ?: return@mapNotNull null
        Passenger(name, age)
    }

    return BookingInfo(flightNumber, departureDate, price, passengers)
}

Then extend the Message model:

val Message.bookingInfo: BookingInfo?
    get() = (extraData["booking"] as? Map<String, Any>)?.toBookingInfo()

Use it when sending and reading messages:

// Sending a message with booking info
val booking = BookingInfo(
    flightNumber = "AB123",
    departureDate = "2026-06-15",
    price = 450.00,
    passengers = listOf(
        Passenger("John Doe", 30),
        Passenger("Jane Doe", 28),
    ),
)

val message: Message = Message(
    cid = "messaging:travel",
    text = "Here are our flight details!",
    extraData = mapOf("booking" to booking.toExtraData()),
)

// Reading booking info from a message
val bookingInfo: BookingInfo? = message.bookingInfo
bookingInfo?.let {
    println("Flight: ${it.flightNumber}")
    println("Passengers: ${it.passengers.map { p -> p.name }}")
}