Model or dataset
livekit/client-sdk-js avatar
livekit/client-sdk-js

livekit-client: realtime video, audio and data in the browser

LiveKit browser client SDK (javascript)

662 stars311 forksTypeScriptApache-2.0

At a glance

What is it?
livekit-client is the TypeScript and JavaScript browser SDK for LiveKit, the WebRTC media server: it manages room connections, track publishing and subscription, adaptive quality and dynamic bitrate, and exposes the whole thing as an event-driven API. It suits teams building calls, live streaming or multimodal apps against LiveKit Cloud or a self-hosted deployment, and it is not the SDK to reach for when the product is a voice agent.
Who is it for?
livekit-client is a well-factored choice for browser applications that need real time media over WebRTC and are willing to depend on a LiveKit server, whether hosted or self-managed. It is not the right SDK for a code-first voice agent, where LiveKit Agents in Python or Node.js is the documented path, and it does not remove the server-side obligations of issuing access tokens and operating SFU capacity.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository received new commits within the last day.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What livekit-client is: the browser half of a WebRTC media stack

The SDK's job is to add realtime video, audio and data features to a JavaScript or TypeScript application. The README describes the shape of the system plainly: you connect to LiveKit Cloud or a self-hosted server, and from there you can build applications such as multi-modal AI, live streaming or video calls with a few lines of code.

The client is not a peer-to-peer library in isolation. It is the browser end of a server-mediated WebRTC stack, and that distinction explains the API. Everything revolves around a Room object. The Room holds the connection to the server, the local participant that publishes your camera and microphone, the remote participants that arrive in the room, and the tracks attached to them. You never touch a peer connection directly; you ask the room to connect, you subscribe to its events, and you attach tracks to DOM elements.

The repository is TypeScript with WebRTC as its topic, licensed under Apache-2.0 and developed under the livekit organisation. It ships from the main branch with regular releases: v2.23.0 on 2026-10-16, v2.22.4 on 2026-10-09 and v2.22.3 on 2026-09-07, with the last push on 2026-10-19. Documentation lives at docs.livekit.io, with the SDK reference on the client-sdk-js page.

One warning in the README deserves to be read before anything else. This is v2 of livekit-client, and migrating from v1.x to v2.x comes with a small set of breaking changes, for which the project publishes a dedicated migration guide.

Installing the SDK: package manager or a script tag

There are three documented install paths, and the third one is unusual enough to be worth reading.

With Yarn:

shell
yarn add livekit-client

With npm:

shell
npm install livekit-client --save

And without any package manager at all, by including the minified build directly:

html
<script src="https://cdn.jsdelivr.net/npm/livekit-client/dist/livekit-client.umd.min.js"></script>

The script tag route comes with a naming rule. The module is exported under LivekitClient in the global namespace, and symbols from the class must be prefixed with it, so Room becomes LivekitClient.Room. That is the standard cost of a UMD build in a page that has no bundler, and the README documents it rather than leaving you to discover it.

The package layout in package.json shows what is actually shipped. The main and unpkg entries point at the UMD bundle, the module entry at the ESM build, and the exports map adds several subpath entries: an e2ee-worker entry for the end-to-end encryption worker, a packet-trailer-worker entry, a frame-metadata-worker entry, and a core entry with its own type declarations and an ESM module. The files array includes dist and src, so the TypeScript sources ship alongside the bundles, and the sideEffects array flags the worker bundles as having side effects, which tells a bundler not to tree-shake them away.

There is also a typesVersions map for TypeScript below 4.8, which redirects the type declarations to ts4.2 directories so that older compilers still resolve the types correctly.

The Room object: adaptiveStream, dynacast and capture defaults

The room is constructed with options, and the three that matter most in production are about bandwidth rather than about media plumbing:

typescript
// creates a new room with options
const room = new Room({
  // automatically manage subscribed video quality
  adaptiveStream: true,

  // optimize publishing bandwidth and CPU for published tracks
  dynacast: true,

  // default capture settings
  videoCaptureDefaults: {
    resolution: VideoPresets.h720.resolution,
  },
});

adaptiveStream automatically manages subscribed video quality, which means the client decides how much resolution to receive for each subscribed video based on what it is displaying and what the network can carry. dynacast optimises publishing bandwidth and CPU for published tracks, so a participant who is not sending meaningful video stops consuming the resources for it. videoCaptureDefaults sets the capture side, here the 720p resolution from the VideoPresets set.

These two flags are the SDK's answer to the classic WebRTC failure where a four-way call on a laptop connection degrades for everyone. Turning them on is close to mandatory for any multi-party call, and it is the first thing to check when a new LiveKit integration performs badly on a laptop rather than on the office network.

The remaining option, videoCaptureDefaults, is where you set a device policy once instead of overriding it at every publish call. The presets are a named set rather than raw pixel counts, which means the same configuration works across the capture paths the SDK exposes.

Connecting: tokens, pre-warming the connection and the event model

A LiveKit client cannot connect without an access token, and the token has to come from your server. The README's own comment states the two options: make a request to an endpoint using the LiveKit server SDK, or use a prebuilt TokenSource. The token is not something the browser can mint, and the authentication documentation is the reference for the details.

The connection sequence in the README is worth reading in order, because the order is the point:

typescript
// get your url from livekit's dashboard, or point it at a self hosted livekit deployment
const url = 'ws://localhost:7800';

// generate a token by making a request to a endpoint using the livekit server sdk or
// using a prebuilt TokenSource (documented below)
const token = '...';

// pre-warm connection, this can be called as early as your page is loaded
room.prepareConnection(url, token);

// set up event listeners
room
  .on(RoomEvent.TrackSubscribed, handleTrackSubscribed)
  .on(RoomEvent.TrackUnsubscribed, handleTrackUnsubscribed)
  .on(RoomEvent.ActiveSpeakersChanged, handleActiveSpeakerChange)
  .on(RoomEvent.Disconnected, handleDisconnect)
  .on(RoomEvent.LocalTrackUnpublished, handleLocalTrackUnpublished);

// connect to room
await room.connect(url, token);
console.log('connected to room', room.name);

// publish local camera and mic tracks
await room.localParticipant.enableCameraAndMicrophone();

prepareConnection is the line most people miss. It can be called as early as your page is loaded, before the user has clicked anything, so the negotiation work starts while the user is still reading. The alternative is to make the user wait for ICE and DTLS setup at the moment they press Join, which is where most WebRTC demos feel slow.

Event registration is chained off the room object and happens before connecting, so no event is missed by ordering. The events in the example cover the four states a UI actually has to react to: a remote track arrived and needs a video or audio element, a remote track went away and the element must be detached, the set of active speakers changed so indicators can move, the room disconnected, and a local track was unpublished so the UI can stop rendering it. The track handlers show the attach and detach pattern, where track.attach() creates the media element and track.detach() removes it.

For CommonJS users the same flow is written with require, and the README shows the three-step version of it: require the module, construct the room, prepare the connection and connect.

Workers, examples and how the package is built

The repository shows how a browser SDK of this kind is put together in practice. The build is Rollup, with three configurations: rollup.config.js for the main bundle, rollup.config.worker.js for the standard worker, and rollup.config.fm-worker.js for the frame metadata worker. That matches the exports map, where the end-to-end encryption worker, the packet trailer worker and the frame metadata worker are separate entry points with their own bundles and their own type declarations.

Splitting workers out is not cosmetic. The end-to-end encryption worker has to run off the main thread because media encryption and decryption are per-frame CPU work, and the frame metadata worker exists for the same reason. Any application that turns on end-to-end encryption is loading a second bundle at runtime, which is worth remembering when you budget for bundle size.

The repository also carries a .size-limit.cjs file, which is how the project keeps the bundle size budget enforced in continuous integration, and a .browserslistrc for the browser support targets. Tests are split between vitest for unit work, with vitest.e2e.config.mts for end-to-end tests, and a smoke-tests/ directory. Examples cover the feature areas worth knowing about: examples/demo/, examples/data-tracks/ for the data channel API, examples/rpc/ for remote procedure calls, examples/state-machine/ and examples/shared/.

The package is also published to npm under the name livekit-client at version 2.23.0, and the tooling around it uses pnpm, with pnpm-lock.yaml and pnpm-workspace.yaml committed, plus a .changeset/ directory for versioning changes. Changesets are why the release numbers move in patch increments for internal work.

Where livekit-client is the wrong choice

The first wrong case is a code-first voice agent. The README carries an important note for exactly this, pointing out that for Voice AI, LiveKit Agents is the SDK, with speech to text, large language models, text to speech, turn detection, expressive speech, keyterm accuracy, tool usage and telephony all bundled into that framework, available in both Python and Node.js. The two projects overlap, but they are not the same tool, and the README is explicit that the agents SDK is the one for voice.

The second wrong case is a native mobile application. This is the browser SDK: the repository's topic list is javascript, typescript and webrtc, and the package targets browsers with a browserslist configuration. A mobile app needs the corresponding native SDKs.

The third is a project that wants to avoid the server. Because the client is only half of the design, adopting it means operating or paying for a LiveKit deployment, issuing tokens from your backend, and accepting that media routing goes through an SFU rather than peer to peer. A team whose real requirement is a two-person call on a LAN, with no interest in a media server, is solving a different problem.

The fourth is any expectation that the SDK removes WebRTC complexity from the browser. It removes a great deal of it, but the constraints of WebRTC remain: browsers decide which codecs are available, autoplay and permission policies still gate the first play, and getUserMedia requires a secure context. The README documents none of that, because it is the platform rather than the library, and it is where the surprises in a first integration usually come from.

The fifth is the migration itself. Moving an existing v1.x application to v2.x is a real cost, with a small set of breaking changes and a separate migration guide, and the v1 line is no longer what the examples and the SDK reference describe.

Editorial conclusion

livekit-client is a well-factored choice for browser applications that need real time media over WebRTC and are willing to depend on a LiveKit server, whether hosted or self-managed. It is not the right SDK for a code-first voice agent, where LiveKit Agents in Python or Node.js is the documented path, and it does not remove the server-side obligations of issuing access tokens and operating SFU capacity. Verify first that you can issue an access token from your backend, since the client cannot connect without one, that the self-hosted endpoint URL scheme matches your deployment, and that your build tooling handles the separate worker entry points before you rely on end-to-end encryption or frame metadata.

Frequently asked questions

How do I install the livekit-client SDK?

With Yarn you run yarn add livekit-client, and with npm you run npm install livekit-client --save. If you have no package manager, the README shows including the minified UMD build from jsdelivr with a script tag, in which case the module is exposed under the LivekitClient global and symbols are prefixed with it, so Room becomes LivekitClient.Room.

What does room.prepareConnection() do?

It pre-warms the connection, and the README notes it can be called as early as your page is loaded, before the actual connect call. The effect is that negotiation work happens before the user acts, instead of during the moment they join.

How does a client get an access token for LiveKit?

You need an access token first, and the README says to generate one by making a request to an endpoint using the LiveKit server SDK, or by using a prebuilt TokenSource. The authentication documentation at docs.livekit.io covers the details, and the browser client cannot mint a token itself.

What are adaptiveStream and dynacast for?

adaptiveStream automatically manages subscribed video quality, and dynacast optimises publishing bandwidth and CPU for published tracks. Both are set as Room options at construction time, alongside videoCaptureDefaults for the capture side.

Is livekit-client v2 a breaking upgrade from v1?

Yes, the README says this is v2 of livekit-client and that migrating from v1.x to v2.x comes with a small set of breaking changes, with a dedicated migration guide covering the details.

Should I use livekit-client or LiveKit Agents for a voice AI app?

For Voice AI the README points to LiveKit Agents, which bundles speech to text, large language models, text to speech, turn detection, expressive speech, keyterm accuracy, tool usage and telephony, and is available in Python and Node.js. livekit-client is the browser SDK for realtime video, audio and data.

Official sources

  1. License: Apache-2.0
  2. livekit/client-sdk-js on GitHub
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/livekit-client-sdk-js.svg)](https://hysenlabs.com/projects/livekit-client-sdk-js)

Was this useful?