Skip to main content
LiveKit allows participants to send arbitrary data to other participants in the room using data channels. This is useful for sending text messages, custom signaling, or any application-specific data.

Publishing Data

Send data to other participants using the publish(data:options:) method:
let message = "Hello, world!"
let data = message.data(using: .utf8)!

try await room.localParticipant.publish(data: data)

Data Publish Options

Control delivery behavior using DataPublishOptions:
let options = DataPublishOptions(
    reliable: true,                    // Use reliable data channel
    destinationIdentities: [],         // Send to specific participants (empty = all)
    topic: "chat"                      // Optional topic/channel identifier
)

try await room.localParticipant.publish(data: data, options: options)
Options:
  • reliable: When true, uses TCP-like reliable delivery. When false, uses UDP-like lossy delivery for lower latency
  • destinationIdentities: Array of participant identities to receive the data. Empty array sends to all participants
  • topic: Optional string to categorize messages (useful for filtering on the receiving end)

Reliable vs. Lossy Delivery

Best for: Chat messages, file transfers, critical data
let options = DataPublishOptions(reliable: true)
try await room.localParticipant.publish(data: data, options: options)
  • Guarantees delivery and order
  • Higher latency under poor network conditions
  • Uses TCP-like transmission

Receiving Data

Implement the RoomDelegate protocol to receive data messages:
class MyRoomDelegate: RoomDelegate {
    func room(
        _ room: Room,
        participant: RemoteParticipant?,
        didReceiveData data: Data,
        topic: String?
    ) {
        // Handle received data
        if let message = String(data: data, encoding: .utf8) {
            print("Received message: \(message)")
            print("From participant: \(participant?.identity ?? "unknown")")
            print("Topic: \(topic ?? "none")")
        }
    }
}

// Add delegate to room
room.add(delegate: MyRoomDelegate())

Filtering by Topic

Use topics to organize different types of data messages:
func room(
    _ room: Room,
    participant: RemoteParticipant?,
    didReceiveData data: Data,
    topic: String?
) {
    switch topic {
    case "chat":
        handleChatMessage(data, from: participant)
    case "game-state":
        handleGameState(data)
    case "reaction":
        handleReaction(data, from: participant)
    default:
        print("Unknown topic: \(topic ?? "none")")
    }
}

Sending to Specific Participants

Send data to one or more specific participants:
// Send to a single participant
let targetIdentity = Participant.Identity("user123")
let options = DataPublishOptions(
    reliable: true,
    destinationIdentities: [targetIdentity]
)

try await room.localParticipant.publish(data: data, options: options)
// Send to multiple participants
let identities = [
    Participant.Identity("user123"),
    Participant.Identity("user456")
]

let options = DataPublishOptions(
    reliable: true,
    destinationIdentities: identities
)

try await room.localParticipant.publish(data: data, options: options)

Size Limits

Each data payload must not exceed 15KB. For larger data:
func sendLargeData(_ data: Data) async throws {
    let chunkSize = 15_000  // 15KB
    let chunks = stride(from: 0, to: data.count, by: chunkSize).map {
        data[$0..<min($0 + chunkSize, data.count)]
    }
    
    for (index, chunk) in chunks.enumerated() {
        let metadata = "chunk-\(index)-of-\(chunks.count)"
        try await room.localParticipant.publish(
            data: chunk,
            options: DataPublishOptions(
                reliable: true,
                topic: metadata
            )
        )
    }
}

Setting Default Options

Set default data publish options when creating a room:
let roomOptions = RoomOptions(
    defaultDataPublishOptions: DataPublishOptions(
        reliable: true,
        topic: "default"
    )
)

let room = Room(roomOptions: roomOptions)

// This will use the default options
try await room.localParticipant.publish(data: data)

Use Cases

Chat Messages

struct ChatMessage: Codable {
    let text: String
    let timestamp: Date
}

func sendChatMessage(_ text: String) async throws {
    let message = ChatMessage(text: text, timestamp: Date())
    let data = try JSONEncoder().encode(message)
    
    try await room.localParticipant.publish(
        data: data,
        options: DataPublishOptions(
            reliable: true,
            topic: "chat"
        )
    )
}

Real-time Cursor Position

struct CursorPosition: Codable {
    let x: Double
    let y: Double
}

func sendCursorPosition(_ position: CursorPosition) async throws {
    let data = try JSONEncoder().encode(position)
    
    try await room.localParticipant.publish(
        data: data,
        options: DataPublishOptions(
            reliable: false,  // Low latency, lossy OK
            topic: "cursor"
        )
    )
}

Reactions

func sendReaction(_ emoji: String) async throws {
    let data = emoji.data(using: .utf8)!
    
    try await room.localParticipant.publish(
        data: data,
        options: DataPublishOptions(
            reliable: true,
            topic: "reaction"
        )
    )
}

Error Handling

Handle errors when publishing data:
do {
    try await room.localParticipant.publish(data: data)
} catch {
    print("Failed to publish data: \(error)")
    
    if let lkError = error as? LiveKitError {
        switch lkError.type {
        case .invalidState:
            print("Data channel is not open")
        default:
            print("Other error: \(lkError)")
        }
    }
}

See Also

  • LocalParticipant.swift:117 (publish(data:options:))
  • Core/DataChannelPair.swift

Build docs developers (and LLMs) love