[{"data":1,"prerenderedAt":1326},["ShallowReactive",2],{"docs:/docs/concepts/rooms":3},{"id":4,"title":5,"body":6,"description":1319,"extension":1320,"meta":1321,"navigation":64,"path":1322,"seo":1323,"stem":1324,"__hash__":1325},"docs/docs/concepts/rooms.md","Rooms",{"type":7,"value":8,"toc":1307},"minimark",[9,13,17,22,25,441,445,450,465,469,472,503,508,523,768,772,781,1016,1020,1027,1156,1159,1163,1174,1177,1199,1216,1220,1231,1235,1256,1260,1286,1290,1303],[10,11,5],"h1",{"id":12},"rooms",[14,15,16],"p",{},"Rooms provide logical namespaces for organizing topics within your application.",[18,19,21],"h2",{"id":20},"what-are-rooms","What are Rooms?",[14,23,24],{},"Rooms are containers that group related topics together. They help organize your messaging structure and provide isolation between different contexts (e.g., different game lobbies, chat channels, or user spaces).",[26,27,28,225,299],"code-tabs",{},[29,30,36],"pre",{"className":31,"code":32,"filename":33,"language":34,"meta":35,"style":35},"language-typescript shiki shiki-themes github-light github-dark","import { NoLag } from '@nolag/js-sdk'\n\nconst client = NoLag('your_access_token')\nawait client.connect()\n\n// Set up app and room\nconst room = client.setApp('my-app').setRoom('game-lobby')\n\n// Subscribe to topics within this room\nroom.subscribe('chat')\nroom.subscribe('player-updates')\n\n// Messages are scoped to this room\nroom.emit('chat', { text: 'Hello lobby!' })\n","TypeScript","typescript","",[37,38,39,59,66,92,107,112,119,152,157,163,179,193,198,204],"code",{"__ignoreMap":35},[40,41,44,48,52,55],"span",{"class":42,"line":43},"line",1,[40,45,47],{"class":46},"szBVR","import",[40,49,51],{"class":50},"sVt8B"," { NoLag } ",[40,53,54],{"class":46},"from",[40,56,58],{"class":57},"sZZnC"," '@nolag/js-sdk'\n",[40,60,62],{"class":42,"line":61},2,[40,63,65],{"emptyLinePlaceholder":64},true,"\n",[40,67,69,72,76,79,83,86,89],{"class":42,"line":68},3,[40,70,71],{"class":46},"const",[40,73,75],{"class":74},"sj4cs"," client",[40,77,78],{"class":46}," =",[40,80,82],{"class":81},"sScJk"," NoLag",[40,84,85],{"class":50},"(",[40,87,88],{"class":57},"'your_access_token'",[40,90,91],{"class":50},")\n",[40,93,95,98,101,104],{"class":42,"line":94},4,[40,96,97],{"class":46},"await",[40,99,100],{"class":50}," client.",[40,102,103],{"class":81},"connect",[40,105,106],{"class":50},"()\n",[40,108,110],{"class":42,"line":109},5,[40,111,65],{"emptyLinePlaceholder":64},[40,113,115],{"class":42,"line":114},6,[40,116,118],{"class":117},"sJ8bj","// Set up app and room\n",[40,120,122,124,127,129,131,134,136,139,142,145,147,150],{"class":42,"line":121},7,[40,123,71],{"class":46},[40,125,126],{"class":74}," room",[40,128,78],{"class":46},[40,130,100],{"class":50},[40,132,133],{"class":81},"setApp",[40,135,85],{"class":50},[40,137,138],{"class":57},"'my-app'",[40,140,141],{"class":50},").",[40,143,144],{"class":81},"setRoom",[40,146,85],{"class":50},[40,148,149],{"class":57},"'game-lobby'",[40,151,91],{"class":50},[40,153,155],{"class":42,"line":154},8,[40,156,65],{"emptyLinePlaceholder":64},[40,158,160],{"class":42,"line":159},9,[40,161,162],{"class":117},"// Subscribe to topics within this room\n",[40,164,166,169,172,174,177],{"class":42,"line":165},10,[40,167,168],{"class":50},"room.",[40,170,171],{"class":81},"subscribe",[40,173,85],{"class":50},[40,175,176],{"class":57},"'chat'",[40,178,91],{"class":50},[40,180,182,184,186,188,191],{"class":42,"line":181},11,[40,183,168],{"class":50},[40,185,171],{"class":81},[40,187,85],{"class":50},[40,189,190],{"class":57},"'player-updates'",[40,192,91],{"class":50},[40,194,196],{"class":42,"line":195},12,[40,197,65],{"emptyLinePlaceholder":64},[40,199,201],{"class":42,"line":200},13,[40,202,203],{"class":117},"// Messages are scoped to this room\n",[40,205,207,209,212,214,216,219,222],{"class":42,"line":206},14,[40,208,168],{"class":50},[40,210,211],{"class":81},"emit",[40,213,85],{"class":50},[40,215,176],{"class":57},[40,217,218],{"class":50},", { text: ",[40,220,221],{"class":57},"'Hello lobby!'",[40,223,224],{"class":50}," })\n",[29,226,231],{"className":227,"code":228,"filename":229,"language":230,"meta":35,"style":35},"language-python shiki shiki-themes github-light github-dark","from nolag import NoLag\n\nclient = NoLag('your_access_token')\nawait client.connect()\n\n# Set up app and room\nroom = client.set_app('my-app').set_room('game-lobby')\n\n# Subscribe to topics within this room\nawait room.subscribe('chat')\nawait room.subscribe('player-updates')\n\n# Messages are scoped to this room\nawait room.emit('chat', {'text': 'Hello lobby!'})\n","Python","python",[37,232,233,238,242,247,252,256,261,266,270,275,280,285,289,294],{"__ignoreMap":35},[40,234,235],{"class":42,"line":43},[40,236,237],{},"from nolag import NoLag\n",[40,239,240],{"class":42,"line":61},[40,241,65],{"emptyLinePlaceholder":64},[40,243,244],{"class":42,"line":68},[40,245,246],{},"client = NoLag('your_access_token')\n",[40,248,249],{"class":42,"line":94},[40,250,251],{},"await client.connect()\n",[40,253,254],{"class":42,"line":109},[40,255,65],{"emptyLinePlaceholder":64},[40,257,258],{"class":42,"line":114},[40,259,260],{},"# Set up app and room\n",[40,262,263],{"class":42,"line":121},[40,264,265],{},"room = client.set_app('my-app').set_room('game-lobby')\n",[40,267,268],{"class":42,"line":154},[40,269,65],{"emptyLinePlaceholder":64},[40,271,272],{"class":42,"line":159},[40,273,274],{},"# Subscribe to topics within this room\n",[40,276,277],{"class":42,"line":165},[40,278,279],{},"await room.subscribe('chat')\n",[40,281,282],{"class":42,"line":181},[40,283,284],{},"await room.subscribe('player-updates')\n",[40,286,287],{"class":42,"line":195},[40,288,65],{"emptyLinePlaceholder":64},[40,290,291],{"class":42,"line":200},[40,292,293],{},"# Messages are scoped to this room\n",[40,295,296],{"class":42,"line":206},[40,297,298],{},"await room.emit('chat', {'text': 'Hello lobby!'})\n",[29,300,305],{"className":301,"code":302,"filename":303,"language":304,"meta":35,"style":35},"language-go shiki shiki-themes github-light github-dark","package main\n\nimport (\n    \"fmt\"\n\n    nolag \"github.com/NoLagApp/go-sdk\"\n)\n\nfunc main() {\n    client := nolag.New(\"your_access_token\")\n    client.Connect()\n\n    // Set up app and room\n    room := client.SetApp(\"my-app\").SetRoom(\"game-lobby\")\n\n    // Subscribe to topics within this room\n    room.Subscribe(\"chat\", func(data any, meta nolag.MessageMeta) {\n        fmt.Println(\"Chat:\", data)\n    })\n    room.Subscribe(\"player-updates\", func(data any, meta nolag.MessageMeta) {\n        fmt.Println(\"Player update:\", data)\n    })\n\n    // Messages are scoped to this room\n    room.Emit(\"chat\", map[string]string{\"text\": \"Hello lobby!\"})\n}\n","Go","go",[37,306,307,312,316,321,326,330,335,339,343,348,353,358,362,367,372,377,383,389,395,401,407,413,418,423,429,435],{"__ignoreMap":35},[40,308,309],{"class":42,"line":43},[40,310,311],{},"package main\n",[40,313,314],{"class":42,"line":61},[40,315,65],{"emptyLinePlaceholder":64},[40,317,318],{"class":42,"line":68},[40,319,320],{},"import (\n",[40,322,323],{"class":42,"line":94},[40,324,325],{},"    \"fmt\"\n",[40,327,328],{"class":42,"line":109},[40,329,65],{"emptyLinePlaceholder":64},[40,331,332],{"class":42,"line":114},[40,333,334],{},"    nolag \"github.com/NoLagApp/go-sdk\"\n",[40,336,337],{"class":42,"line":121},[40,338,91],{},[40,340,341],{"class":42,"line":154},[40,342,65],{"emptyLinePlaceholder":64},[40,344,345],{"class":42,"line":159},[40,346,347],{},"func main() {\n",[40,349,350],{"class":42,"line":165},[40,351,352],{},"    client := nolag.New(\"your_access_token\")\n",[40,354,355],{"class":42,"line":181},[40,356,357],{},"    client.Connect()\n",[40,359,360],{"class":42,"line":195},[40,361,65],{"emptyLinePlaceholder":64},[40,363,364],{"class":42,"line":200},[40,365,366],{},"    // Set up app and room\n",[40,368,369],{"class":42,"line":206},[40,370,371],{},"    room := client.SetApp(\"my-app\").SetRoom(\"game-lobby\")\n",[40,373,375],{"class":42,"line":374},15,[40,376,65],{"emptyLinePlaceholder":64},[40,378,380],{"class":42,"line":379},16,[40,381,382],{},"    // Subscribe to topics within this room\n",[40,384,386],{"class":42,"line":385},17,[40,387,388],{},"    room.Subscribe(\"chat\", func(data any, meta nolag.MessageMeta) {\n",[40,390,392],{"class":42,"line":391},18,[40,393,394],{},"        fmt.Println(\"Chat:\", data)\n",[40,396,398],{"class":42,"line":397},19,[40,399,400],{},"    })\n",[40,402,404],{"class":42,"line":403},20,[40,405,406],{},"    room.Subscribe(\"player-updates\", func(data any, meta nolag.MessageMeta) {\n",[40,408,410],{"class":42,"line":409},21,[40,411,412],{},"        fmt.Println(\"Player update:\", data)\n",[40,414,416],{"class":42,"line":415},22,[40,417,400],{},[40,419,421],{"class":42,"line":420},23,[40,422,65],{"emptyLinePlaceholder":64},[40,424,426],{"class":42,"line":425},24,[40,427,428],{},"    // Messages are scoped to this room\n",[40,430,432],{"class":42,"line":431},25,[40,433,434],{},"    room.Emit(\"chat\", map[string]string{\"text\": \"Hello lobby!\"})\n",[40,436,438],{"class":42,"line":437},26,[40,439,440],{},"}\n",[18,442,444],{"id":443},"room-types","Room Types",[446,447,449],"h3",{"id":448},"static-rooms","Static Rooms",[14,451,452,453,456,457,460,461,464],{},"Seeded from the app's blueprint when the app is created, and manageable in the dashboard. These rooms exist permanently (they can be disabled but not deleted) and are ideal for fixed channels like ",[37,454,455],{},"general",", ",[37,458,459],{},"announcements",", or ",[37,462,463],{},"support",".",[446,466,468],{"id":467},"dynamic-rooms","Dynamic Rooms",[14,470,471],{},"Created programmatically via the REST API for user-specific or session-specific namespaces. Dynamic rooms must be created server-side before clients can use them.",[473,474,476],"callout",{"type":475},"warning",[14,477,478,482,483,486,487,490,491,494,495,498,499,502],{},[479,480,481],"strong",{},"Rooms never exist implicitly."," The broker does not create a room the first time someone names it. A subscribe or publish to a room it does not know is refused with ",[37,484,485],{},"unknown_topic"," (42940) on the client's ",[37,488,489],{},"error"," event, and nothing is delivered. Create the room through the REST API first, then connect. For per-entity rooms created at runtime, set ",[37,492,493],{},"config.autoProvisionRooms: true"," on the app and call ",[37,496,497],{},"POST /apps/{appId}/rooms/ensure"," (",[37,500,501],{},"api.rooms.ensure"," in the JavaScript SDK), which is an idempotent create-if-missing capped per app.",[504,505,507],"h4",{"id":506},"step-1-create-room-via-api","Step 1: Create Room via API",[14,509,510,511,514,515,518,519,522],{},"Room slugs are stored exactly as given (or derived from ",[37,512,513],{},"name"," by lowercasing and replacing runs of other characters with ",[37,516,517],{},"-","). Pass a ",[37,520,521],{},"slug"," explicitly and use the value from the response when you connect.",[26,524,525,651,708],{},[29,526,528],{"className":31,"code":527,"filename":33,"language":34,"meta":35,"style":35},"import { NoLagApi } from '@nolag/js-sdk'\n\nconst api = new NoLagApi('your_api_key')\n\n// Create a dynamic room - inherits topics from app config\nconst room = await api.rooms.create(appId, {\n  name: `Game ${gameId}`,\n  slug: `game-${gameId}`\n})\n\nconsole.log('Created room:', room.roomId, room.slug)\n",[37,529,530,541,545,567,571,576,596,613,626,631,635],{"__ignoreMap":35},[40,531,532,534,537,539],{"class":42,"line":43},[40,533,47],{"class":46},[40,535,536],{"class":50}," { NoLagApi } ",[40,538,54],{"class":46},[40,540,58],{"class":57},[40,542,543],{"class":42,"line":61},[40,544,65],{"emptyLinePlaceholder":64},[40,546,547,549,552,554,557,560,562,565],{"class":42,"line":68},[40,548,71],{"class":46},[40,550,551],{"class":74}," api",[40,553,78],{"class":46},[40,555,556],{"class":46}," new",[40,558,559],{"class":81}," NoLagApi",[40,561,85],{"class":50},[40,563,564],{"class":57},"'your_api_key'",[40,566,91],{"class":50},[40,568,569],{"class":42,"line":94},[40,570,65],{"emptyLinePlaceholder":64},[40,572,573],{"class":42,"line":109},[40,574,575],{"class":117},"// Create a dynamic room - inherits topics from app config\n",[40,577,578,580,582,584,587,590,593],{"class":42,"line":114},[40,579,71],{"class":46},[40,581,126],{"class":74},[40,583,78],{"class":46},[40,585,586],{"class":46}," await",[40,588,589],{"class":50}," api.rooms.",[40,591,592],{"class":81},"create",[40,594,595],{"class":50},"(appId, {\n",[40,597,598,601,604,607,610],{"class":42,"line":121},[40,599,600],{"class":50},"  name: ",[40,602,603],{"class":57},"`Game ${",[40,605,606],{"class":50},"gameId",[40,608,609],{"class":57},"}`",[40,611,612],{"class":50},",\n",[40,614,615,618,621,623],{"class":42,"line":154},[40,616,617],{"class":50},"  slug: ",[40,619,620],{"class":57},"`game-${",[40,622,606],{"class":50},[40,624,625],{"class":57},"}`\n",[40,627,628],{"class":42,"line":159},[40,629,630],{"class":50},"})\n",[40,632,633],{"class":42,"line":165},[40,634,65],{"emptyLinePlaceholder":64},[40,636,637,640,643,645,648],{"class":42,"line":181},[40,638,639],{"class":50},"console.",[40,641,642],{"class":81},"log",[40,644,85],{"class":50},[40,646,647],{"class":57},"'Created room:'",[40,649,650],{"class":50},", room.roomId, room.slug)\n",[29,652,654],{"className":227,"code":653,"filename":229,"language":230,"meta":35,"style":35},"from nolag import NoLagApi, RoomCreate\n\napi = NoLagApi('your_api_key')\n\n# Create a dynamic room - inherits topics from app config\nroom = await api.rooms.create(app_id, RoomCreate(\n    name=f'Game {game_id}',\n    slug=f'game-{game_id}'\n))\n\nprint('Created room:', room.room_id, room.slug)\n",[37,655,656,661,665,670,674,679,684,689,694,699,703],{"__ignoreMap":35},[40,657,658],{"class":42,"line":43},[40,659,660],{},"from nolag import NoLagApi, RoomCreate\n",[40,662,663],{"class":42,"line":61},[40,664,65],{"emptyLinePlaceholder":64},[40,666,667],{"class":42,"line":68},[40,668,669],{},"api = NoLagApi('your_api_key')\n",[40,671,672],{"class":42,"line":94},[40,673,65],{"emptyLinePlaceholder":64},[40,675,676],{"class":42,"line":109},[40,677,678],{},"# Create a dynamic room - inherits topics from app config\n",[40,680,681],{"class":42,"line":114},[40,682,683],{},"room = await api.rooms.create(app_id, RoomCreate(\n",[40,685,686],{"class":42,"line":121},[40,687,688],{},"    name=f'Game {game_id}',\n",[40,690,691],{"class":42,"line":154},[40,692,693],{},"    slug=f'game-{game_id}'\n",[40,695,696],{"class":42,"line":159},[40,697,698],{},"))\n",[40,700,701],{"class":42,"line":165},[40,702,65],{"emptyLinePlaceholder":64},[40,704,705],{"class":42,"line":181},[40,706,707],{},"print('Created room:', room.room_id, room.slug)\n",[29,709,711],{"className":301,"code":710,"filename":303,"language":304,"meta":35,"style":35},"api := nolag.NewAPI(\"your_api_key\")\n\n// Create a dynamic room - inherits topics from app config\nroom, err := api.Rooms.Create(ctx, appID, nolag.RoomCreate{\n    Name: fmt.Sprintf(\"Game %s\", gameID),\n    Slug: fmt.Sprintf(\"game-%s\", gameID),\n})\nif err != nil {\n    log.Fatal(err)\n}\n\nfmt.Println(\"Created room:\", room.RoomID, room.Slug)\n",[37,712,713,718,722,726,731,736,741,745,750,755,759,763],{"__ignoreMap":35},[40,714,715],{"class":42,"line":43},[40,716,717],{},"api := nolag.NewAPI(\"your_api_key\")\n",[40,719,720],{"class":42,"line":61},[40,721,65],{"emptyLinePlaceholder":64},[40,723,724],{"class":42,"line":68},[40,725,575],{},[40,727,728],{"class":42,"line":94},[40,729,730],{},"room, err := api.Rooms.Create(ctx, appID, nolag.RoomCreate{\n",[40,732,733],{"class":42,"line":109},[40,734,735],{},"    Name: fmt.Sprintf(\"Game %s\", gameID),\n",[40,737,738],{"class":42,"line":114},[40,739,740],{},"    Slug: fmt.Sprintf(\"game-%s\", gameID),\n",[40,742,743],{"class":42,"line":121},[40,744,630],{},[40,746,747],{"class":42,"line":154},[40,748,749],{},"if err != nil {\n",[40,751,752],{"class":42,"line":159},[40,753,754],{},"    log.Fatal(err)\n",[40,756,757],{"class":42,"line":165},[40,758,440],{},[40,760,761],{"class":42,"line":181},[40,762,65],{"emptyLinePlaceholder":64},[40,764,765],{"class":42,"line":195},[40,766,767],{},"fmt.Println(\"Created room:\", room.RoomID, room.Slug)\n",[504,769,771],{"id":770},"step-2-grant-access-optional","Step 2: Grant Access (optional)",[14,773,774,775,780],{},"A room with no grants is public to every actor in the project that can reach the app. To make it private, grant the actors that belong in it. The first grant is what closes the room, so grant everything that needs access, including your own backend services, in the same step. All three REST clients expose room grants; see ",[776,777,779],"a",{"href":778},"/docs/concepts/acl","Access Control"," for the full rules.",[26,782,783,886,948],{},[29,784,786],{"className":31,"code":785,"filename":33,"language":34,"meta":35,"style":35},"// Grant one player pub/sub on the room's topics\nconst grant = await api.rooms.grantActor(appId, room.roomId, {\n  actorTokenId: playerActorId,\n  permission: 'pubSub',\n  topics: ['moves', 'chat']\n})\n\n// Inspect and revoke later\nconst grants = await api.rooms.listActors(appId, room.roomId)\nawait api.rooms.revokeActor(appId, room.roomId, grant.roomActorAccessId)\n",[37,787,788,793,812,817,827,842,846,850,855,874],{"__ignoreMap":35},[40,789,790],{"class":42,"line":43},[40,791,792],{"class":117},"// Grant one player pub/sub on the room's topics\n",[40,794,795,797,800,802,804,806,809],{"class":42,"line":61},[40,796,71],{"class":46},[40,798,799],{"class":74}," grant",[40,801,78],{"class":46},[40,803,586],{"class":46},[40,805,589],{"class":50},[40,807,808],{"class":81},"grantActor",[40,810,811],{"class":50},"(appId, room.roomId, {\n",[40,813,814],{"class":42,"line":68},[40,815,816],{"class":50},"  actorTokenId: playerActorId,\n",[40,818,819,822,825],{"class":42,"line":94},[40,820,821],{"class":50},"  permission: ",[40,823,824],{"class":57},"'pubSub'",[40,826,612],{"class":50},[40,828,829,832,835,837,839],{"class":42,"line":109},[40,830,831],{"class":50},"  topics: [",[40,833,834],{"class":57},"'moves'",[40,836,456],{"class":50},[40,838,176],{"class":57},[40,840,841],{"class":50},"]\n",[40,843,844],{"class":42,"line":114},[40,845,630],{"class":50},[40,847,848],{"class":42,"line":121},[40,849,65],{"emptyLinePlaceholder":64},[40,851,852],{"class":42,"line":154},[40,853,854],{"class":117},"// Inspect and revoke later\n",[40,856,857,859,862,864,866,868,871],{"class":42,"line":159},[40,858,71],{"class":46},[40,860,861],{"class":74}," grants",[40,863,78],{"class":46},[40,865,586],{"class":46},[40,867,589],{"class":50},[40,869,870],{"class":81},"listActors",[40,872,873],{"class":50},"(appId, room.roomId)\n",[40,875,876,878,880,883],{"class":42,"line":165},[40,877,97],{"class":46},[40,879,589],{"class":50},[40,881,882],{"class":81},"revokeActor",[40,884,885],{"class":50},"(appId, room.roomId, grant.roomActorAccessId)\n",[29,887,889],{"className":227,"code":888,"filename":229,"language":230,"meta":35,"style":35},"from nolag import RoomActorAccessCreate\n\n# Grant one player pub/sub on the room's topics\ngrant = await api.rooms.grant_actor(app_id, room.room_id, RoomActorAccessCreate(\n    permission='pubSub',\n    actor_token_id=player_actor_id,\n    topics=['moves', 'chat'],\n))\n\n# Inspect and revoke later\ngrants = await api.rooms.list_actors(app_id, room.room_id)\nawait api.rooms.revoke_actor(app_id, room.room_id, grant.room_actor_access_id)\n",[37,890,891,896,900,905,910,915,920,925,929,933,938,943],{"__ignoreMap":35},[40,892,893],{"class":42,"line":43},[40,894,895],{},"from nolag import RoomActorAccessCreate\n",[40,897,898],{"class":42,"line":61},[40,899,65],{"emptyLinePlaceholder":64},[40,901,902],{"class":42,"line":68},[40,903,904],{},"# Grant one player pub/sub on the room's topics\n",[40,906,907],{"class":42,"line":94},[40,908,909],{},"grant = await api.rooms.grant_actor(app_id, room.room_id, RoomActorAccessCreate(\n",[40,911,912],{"class":42,"line":109},[40,913,914],{},"    permission='pubSub',\n",[40,916,917],{"class":42,"line":114},[40,918,919],{},"    actor_token_id=player_actor_id,\n",[40,921,922],{"class":42,"line":121},[40,923,924],{},"    topics=['moves', 'chat'],\n",[40,926,927],{"class":42,"line":154},[40,928,698],{},[40,930,931],{"class":42,"line":159},[40,932,65],{"emptyLinePlaceholder":64},[40,934,935],{"class":42,"line":165},[40,936,937],{},"# Inspect and revoke later\n",[40,939,940],{"class":42,"line":181},[40,941,942],{},"grants = await api.rooms.list_actors(app_id, room.room_id)\n",[40,944,945],{"class":42,"line":195},[40,946,947],{},"await api.rooms.revoke_actor(app_id, room.room_id, grant.room_actor_access_id)\n",[29,949,951],{"className":301,"code":950,"filename":303,"language":304,"meta":35,"style":35},"// Grant one player pub/sub on the room's topics\ngrant, err := api.Rooms.GrantActor(ctx, appID, room.RoomID, nolag.RoomActorAccessCreate{\n    ActorTokenID: playerActorID,\n    Permission:   nolag.PermissionPubSub,\n    Topics:       []string{\"moves\", \"chat\"},\n})\nif err != nil {\n    log.Fatal(err)\n}\n\n// Inspect and revoke later\ngrants, _ := api.Rooms.ListActors(ctx, appID, room.RoomID)\nfmt.Println(len(grants), \"grants\")\n_ = api.Rooms.RevokeActor(ctx, appID, room.RoomID, grant.RoomActorAccessID)\n",[37,952,953,957,962,967,972,977,981,985,989,993,997,1001,1006,1011],{"__ignoreMap":35},[40,954,955],{"class":42,"line":43},[40,956,792],{},[40,958,959],{"class":42,"line":61},[40,960,961],{},"grant, err := api.Rooms.GrantActor(ctx, appID, room.RoomID, nolag.RoomActorAccessCreate{\n",[40,963,964],{"class":42,"line":68},[40,965,966],{},"    ActorTokenID: playerActorID,\n",[40,968,969],{"class":42,"line":94},[40,970,971],{},"    Permission:   nolag.PermissionPubSub,\n",[40,973,974],{"class":42,"line":109},[40,975,976],{},"    Topics:       []string{\"moves\", \"chat\"},\n",[40,978,979],{"class":42,"line":114},[40,980,630],{},[40,982,983],{"class":42,"line":121},[40,984,749],{},[40,986,987],{"class":42,"line":154},[40,988,754],{},[40,990,991],{"class":42,"line":159},[40,992,440],{},[40,994,995],{"class":42,"line":165},[40,996,65],{"emptyLinePlaceholder":64},[40,998,999],{"class":42,"line":181},[40,1000,854],{},[40,1002,1003],{"class":42,"line":195},[40,1004,1005],{},"grants, _ := api.Rooms.ListActors(ctx, appID, room.RoomID)\n",[40,1007,1008],{"class":42,"line":200},[40,1009,1010],{},"fmt.Println(len(grants), \"grants\")\n",[40,1012,1013],{"class":42,"line":206},[40,1014,1015],{},"_ = api.Rooms.RevokeActor(ctx, appID, room.RoomID, grant.RoomActorAccessID)\n",[504,1017,1019],{"id":1018},"step-3-connect-via-websocket","Step 3: Connect via WebSocket",[14,1021,1022,1023,1026],{},"Use the app slug the control plane returned when you created the app (it carries a random suffix, for example ",[37,1024,1025],{},"my-app-a3f9",") and the room slug from Step 1:",[26,1028,1029,1089,1114],{},[29,1030,1032],{"className":31,"code":1031,"filename":33,"language":34,"meta":35,"style":35},"// After room is created via API, connect to it via WebSocket\nconst gameRoom = client.setApp(APP_SLUG).setRoom(room.slug)\ngameRoom.subscribe('moves')\ngameRoom.subscribe('chat')\n",[37,1033,1034,1039,1064,1077],{"__ignoreMap":35},[40,1035,1036],{"class":42,"line":43},[40,1037,1038],{"class":117},"// After room is created via API, connect to it via WebSocket\n",[40,1040,1041,1043,1046,1048,1050,1052,1054,1057,1059,1061],{"class":42,"line":61},[40,1042,71],{"class":46},[40,1044,1045],{"class":74}," gameRoom",[40,1047,78],{"class":46},[40,1049,100],{"class":50},[40,1051,133],{"class":81},[40,1053,85],{"class":50},[40,1055,1056],{"class":74},"APP_SLUG",[40,1058,141],{"class":50},[40,1060,144],{"class":81},[40,1062,1063],{"class":50},"(room.slug)\n",[40,1065,1066,1069,1071,1073,1075],{"class":42,"line":68},[40,1067,1068],{"class":50},"gameRoom.",[40,1070,171],{"class":81},[40,1072,85],{"class":50},[40,1074,834],{"class":57},[40,1076,91],{"class":50},[40,1078,1079,1081,1083,1085,1087],{"class":42,"line":94},[40,1080,1068],{"class":50},[40,1082,171],{"class":81},[40,1084,85],{"class":50},[40,1086,176],{"class":57},[40,1088,91],{"class":50},[29,1090,1092],{"className":227,"code":1091,"filename":229,"language":230,"meta":35,"style":35},"# After room is created via API, connect to it via WebSocket\ngame_room = client.set_app(APP_SLUG).set_room(room.slug)\nawait game_room.subscribe('moves')\nawait game_room.subscribe('chat')\n",[37,1093,1094,1099,1104,1109],{"__ignoreMap":35},[40,1095,1096],{"class":42,"line":43},[40,1097,1098],{},"# After room is created via API, connect to it via WebSocket\n",[40,1100,1101],{"class":42,"line":61},[40,1102,1103],{},"game_room = client.set_app(APP_SLUG).set_room(room.slug)\n",[40,1105,1106],{"class":42,"line":68},[40,1107,1108],{},"await game_room.subscribe('moves')\n",[40,1110,1111],{"class":42,"line":94},[40,1112,1113],{},"await game_room.subscribe('chat')\n",[29,1115,1117],{"className":301,"code":1116,"filename":303,"language":304,"meta":35,"style":35},"// After room is created via API, connect to it via WebSocket\ngameRoom := client.SetApp(appSlug).SetRoom(room.Slug)\ngameRoom.Subscribe(\"moves\", func(data any, meta nolag.MessageMeta) {\n    fmt.Println(\"Move:\", data)\n})\ngameRoom.Subscribe(\"chat\", func(data any, meta nolag.MessageMeta) {\n    fmt.Println(\"Chat:\", data)\n})\n",[37,1118,1119,1123,1128,1133,1138,1142,1147,1152],{"__ignoreMap":35},[40,1120,1121],{"class":42,"line":43},[40,1122,1038],{},[40,1124,1125],{"class":42,"line":61},[40,1126,1127],{},"gameRoom := client.SetApp(appSlug).SetRoom(room.Slug)\n",[40,1129,1130],{"class":42,"line":68},[40,1131,1132],{},"gameRoom.Subscribe(\"moves\", func(data any, meta nolag.MessageMeta) {\n",[40,1134,1135],{"class":42,"line":94},[40,1136,1137],{},"    fmt.Println(\"Move:\", data)\n",[40,1139,1140],{"class":42,"line":109},[40,1141,630],{},[40,1143,1144],{"class":42,"line":114},[40,1145,1146],{},"gameRoom.Subscribe(\"chat\", func(data any, meta nolag.MessageMeta) {\n",[40,1148,1149],{"class":42,"line":121},[40,1150,1151],{},"    fmt.Println(\"Chat:\", data)\n",[40,1153,1154],{"class":42,"line":154},[40,1155,630],{},[14,1157,1158],{},"Actors only see the rooms that existed when they authenticated, plus rooms the broker re-checks with the control plane on a cache miss. If a client connected before the room was created and its subscribe still fails, reconnect it.",[18,1160,1162],{"id":1161},"public-vs-private-rooms","Public vs Private Rooms",[14,1164,1165,1166,1169,1170,1173],{},"By default, rooms are ",[479,1167,1168],{},"public",", meaning any actor with access to the app can connect to them. The moment you attach one or more actors to a room, that room becomes ",[479,1171,1172],{},"private",". Only the attached actors can access it.",[14,1175,1176],{},"This means access control is built into room assignment rather than requiring separate permission rules. A few examples:",[1178,1179,1180,1187,1193],"ul",{},[1181,1182,1183,1186],"li",{},[479,1184,1185],{},"Notifications:"," attach a user to their own room, and only they receive their notifications",[1181,1188,1189,1192],{},[479,1190,1191],{},"Team chat:"," attach team members to a shared room, and outsiders can't join",[1181,1194,1195,1198],{},[479,1196,1197],{},"Device telemetry:"," attach a device and its owner to a room, isolating the data stream",[473,1200,1202],{"type":1201},"info",[14,1203,1204,1207,1208,456,1211,456,1213,1215],{},[479,1205,1206],{},"No actors attached = public."," At least one actor attached = private. There is no toggle; the presence of actor assignments is what makes a room private. Grants are managed in the dashboard, over the REST API, or with the SDK REST clients (",[37,1209,1210],{},"api.rooms.grantActor",[37,1212,870],{},[37,1214,882],{}," and their Python and Go equivalents).",[18,1217,1219],{"id":1218},"topic-inheritance","Topic Inheritance",[14,1221,1222,1223,1226,1227,1230],{},"Every room inherits the topics defined on its parent app. When you create a chat app with ",[37,1224,1225],{},"messages"," and ",[37,1228,1229],{},"_typing"," topics, every room in that app, whether static or dynamic, automatically has both topics available. You configure topics once at the app level and they scale to thousands of rooms.",[18,1232,1234],{"id":1233},"topic-resolution","Topic Resolution",[14,1236,1237,1238,1241,1242,1245,1246,1248,1249,1252,1253,464],{},"Topics within a room are namespaced automatically. When you subscribe to ",[37,1239,1240],{},"chat"," in room ",[37,1243,1244],{},"game-lobby"," of the app whose slug is ",[37,1247,1025],{},", the full topic path is ",[37,1250,1251],{},"my-app-a3f9/game-lobby/chat",". That three-part path is the only hierarchy: topic names themselves are single tokens with no ",[37,1254,1255],{},"/",[18,1257,1259],{"id":1258},"use-cases","Use Cases",[1178,1261,1262,1268,1274,1280],{},[1181,1263,1264,1267],{},[479,1265,1266],{},"Game lobbies"," - Each game session gets its own room",[1181,1269,1270,1273],{},[479,1271,1272],{},"Chat channels"," - Separate rooms for different conversations",[1181,1275,1276,1279],{},[479,1277,1278],{},"User spaces"," - Private rooms for user-specific notifications",[1181,1281,1282,1285],{},[479,1283,1284],{},"Multi-tenant apps"," - Isolate data between organizations",[18,1287,1289],{"id":1288},"next-steps","Next Steps",[1178,1291,1292,1298],{},[1181,1293,1294],{},[776,1295,1297],{"href":1296},"/docs/concepts/topics","Learn about Topics",[1181,1299,1300],{},[776,1301,1302],{"href":778},"Set up Access Control",[1304,1305,1306],"style",{},"html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":35,"searchDepth":61,"depth":61,"links":1308},[1309,1310,1314,1315,1316,1317,1318],{"id":20,"depth":61,"text":21},{"id":443,"depth":61,"text":444,"children":1311},[1312,1313],{"id":448,"depth":68,"text":449},{"id":467,"depth":68,"text":468},{"id":1161,"depth":61,"text":1162},{"id":1218,"depth":61,"text":1219},{"id":1233,"depth":61,"text":1234},{"id":1258,"depth":61,"text":1259},{"id":1288,"depth":61,"text":1289},"Learn about NoLag rooms for organizing topics and managing message namespaces.","md",{},"/docs/concepts/rooms",{"title":5,"description":1319},"docs/concepts/rooms","ARLHPoYFYSB4dFnVqarjPmtfZW5RZTO8bixgM1NUvGY",1789869419587]