using livekit-server-sdk JS, how do I pass metadat...
# ask-ai
c
using livekit-server-sdk JS, how do I pass metadata alongside the token request
t
Thanks for reaching out, Guillaume. I'm an AI assistant and I'm here to help. Give me a moment while I research this topic.
I'm glad you're looking to pass metadata alongside a token request using the
livekit-server-sdk
in JavaScript! To do this smoothly, you can include the
metadata
field in the
AccessTokenOptions
object when creating your
AccessToken
. This metadata will be tied to the participant and will be accessible when they join the room. Here’s a simple example to guide you:
Copy code
const at = new AccessToken(apiKey, apiSecret, {
  identity: "user123",
  metadata: JSON.stringify({ displayName: "John Doe", role: "admin" }),
});

at.addGrant({
  room: "my-room",
  roomJoin: true,
  canPublish: true,
  canSubscribe: true,
});

const token = await at.toJwt();
If you prefer, you can also set the metadata after creating the
AccessToken
instance by using the
.metadata
setter like this:
Copy code
const at = new AccessToken(apiKey, apiSecret, { identity: "user123" });
at.metadata = JSON.stringify({ customData: "value" }); // Set metadata separately
at.addGrant({ room: "my-room", roomJoin: true });
const token = await at.toJwt();
Just a quick tip: make sure the
metadata
field is a string, and using JSON is a great way to keep your data structured and easy to manage. Sources: Dispatching agents | LiveKit Docs | src/pages/api/token.ts | Agent dispatch | LiveKit Docs | web/src/app/api/token/route.ts | web/src/app/api/token/route.ts
c
this is how I’m setting the metadata
Copy code
export async function POST(req: Request) {
  try {
    if (LIVEKIT_URL === undefined) {
      throw new Error('LIVEKIT_URL is not defined');
    }
    if (API_KEY === undefined) {
      throw new Error('LIVEKIT_API_KEY is not defined');
    }
    if (API_SECRET === undefined) {
      throw new Error('LIVEKIT_API_SECRET is not defined');
    }

    // Parse agent configuration from request body
    const body = await req.json();
    const student: Student = body?.student;

    // Generate participant token
    const participantName = 'user';
    const participantIdentity = `voice_assistant_user_${Math.floor(Math.random() * 10_000)}`;
    const roomName = `voice_assistant_room_${Math.floor(Math.random() * 10_000)}`;

    const participantToken = await createParticipantToken(
      { identity: participantIdentity, name: participantName, metadata: JSON.stringify(student) },
      roomName,
    );

    // Return connection details
    const data: ConnectionDetails = {
      serverUrl: LIVEKIT_URL,
      roomName,
      participantToken: participantToken,
      participantName,
    };
    const headers = new Headers({
      'Cache-Control': 'no-store',
    });
    return NextResponse.json(data, { headers });
  } catch (error) {
    if (error instanceof Error) {
      console.error(error);
      return new NextResponse(error.message, { status: 500 });
    }
  }
}

function createParticipantToken(
  userInfo: AccessTokenOptions,
  roomName: string,
): Promise<string> {
  const at = new AccessToken(API_KEY, API_SECRET, {
    ...userInfo,
    ttl: '15m',
  });
  const grant: VideoGrant = {
    room: roomName,
    roomJoin: true,
    canPublish: true,
    canPublishData: true,
    canSubscribe: true,
  };
  at.addGrant(grant);

  return at.toJwt();
despite that, I’m still getting an empty metadata field when receiving the request on the agent side
t
Let's walk through some helpful steps to figure out why the metadata field might be coming up empty when using `createParticipantToken`: 1. Make Sure Metadata is Properly Passed: Double-check that the
metadata
field is clearly included in the
AccessTokenOptions
object. Here's a quick example:
Copy code
const participantToken = await createParticipantToken(
  {
    identity: "user-identity",
    name: "User Name",
    metadata: JSON.stringify(student),
  },
  roomName,
);
1. Validate the Token Contents: Try decoding the JWT token you generated to see if the
metadata
field is actually there. You can use online JWT decoders or libraries like
jose
to peek inside the token. 2. Check How You Access Metadata on the Client Side: Make sure you're retrieving the metadata correctly. For instance:
Copy code
console.log(room.localParticipant.metadata);
Or you can listen for updates like this:
Copy code
room.localParticipant.on('metadataUpdated', () => {
  console.log(room.localParticipant.metadata);
});
1. Verify Your API Credentials: It's important to confirm that
LIVEKIT_API_KEY
and
LIVEKIT_API_SECRET
are set correctly and passed properly to the
AccessToken
constructor. 2. Watch Out for Overwritten Metadata: Check that the metadata isn’t accidentally being overwritten with an empty string or undefined during token creation. 3. Consider Optional Permissions: If you want to update metadata after joining, remember to include
canUpdateOwnMetadata: true
in the
VideoGrant
. If you’re still running into trouble, take a close look at the generated token to see whether the issue might be with how the token is created or how it’s handled on the client side. I’m here to help if you need more guidance! Sources: src/pages/api/token.ts | src/room/Room.ts | packages/livekit-server-sdk/src/AccessToken.ts | app/api/connection-details/route.ts | Authentication | LiveKit Docs
c
I decoded the token and it does contain the metadata. I don’t understand why the metadata is empty on the agent side
t
Thanks for sharing that the metadata is present in the token! To help us get to the bottom of why the agent might be seeing it as empty, could you please share a bit more about how the agent is accessing or processing the metadata? For example: • Are you using a specific method or API to retrieve the metadata on the agent side? • Is the agent set up to handle metadata, and have you noticed any logs or errors that might give us a clue? Understanding these details will really help us figure out what’s going on and find the best way to assist you.
c
Copy code
export default defineAgent({
    prewarm: async (proc: JobProcess) => {
      proc.userData.vad = await silero.VAD.load();
    },
    entry: async (ctx: JobContext) => {
      const vad = ctx.proc.userData.vad! as silero.VAD;
      
      const metadata = ctx.job.metadata ? JSON.parse(ctx.job.metadata) : {};
      console.log("metadata", metadata);
This is how I access the metadata on the agent side
Copy code
[13:31:23.421] INFO (6367): received job request
    version: "0.1.0"
    job: {
      "id": "AJ_HEqSTwkHTCCh",
      "type": "JT_ROOM",
      "room": {
        "sid": "RM_fJfz7J2BPU3U",
        "name": "voice_assistant_room_7502",
        "emptyTimeout": 300,
        "maxParticipants": 0,
        "creationTime": "1758717083",
        "turnPassword": "",
        "enabledCodecs": [
          {
            "mime": "video/H264",
            "fmtpLine": ""
          },
          {
            "mime": "video/VP8",
            "fmtpLine": ""
          },
          {
            "mime": "video/VP9",
            "fmtpLine": ""
          },
          {
            "mime": "video/AV1",
            "fmtpLine": ""
          },
          {
            "mime": "audio/red",
            "fmtpLine": ""
          },
          {
            "mime": "audio/opus",
            "fmtpLine": ""
          }
        ],
        "metadata": "",
        "numParticipants": 0,
        "activeRecording": false,
        "numPublishers": 0,
        "version": {
          "unixMicro": "1758717083334102",
          "ticks": 0
        },
        "departureTimeout": 20,
        "creationTimeMs": "1758717083326"
      },
      "namespace": "",
      "metadata": "",
      "agentName": "",
      "state": {
        "status": "JS_RUNNING",
        "error": "",
        "startedAt": "1758717083459674915",
        "endedAt": "0",
        "updatedAt": "1758717083459674915",
        "participantIdentity": "",
        "workerId": "AW_DLBKHVR8YfBV",
        "agentId": "A_ZJ2ewEkShrfu"
      },
      "dispatchId": ""
    }
    resuming: false
    agentName: ""
This is the log of the request the agent receives
t
It looks like you’re accessing the metadata in the agent the right way! That’s a great start. Let’s go over a few additional things together to make sure everything’s set up correctly: 1. Make Sure Metadata is Properly Passed During Dispatch: When you dispatch the agent, the metadata needs to be explicitly included. Here’s an example to guide you:
Copy code
lk dispatch create \
--agent-name test-agent \
--room my-room \
--metadata '{"user_id": "12345"}'
1. Check the Metadata in the Job Context: The
ctx.job.metadata
field should have the metadata string. If it’s coming up empty, it’s worth double-checking that the metadata was indeed sent during dispatch. 2. Look Out for JSON Parsing Issues: The metadata string needs to be valid JSON. If there’s a formatting problem,
JSON.parse
will throw an error, so it’s good to verify this. 3. Use Logs to Inspect Metadata: Adding some logging can help confirm what’s inside
ctx.job.metadata
before parsing. For example:
Copy code
console.log("Raw metadata:", ctx.job.metadata);
If you still find the metadata empty after these checks, it might be related to how the agent is dispatched or how the metadata is passed along. Please feel free to reach out if you’d like me to help you troubleshoot further—I’m here to support you! Sources: Job lifecycle | LiveKit Docs | Agent dispatch | LiveKit Docs | External data and RAG | LiveKit Docs | Making calls using SIP | LiveKit Docs | Dispatching agents | LiveKit Docs
It looks like the
job.metadata
field isn’t being populated during the agent dispatch process, and I’m here to help you work through this! Let’s go over some important points to get this sorted out: 1. Keep Participant Metadata and Job Metadata Separate: The metadata you set in the participant token is different from the
job.metadata
field. To make sure
job.metadata
is filled, it needs to be explicitly included during the agent dispatch. 2. Double-Check Your Dispatch Method: Make sure the agent is explicitly dispatched with the
agent_name
specified in the dispatch configuration. If dispatch isn’t explicit,
job.metadata
might not be passed along. 3. Include Metadata When Dispatching: When you dispatch the agent, be sure to add the metadata in the
RoomAgentDispatch
configuration or the API request. Here’s a quick example:
Copy code
const at = new AccessToken(apiKey, apiSecret, { identity: "user123" });
at.roomConfig = new RoomConfiguration({
  agents: [
    new RoomAgentDispatch({
      agentName: "test-agent",
      metadata: JSON.stringify({ user_id: "12345" }),
    }),
  ],
});
1. Check the Job Context: It’s helpful to log the full job context in the agent’s
entrypoint
so you can see exactly what data is coming through:
Copy code
export default defineAgent({
  entry: async (ctx: JobContext) => {
    console.log("Full job context:", ctx.job);
  },
});
If you’re still running into trouble, please make sure the dispatch request includes the
metadata
field and that the agent isn’t relying on automatic dispatch, which might skip job-specific metadata. I’m here if you want to walk through this together! Sources: Dispatching agents | LiveKit Docs | Participant attributes and metadata | LiveKit Docs | Agent dispatch | LiveKit Docs | Authentication | LiveKit Docs | Job lifecycle | LiveKit Docs
c
how do I access the metadata I set in the localparticipant token in the agent code?
t
I don't have the answer you're looking for. You could also try asking your question: • in one of the other Slack channels or • to https://deepwiki.com/livekit/livekit_composite which is trained on all LiveKit source code If you find the answer, please post it here to help others!