LiveKit docs › Migration Guides › SDK v1 to v2 migration

---

# SDK migration from v1 to v2

> Overview of how to migrate your applications from LiveKit SDK v1.x to v2

Version 2 of the LiveKit SDKs include a small number of breaking changes, focused on:

- Streamlining APIs to reduce confusion and improve naming consistency.
- Updated APIs to accept a participant's identity instead of their SID, offering a more intuitive experience as identities are application-provided.
- Enabling the coexistence of multiple libraries dependent on libwebrtc with LiveKit native SDKs.

## Breaking changes across SDKs

This section outlines changes applicable to all frontend/client SDKs.

### `room.participants` -> `room.remoteParticipants`

In v2, we've updated the participants map on the room object, with key changes to note:

- Clarification: `localParticipant` has always been excluded from this map, so the term `participants` was previously misleading.
- Map key change: Instead of using the participant's `SID` as the map key, we now use their `identity`.

**JavaScript**:

```js

// legacy v1: in v1 participants were stored in a map with keys representing their SID. This led to unnecessary complications e.g. when trying to filter for a list of identities
const alice = room.participants.get('PA_8sMkEu4vhz4v');

// new in v2: you can now use a participant's identity (encoded in the token) to directly access it from the remoteParticipants map
const alice = room.remoteParticipants.get('alice');


```

---

**Android**:

```kotlin
// legacy v1: in v1 participants were stored in a map with keys representing their SID. This led to unnecessary complications e.g. when trying to filter for a list of identities
val alice = room.remoteParticipants['PA_8sMkEu4vhz4v'];

// new in v2: you can now use a participant's identity (encoded in the token) to directly access it from the remoteParticipants map
val alice = room.remoteParticipants[Participant.Identity('alice')];

```

---

**Swift**:

```swift
// v1
let alice = room.remoteParticipants["PA_8sMkEu4vhz4v"]

// v2
let alice = room.remoteParticipants["alice"]

```

---

**Flutter**:

```dart
/// legacy v1: in v1 participants were stored in a map with keys representing their SID. This led to unnecessary complications e.g. when trying to filter for a list of identities
final alice = room.participants['PA_8sMkEu4vhz4v'];

/// new in v2: you can now use a participant's identity (encoded in the token) to directly access it from the remoteParticipants map
final alice = room.getParticipantByIdentity('alice');

```

---

**Go**:

```go
// legacy v1
alice := room.GetParticipant("PA_8sMkEu4vhz4v")
remoteParticipants := room.GetParticipants()

// new in v2
alice := room.GetParticipantByIdentity("alice")
remoteParticipants := room.GetRemoteParticipants()

```

### `track` -> `trackPublication`

In version 1, our SDKs used the term `track` ambiguously, referring to both `TrackPublication` and `Track`. In version 2, we've simplified this terminology: now, all API references to publications explicitly use `trackPublications`. For instance,

- `participant.tracks` -> `participant.trackPublications`
- `participant.getTrack` -> `participant.getTrackPublication`
- `participant.videoTracks` -> `participant.videoTrackPublications`

**JavaScript**:

```js

// v1
const cameraPublication = room.localParticipant.getTrack(Track.Source.Camera);

// v2
const cameraPublication = room.localParticipant.getTrackPublication(Track.Source.Camera);


```

---

**Android**:

```kotlin

// v1
val trackPublications = room.localParticipant.tracks

// v2
val trackPublications = room.localParticipant.trackPublications

```

---

**Swift**:

```swift
// v1
let trackPublications = room.localParticipant.tracks

// v2
let trackPublications = room.localParticipant.trackPublications

```

---

**Flutter**:

```dart
/// v1
final audioTracks = room.localParticipant.audioTracks;
final videoTracks = room.localParticipant.videoTracks;

/// v2
final audioTrackPublications = room.localParticipant.audioTrackPublications;
final videoTrackPublications = room.localParticipant.videoTrackPublications;

```

---

**Go**:

```go
// legacy v1
publications := participant.Tracks()
cameraPublication := participant.GetTrack(livekit.TrackSource_CAMERA)

// new in v2
publications := participant.TrackPublications()
cameraPublication := participant.GetTrackPublication(livekit.TrackSource_CAMERA)

```

### Updated publishData API

We've streamlined the `publishData` API in v2, reducing its arguments to:

1. The payload (data being sent)
2. A `DataPublishOptions` object for advanced features

`DataPublishOptions` now allows you to:

- specify a list of recipient participants using their identities
- set a topic
- choose if the data should be delivered reliably (slower, with retries) or not (faster)

In our effort to remove server identities from user facing APIs, we've removed the need to specify participant SIDs for recipients. In v2, simply use participant identities, which are stable across reconnects.

**JavaScript**:

```javascript
// v1
localParticipant.publishData(data, DataPacketKind.Reliable, ['participant-sid']);

// v2
localParticipant.publishData(data, {
  reliable: true,
  destinationIdentities: ['participant-identity'],
});

```

---

**Android**:

```kotlin
// v1
room.localParticipant.publishData(
  data = msg,
  destination = listOf(participantSid)
)

// v2
room.localParticipant.publishData(
  data = msg,
  identities = listOf(Participant.Identity(identity))
)

```

---

**Swift**:

```swift
// v1
room.localParticipant.publishData(data: data, reliability: .reliable, destinations: ["participant-sid"])

// v2
let options = DataPublishOptions(reliable: true, destinationIdentities: [exampleIdentity])
try await room.localParticipant.publish(data: data, options: options)

```

---

**Flutter**:

```dart
/// v1
await room.localParticipant.publishData(
        utf8.encode('This is a sample data packet'),
        reliability = Reliability.reliable,
        destinationSids = [participantSid],
      );

/// v2
await room.localParticipant.publishData(
        utf8.encode('This is a sample data packet'),
        reliable = true,
        destinationIdentities = [participant.identity],
      );

```

---

**Go**:

```go
// legacy v1 publishing
localParticipant.PublishDataPacket(payloadBytes, livekit.DataPacket_RELIABLE, nil)
// legacy v1 receiving
cb := lksdk.NewRoomCallback()
cb.OnDataReceived = func(data []byte, rp *lksdk.RemoteParticipant) {
}
room := lksdk.CreateRoom(cb)

// v2 publishing
localParticipant.PublishDataPacket(lksdk.UserData(payloadBytes),
    lksdk.WithDataPublishReliable(true),
    lksdk.WithDataPublishTopic("topic"),
    lksdk.WithDataPublishDestination([]string{"alice", "bob"}),
)
// v2 receiving
cb := lksdk.NewRoomCallback()
cb.OnDataReceived = func(data []byte, params lksdk.DataReceiveParams) {
}
room := lksdk.NewRoom(cb)

```

### Async room SID

In order to speed up the initial connection, the room SID may not be immediately available upon connection. It's instead received later (typically within 300ms). To handle this, getting the room SID is done asynchronously in v2.

**JavaScript**:

```javascript
//v1
room.sid;

//v2
await room.getSid();

```

---

**Android**:

```kotlin
// v1
val roomSid = room.sid

// v2
coroutineScope {
  // room.getSid() is a suspend function
  val roomSid = room.getSid()
}

```

---

**Swift**:

```swift
// v1
let sid = room.sid

// v2
// In addition to the sid property, now there is an async method.
let sid = try await room.sid()

```

---

**Flutter**:

```dart
/// v1
final roomSid = room.sid;

/// v2
final roomSid = await room.getSid();

```

---

**Go**:

```go
// API is unchanged, but room.SID() will now block until the SID is available
roomID := room.SID()

```

### Removed `VideoQuality.OFF` from VideoQuality enum

In v2 we've removed the `OFF` option on the VideoQuality enum. Previously, setting OFF via the setQuality APIs had no effect and was confusing to users.

**JavaScript**:

```javascript
// v1
remotePublication.setQuality(VideoQuality.HIGH);

// v2 VideoQuality.OFF is no longer available
remotePublication.setQuality(VideoQuality.HIGH);

```

---

**Android**:

```kotlin
// v1
import livekit.LivekitModels.VideoQuality

// v2 the enum has moved to a different package, with OFF option removed
import io.livekit.android.room.track.VideoQuality

```

---

**Swift**:

```swift
// v1 Swift did not expose setVideoQuality APIs

// v2
remoteTrackPublication.set(videoQuality: .high)

```

---

**Flutter**:

```dart
/// v1 the lk_models.VideoQuality is an enum from protobuf
remoteTrackPublication.setVideoQuality(lk_models.VideoQuality.HIGH)

/// v2 VideoQuality.OFF is no longer available
remoteTrackPublication.setVideoQuality(VideoQuality.HIGH)

```

---

**Go**:

```go
// SetVideoQuality was previously unimplemented
// returns error if quality is livekit.VideoQuality_OFF
err := remoteTrackPublication.SetVideoQuality(livekit.VideoQuality_HIGH)

```

## Platform specific changes

### Android

#### Removal of previously deprecated APIs

- `LiveKit.connect` - Please use `LiveKit.create` and `Room.connect` instead.
- `Room.listener` - Please use `Room.events` instead.
- `Participant.listener` - Please use `Participant.events` instead.

#### Renaming of org.webrtc package to livekit.org.webrtc

We've renamed our internal `org.webrtc` package to `livekit.org.webrtc` to prevent conflicts with other WebRTC implementations. If your code references this package, update your import as follows:

```kotlin
// v1
import org.webrtc.*

// v2
import livekit.org.webrtc.*

```

#### Moved composables into a separate package

Composables, including `VideoRenderer` have been moved into a separate package, `components-android`. Previously the SDK depended on Jetpack Compose, causing View-based apps to depend on an unnecessary package. By moving these components to a separate package, only Compose-based apps will need to depend on it.

To migrate, add in your `build.gradle`:

```groovy
dependencies {
  implementation "io.livekit:livekit-android-compose-components:1.0.0"
}

```

The `VideoRenderer` composable has also been renamed to `VideoTrackView` to maintain parity with other platforms.

#### Participant.Sid and Identity inline value classes

To avoid confusion between participant `sid` and `identity` which shared the `String` type, we've added the `Participant.Sid` and `Participant.Identity` inline value classes. This will prevent inadvertently using one in place of the other.

### Flutter

#### Removal of previously deprecated APIs

- `LiveKitClient.connect` - Please use `var room = Room(...)` and `room.connect` instead.
- `track` in `TrackMutedEvent/TrackUnmutedEvent` - Use `publication` instead
- `TrackStreamStateUpdatedEvent.trackPublication` - Use `TrackStreamStateUpdatedEvent.publication` instead
- `RemotePublication.videoQuality` - Use `RemotePublication.setVideoQuality(quality)` instead
- `RemotePublication.subscribed` - Use `RemotePublication.subscribe()` or `unsubscribe()` instead
- `RemotePublication.enabled` - Use `RemotePublication.enable()` or `disable()` instead
- `Participant.unpublishTrack` - Use `Participant.removePublishedTrack` instead
- Removed `AudioPublishOptions.stopMicTrackOnMute`

### Javascript/Typescript

#### `webAudioMix` is no longer experimental

For this release, we're removing the `experimental` notion of the `expWebAudioMix` room option.

When using web audio mixing, setting volume directly on the HTMLAudioElements would no longer have any effects. Instead, you can use `setVolume` methods that exist on both `RemoteParticipant` and `RemoteAudioTrack` to control the output volume.

#### Removal of previously deprecated APIs

- `RoomConnectOptions.publishOnly` - The publishOnly mode has been deprecated even before v1.0, finally removing those bits in the code
- `RoomState` - Use `ConnectionState` instead
- `RoomEvent.StateChanged` - Use `RoomEvent.ConnectionStateChanged` instead
- `TrackPublishOptions.audioBitrate` - Use `TrackPublishOptions.audioPreset` instead
- `room.getActiveAudioOutputDevice()` - Use `room.getActiveDevice('audiooutput')` instead

### Swift

#### Swift concurrency support

Swift SDK v2 has migrated to [Swift Concurrency(async/await)](https://developer.apple.com/documentation/swift/updating_an_app_to_use_swift_concurrency) from [Google Promises](https://github.com/google/promises).

#### Renamed APIs

- WebRTC types such as `RTCVideoFrame` are now _not exported_ by the SDK, use new types defined by the SDK(`VideoFrame` etc) instead.
- `LocalParticipant.publish(track:publishOptions:)` has been renamed to `LocalParticipant.publish(track:options:)`.
- `RoomDelegate` and `ParticipantDelegate` signatures have been renamed. Xcode compiler will fail and suggest a rename if any of the previous delegates are used.
- Legacy statistics (`TrackStats`) has been replaced with `TrackStatistics`.

### Go

#### CreateRoom -> NewRoom

The `CreateRoom` function has been renamed to `NewRoom` to disambiguate it from the `RoomService.CreateRoom` API in the server SDK.

---

This document was rendered at 2026-06-07T11:36:48.393Z.
For the latest version of this document, see [https://docs.livekit.io/reference/migration-guides/migrate-from-v1.md](https://docs.livekit.io/reference/migration-guides/migrate-from-v1.md).

To explore all LiveKit documentation, see [llms.txt](https://docs.livekit.io/llms.txt).