LiveKit docs › Building Frontends › Authentication › Custom token generation

---

# Custom token generation

> Use a pre-existing token generation mechanism with LiveKit SDKs.

## Overview

If you already have a way of generating LiveKit tokens and don't want to use the [token server](https://docs.livekit.io/frontends/build/authentication/sandbox-token-server.md) or [endpoint token generation](https://docs.livekit.io/frontends/build/authentication/endpoint.md), you can use a custom `TokenSource`. This allows you to provide your own token generation mechanism, with automatic token fetching, caching, and refreshing included.

> 💡 **Agent dispatch**
> 
> When using a custom `TokenSource` with agent applications, agent information (like `agentName` and `agentMetadata`) is available in the options parameter. If your custom token generation function calls a standard token endpoint, you should package this agent information into `room_config` before sending the request. The endpoint will then pass `room_config` directly to the access token builder. See the examples below for platform-specific syntax.

### Caching tokens

`TokenSource.custom` refetches cached tokens when they expire, or when the input parameters passed into the `fetch` method change.

If you'd like to avoid the automatic caching behavior or handle it manually, see [`TokenSource.literal`](https://github.com/livekit/client-sdk-js?tab=readme-ov-file#tokensourceliteral).

## Use a custom TokenSource

This example shows how to use a custom `TokenSource` to connect to a LiveKit room.

**JavaScript**:

```typescript
import { Room, TokenSource } from 'livekit-client';

const LIVEKIT_URL = "%{wsURL}%";

// Create the TokenSource
const tokenSource = TokenSource.custom(async (options) => {
  // Run your custom token generation logic, using values in `options` as inputs
  // `options` includes: roomName, participantName, agentName, roomConfig, etc.
  // For agent applications, if calling a standard endpoint, package agent info into roomConfig
  const participantToken = await customTokenGenerationFunction(
    options.roomName, 
    options.participantName, 
    options.agentName, // Available when using Session APIs with agentName
    /* etc */
  );

  return { serverUrl: LIVEKIT_URL, participantToken };
});

// Generate a new token (cached and automatically refreshed as needed)
const { serverUrl, participantToken } = await tokenSource.fetch({ roomName: "room name to join" });

// Use the generated token to connect to a room
const room = new Room();
room.connect(serverUrl, participantToken);

```

---

**React**:

```typescript
import { TokenSource } from 'livekit-client';
import { useSession, SessionProvider } from '@livekit/components-react';

const LIVEKIT_URL = "%{wsURL}%";

// Create the TokenSource
// 
// If your TokenSource.custom relies on other dependencies other than `options`, be
// sure to wrap it in a `useMemo` so that the reference stays stable.
const tokenSource = TokenSource.custom(async (options) => {
  // Run your custom token generation logic, using values in `options` as inputs
  // `options` includes: roomName, participantName, agentName, roomConfig, etc.
  // For agent applications, if calling a standard endpoint, package agent info into roomConfig
  const participantToken = await customTokenGenerationFunction(
    options.roomName, 
    options.participantName, 
    options.agentName, // Available when using Session APIs with agentName
    /* etc */
  );

  return { serverUrl: LIVEKIT_URL, participantToken };
});

export const MyPage = () => {
  const session = useSession(tokenSource, { roomName: "room name to join" });

  // Start the session when the component mounts, and end the session when the component unmounts
  useEffect(() => {
    session.start();
    return () => {
      session.end();
    };
  }, []);

  return (
    <SessionProvider session={session}>
      <MyComponent />
    </SessionProvider>
  )
}

export const MyComponent = () => {
  // Access the session available via the context to build your app
  // ie, show a list of all camera tracks:
  const cameraTracks = useTracks([Track.Source.Camera], {onlySubscribed: true});
  return (
    <>
      {cameraTracks.map((trackReference) => {
        return (
          <VideoTrack {...trackReference} />
        )
      })}
    </>
  )
}

```

---

**Swift**:

```swift
import LiveKitComponents

let LIVEKIT_URL = URL(string: "%{wsURL}%")!

public struct MyTokenSource: TokenSourceConfigurable {}

public extension MyTokenSource {
    func fetch(_ options: TokenRequestOptions) async throws -> TokenSourceResponse {
        // Run your custom token generation logic, using values in `options` as inputs
        // `options` includes: roomName, participantName, agentName, roomConfig, etc.
        // For agent applications, if calling a standard endpoint, package agent info into roomConfig
        let participantToken = await customTokenGenerationFunction(
            options.roomName, 
            options.participantName, 
            options.agentName, // Available when using Session APIs with agentName
            /* etc */
        )

        return TokenSourceResponse(serverURL: LIVEKIT_URL, participantToken: participantToken)
    }
}

@main
struct SessionApp: App {
    let session = Session(tokenSource: MyTokenSource())

    var body: some Scene {
        WindowGroup {
            ContentView()
                .environmentObject(session)
                .alert(session.error?.localizedDescription ?? "Error", isPresented: .constant(session.error != nil)) {
                    Button(action: session.dismissError) { Text("OK") }
                }
                .alert(session.agent.error?.localizedDescription ?? "Error", isPresented: .constant(session.agent.error != nil)) {
                    AsyncButton(action: session.end) { Text("OK") }
                }
        }
    }
}

struct ContentView: View {
    @EnvironmentObject var session: Session

    var body: some View {
        if session.isConnected {
            AsyncButton(action: session.end) {
                Text("Disconnect")
            }

            Text(String(describing: session.agent.agentState))
        } else {
            AsyncButton(action: session.start) {
                Text("Connect")
            }
        }
    }
}

```

---

**Android**:

```kotlin
val LIVEKIT_URL = "%{wsURL}%"

val tokenSource = remember {
    TokenSource.fromCustom { options ->
        // Run your custom token generation logic, using values in `options` as inputs
        // `options` includes: roomName, participantName, agentName, roomConfig, etc.
        // For agent applications, if calling a standard endpoint, package agent info into roomConfig
        var participantToken = customTokenGenerationFunction(
            options.roomName, 
            options.participantName, 
            options.agentName, // Available when using Session APIs with agentName
            /* etc */
        )
        return@fromCustom Result.success(TokenSourceResponse(LIVEKIT_URL, participantToken))
    }
}
val session = rememberSession(
    tokenSource = tokenSource
)

Column {
    SessionScope(session = session) { session ->
        val coroutineScope = rememberCoroutineScope()
        var shouldConnect by remember { mutableStateOf(false) }

        LaunchedEffect(shouldConnect) {
            if (shouldConnect) {

                val result = session.start()

                // Handle if the session fails to connect.
                if (result.isFailure) {
                    Toast.makeText(context, "Error connecting to the session.", Toast.LENGTH_SHORT).show()
                    shouldConnect = false
                }
            } else {
                session.end()
            }
        }
        Button(onClick = { shouldConnect = !shouldConnect }) {
            Text(
                if (shouldConnect) {
                    "Disconnect"
                } else {
                    "Connect"
                }
            )
        }

        // Agent provides state information about the agent participant.
        val agent = rememberAgent()
        Text(agent.agentState.name)

        // SessionMessages handles all transcriptions and chat messages
        val sessionMessages = rememberSessionMessages()

        LazyColumn {
            items(items = sessionMessages.messages) { message ->
                Text(message.message)
            }
        }

        val messageState = rememberTextFieldState()
        TextField(state = messageState)
        Button(onClick = {
            coroutineScope.launch {
                sessionMessages.send(messageState.text.toString())
                messageState.clearText()
            }
        }) {
            Text("Send")
        }
    }
}

```

---

**Flutter**:

```dart
import 'package:livekit_client/livekit_client.dart' as sdk;

final LIVEKIT_URL = "%{wsURL}%";

final tokenSource = sdk.CustomTokenSource((options) async {
  // Run your custom token generation logic, using values in `options` as inputs
  // `options` includes: roomName, participantName, agentName, roomConfig, etc.
  // For agent applications, if calling a standard endpoint, package agent info into roomConfig
  final participantToken = await customTokenGenerationFunction(
    options.roomName, 
    options.participantName, 
    options.agentName, // Available when using Session APIs with agentName
    /* etc */
  );

  return TokenSourceResponse(serverUrl: LIVEKIT_URL, participantToken: participantToken);
});
final session = sdk.Session.fromConfigurableTokenSource(
  tokenSource,
  const TokenRequestOptions()
);

/* ... */

await session.start();

// Use session to further build out your application.

```

---

**React Native**:

```typescript
import { TokenSource } from 'livekit-client';
import { useSession, SessionProvider } from '@livekit/components-react';

const LIVEKIT_URL = "%{wsURL}%";

// Create the TokenSource
// 
// If your TokenSource.custom relies on other dependencies other than `options`, be
// sure to wrap it in a `useMemo` so that the reference stays stable.
const tokenSource = TokenSource.custom(async (options) => {
  // Run your custom token generation logic, using values in `options` as inputs
  // `options` includes: roomName, participantName, agentName, roomConfig, etc.
  // For agent applications, if calling a standard endpoint, package agent info into roomConfig
  const participantToken = await customTokenGenerationFunction(
    options.roomName, 
    options.participantName, 
    options.agentName, // Available when using Session APIs with agentName
    /* etc */
  );

  return { serverUrl: LIVEKIT_URL, participantToken };
});

export const MyPage = () => {
  const session = useSession(tokenSource, { roomName: "room name to join" });

  // Start the session when the component mounts, and end the session when the component unmounts
  useEffect(() => {
    session.start();
    return () => {
      session.end();
    };
  }, []);

  return (
    <SessionProvider session={session}>
      {/* render the rest of your application here */}
    </SessionProvider>
  )
}

```

## Manual token creation

If you need to create tokens programmatically on the backend (for example, to power a custom `TokenSource` or to use `Room.connect` directly), use the server SDK methods below.

> ℹ️ **Using Session APIs**
> 
> If you're building a 1:1 agent application using Session APIs, token creation is handled automatically by your `TokenSource`. Use the examples below only if you're implementing your own token endpoint or using manual token generation.

**LiveKit CLI**:

```shell
lk token create \
  --api-key <KEY> \
  --api-secret <SECRET> \
  --identity <NAME> \
  --room <ROOM_NAME> \
  --join \
  --valid-for 1h

```

---

**Node.js**:

```typescript
import { AccessToken, VideoGrant } from 'livekit-server-sdk';
import { RoomAgentDispatch, RoomConfiguration } from '@livekit/protocol';

const roomName = 'name-of-room';
const participantName = 'user-name';
const agentName = 'my-agent'; // Optional: for agent dispatch

const at = new AccessToken('api-key', 'secret-key', {
  identity: participantName,
});

const videoGrant: VideoGrant = {
  room: roomName,
  roomJoin: true,
  canPublish: true,
  canSubscribe: true,
};

at.addGrant(videoGrant);

// Optional: Add agent dispatch for 1:1 agent applications
if (agentName) {
  at.roomConfig = new RoomConfiguration({
    agents: [
      new RoomAgentDispatch({
        agentName,
      })
    ]
  });
}

const token = await at.toJwt();
console.log('access token', token);

```

---

**Go**:

```go
import (
  "time"

  "github.com/livekit/protocol/auth"
  "github.com/livekit/protocol/livekit"
)

func getJoinToken(apiKey, apiSecret, room, identity string, agentName string) (string, error) {
  canPublish := true
  canSubscribe := true

  at := auth.NewAccessToken(apiKey, apiSecret)
  grant := &auth.VideoGrant{
    RoomJoin:     true,
    Room:         room,
    CanPublish:   &canPublish,
    CanSubscribe: &canSubscribe,
  }
  at.SetVideoGrant(grant).
     SetIdentity(identity).
     SetValidFor(time.Hour)

  // Optional: Add agent dispatch for 1:1 agent applications
  if agentName != "" {
    roomConfig := &livekit.RoomConfiguration{
      Agents: []*livekit.RoomAgentDispatch{{
        AgentName: agentName,
      }},
    }
    at.SetRoomConfig(roomConfig)
  }

  return at.ToJWT()
}

```

---

**Ruby**:

```ruby
require 'livekit'

token = LiveKit::AccessToken.new(api_key: 'yourkey', api_secret: 'yoursecret')
token.identity = 'participant-identity'
token.name = 'participant-name'
token.video_grant=(LiveKit::VideoGrant.from_hash(roomJoin: true,
                                                 room: 'room-name'))

# Optional: Add agent dispatch for 1:1 agent applications
agent_name = 'my-agent'
if agent_name
  token.room_config = LiveKit::Proto::RoomConfiguration.new(
    agents: [
      LiveKit::Proto::RoomAgentDispatch.new(
        agent_name: agent_name
      )
    ]
  )
end

puts token.to_jwt

```

---

**Python**:

```python
from livekit import api
import os

agent_name = "my-agent"  # Optional: for agent dispatch

token = api.AccessToken(os.environ['LIVEKIT_API_KEY'], os.environ['LIVEKIT_API_SECRET']) \
    .with_identity("identity") \
    .with_name("name") \
    .with_grants(api.VideoGrants(
        room_join=True,
        room="my-room",
    ))

# Optional: Add agent dispatch for 1:1 agent applications
if agent_name:
    token = token.with_room_config(
        api.RoomConfiguration(
            agents=[
                api.RoomAgentDispatch(
                    agent_name=agent_name
                )
            ],
        ),
    )

token = token.to_jwt()

```

---

**Rust**:

```rust
use livekit_api::access_token;
use std::env;

fn create_token(agent_name: Option<String>) -> Result<String, access_token::AccessTokenError> {
   let api_key = env::var("LIVEKIT_API_KEY").expect("LIVEKIT_API_KEY is not set");
   let api_secret = env::var("LIVEKIT_API_SECRET").expect("LIVEKIT_API_SECRET is not set");

   let mut token = access_token::AccessToken::with_api_key(&api_key, &api_secret)
      .with_identity("identity")
      .with_name("name")
      .with_grants(access_token::VideoGrants {
         room_join: true,
         room: "my-room".to_string(),
         ..Default::default()
      });

   // Optional: Add agent dispatch for 1:1 agent applications
   if let Some(agent_name) = agent_name {
       token = token.with_room_config(livekit::RoomConfiguration {
           agents: vec![livekit::AgentDispatch {
               agent_name,
           }],
       });
   }

   token.to_jwt()
}

```

### Agent dispatch in tokens

For 1:1 agent applications, you can include agent dispatch information in your tokens. This tells LiveKit which agent to automatically dispatch when a participant joins the room.

> 💡 **Using Session APIs**
> 
> When using the [Session APIs](https://docs.livekit.io/frontends/build/sessions.md), you can provide the agent name at runtime. The Session API automatically packages agent information into `room_config` and includes it in token requests to your backend. Your token endpoint must accept `room_config` and pass it directly to the access token builder. This is the recommended approach for most applications.

**For manual token generation**: If you're generating tokens manually (not using Session APIs), you must include the agent dispatch information when you create the token. You cannot change the agent name at runtime with this approach unless you communicate it outside of LiveKit's abstractions.

For complete reference on token structure, grants, and permissions, see [Tokens & grants](https://docs.livekit.io/frontends/reference/tokens-grants.md).

---

This document was rendered at 2026-06-07T11:34:13.397Z.
For the latest version of this document, see [https://docs.livekit.io/frontends/build/authentication/custom.md](https://docs.livekit.io/frontends/build/authentication/custom.md).

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