Overview
Each LiveKit participant has two fields for application-specific state:
- Participant.attributes: A string key-value store
- Participant.metadata: A single string that can store any data.
These fields are stored and managed by the LiveKit server, and are automatically synchronized to new participants who join the room later.
Initial values can be set in the participant's access token, ensuring the value is immediately available when the participant connects.
While the metadata field is a single string, the attributes field is a key-value store. This allows fine-grained updates to different parts of the state without affecting or transmitting the values of other keys.
Deleting attributes
To delete an attribute key, set its value to an empty string ('').
Update frequency
Attributes and metadata are not suitable for high-frequency updates (more than once every few seconds) due to synchronization overhead on the server. If you need to send updates more frequently, consider using data packets instead.
Size limits
Metadata is limited to 512 KiB. Attributes are limited to 64 KiB combined across all keys and values. For payloads that exceed these limits, see Reference large data by ID.
Usage from LiveKit SDKs
The LiveKit SDKs receive events on attributes and metadata changes for both the local participant and any remote participants in the room. See Handling events for more information.
Participants must have the canUpdateOwnMetadata permission in their access token to update their own attributes or metadata.
// receiving changesroom.on(RoomEvent.ParticipantAttributesChanged,(changed: Record<string, string>, participant: Participant) => {console.log('participant attributes changed',changed,'all attributes',participant.attributes,);},);room.on(RoomEvent.ParticipantMetadataChanged,(oldMetadata: string | undefined, participant: Participant) => {console.log('metadata changed from', oldMetadata, participant.metadata);},);// updating local participantroom.localParticipant.setAttributes({myKey: 'myValue',myOtherKey: 'otherValue',});room.localParticipant.setMetadata(JSON.stringify({some: 'values',}),);
Our React component library provides a few convenience hooks to work with participant attributes.
function MyComponent() {// getting all attributes of a participantconst { attributes } = useParticipantAttributes({ participant: participant });// getting a single attribute of a participantconst myKey = useParticipantAttribute('myKey', { participant: participant });// setting attributes and metadata would be the same as in JS}
extension MyClass: RoomDelegate {// receiving participant attributes changesfunc room(_ room: Room, participant: Participant, didUpdateAttributes changedAttributes: [String: String]) {}// receiving room metadata changesfunc room(_ room: Room, didUpdateMetadata newMetadata: String?) {}}// updating participant attributes (from async function)try await room.localParticipant.set(attributes: ["mykey" : "myvalue"])// updating participant metadatatry await room.localParticipant.set(metadata: "some metadata")
room.events.collect { event ->when (event) {is RoomEvent.ParticipantAttributesChanged -> {}is RoomEvent.ParticipantMetadataChanged -> {}}}localParticipant.updateAttributes(mapOf("myKey" to "myvalue"))localParticipant.updateMetadata("mymetadata")
final listener = room.createListener();listener..on<ParticipantAttributesChanged>((event) {})..on<ParticipantMetadataUpdatedEvent>((event) {});room.localParticipant?.setAttributes({'myKey': 'myValue',});room.localParticipant?.setMetadata('myMetadata');
@room.on("participant_attributes_changed")def on_attributes_changed(changed_attributes: dict[str, str], participant: rtc.Participant):logging.info("participant attributes changed: %s %s",participant.attributes,changed_attributes,)@room.on("participant_metadata_changed")def on_metadata_changed(participant: rtc.Participant, old_metadata: str, new_metadata: str):logging.info("metadata changed from %s to %s",old_metadata,participant.metadata,)# setting attributes & metadata are async functionsasync def myfunc():await room.local_participant.set_attributes({"foo": "bar"})await room.local_participant.set_metadata("some metadata")asyncio.run(myfunc())
// Receiving changeswhile let Some(event) = room_events.recv().await {match event {RoomEvent::ParticipantAttributesChanged { changed_attributes, .. } => {log::info!("participant attributes changed: {:?}", changed_attributes);}RoomEvent::ParticipantMetadataChanged { old_metadata, metadata, .. } => {log::info!("metadata changed from {} to {}", old_metadata, metadata);}_ => {}}}// Updating local participantroom.local_participant().set_attributes(HashMap::from([("myKey".into(), "myValue".into()),("myOtherKey".into(), "otherValue".into()),])).await?;room.local_participant().set_metadata("some metadata".to_string()).await?;
class MetadataDelegate : public livekit::RoomDelegate {public:void onParticipantAttributesChanged(livekit::Room&,const livekit::ParticipantAttributesChangedEvent& event) override {if (event.participant == nullptr) {return;}std::cout << "Attributes changed for " << event.participant->identity() << "\n";}void onParticipantMetadataChanged(livekit::Room&, const livekit::ParticipantMetadataChangedEvent& event) override {if (event.participant == nullptr) {return;}std::cout << "Metadata changed from " << event.old_metadata << " to " << event.new_metadata << "\n";}};MetadataDelegate delegate;room->setDelegate(&delegate);if (auto lp = room->localParticipant().lock()) {lp->setAttributes({{"myKey", "myValue"},{"myOtherKey", "otherValue"},});lp->setMetadata(R"({"some":"values"})");}else{std::cerr << "Failed to get local participant\n";return;}
Room _room;IEnumerator ConnectToRoom(){// ..._room = new Room();_room.ParticipantAttributesChanged += OnParticipantAttributesChanged;_room.ParticipantMetadataChanged += OnParticipantMetadataChanged;var connect = _room.Connect(serverUrl, token, new RoomOptions());// ...}void OnParticipantAttributesChanged(Participant participant){if (participant == _room.LocalParticipant) return;foreach (var attribute in participant.Attributes){Debug.Log($"{participant.Identity} {attribute.Key}: {attribute.Value}");}}void OnParticipantMetadataChanged(Participant participant){if (participant == _room.LocalParticipant) return;Debug.Log($"{participant.Identity} {participant.Metadata}");}// Updates on local participantIEnumerator UpdateLocalParticipantAttributes(Room room){var instruction = room.LocalParticipant.SetAttributes(new Dictionary<string, string>{{"myKey", "myValue"}});yield return instruction;}IEnumerator UpdateLocalParticipantMetadata(Room room){var instruction = room.LocalParticipant.SetMetadata("myMetadata");yield return instruction;}
Usage from server APIs
From the server side, you can update attributes or metadata of any participant in the room using the UpdateParticipant API on the room service. The api object in the following examples is a LiveKitAPI instance (lkapi in Python and Ruby).
await api.room.updateParticipant('roomName', 'participantIdentity', {metadata: 'new metadata',attributes: {myKey: 'myValue',},});
from livekit.api import UpdateParticipantRequestawait lkapi.room.update_participant(UpdateParticipantRequest(room="roomName",identity="participantIdentity",metadata="new metadata",attributes={"myKey": "myValue",},))
lkapi.room.update_participant(room: 'roomName',identity: 'participantIdentity',metadata: 'new metadata',attributes: { 'myKey' => 'myValue' },)
_, err := api.Room().UpdateParticipant(context.Background(), &livekit.UpdateParticipantRequest{Room: "roomName",Identity: "participantIdentity",Metadata: "new metadata",Attributes: map[string]string{"myKey": "myValue",},})
api.room.updateParticipant(roomName = "roomName",identity = "participantIdentity",metadata = "new metadata",attributes = mapOf("myKey" to "myValue"),).execute()
use std::collections::HashMap;use livekit_api::services::room::UpdateParticipantOptions;api.room().update_participant("roomName", "participantIdentity", UpdateParticipantOptions {metadata: "new metadata".to_string(),attributes: HashMap::from([("myKey".to_string(), "myValue".to_string())]),..Default::default()}).await?;