Skip to content

Settings ​

Kernel 0.3.8 reads your game's live settings from one signed record: who is a dev, where the fleet API is, where analytics go, and your own live values (flags, kill switches, prices). You change it with the CLI; running servers apply a change within seconds, with no deploy and no place publish.

  • It lives in the game's DataStore TypeTorch, key settings.
  • The CLI signs it with your two prod signing keys (Prod signing). Game code can write DataStores but can't sign, and servers refuse a record that doesn't verify. So a free model or a dev branch can't make itself a dev or point your servers somewhere else.
  • Server only. It holds tokens; nothing in it reaches clients.
  • It replaces every ConfigService key TypeTorch used before (TypeTorch, TypeTorchAccess, TypeTorchFleet, TypeTorchAnalytics). Kernel 0.3.8 reads no ConfigService keys, and the API key needs no universe:write.

What it holds ​

FieldWhatWritten by
accessmembers, revoked, devBadgeId from typetorch.json (who gets the dev menu)typetorch access push
defaultBranch, channelsfrom typetorch.jsontypetorch settings push
fleetderived from backend for kernels before 0.4: { url, token }, the fleet API and its write-only ingest tokentypetorch backend setup
backend{ url, key, analytics?, notify? }: the TypeTorch backend and its game key (CLI 0.9; kernels before 0.4 read the fleet and analytics sections it derives). analytics holds the analytics dials and the experiment overrides. notify = { keys: { current, previous?, since } }: the notification launch-data signing keys (43-character base64url strings; since in unix seconds)typetorch backend setup; typetorch experiment for backend.analytics.experiments (Analytics: experiments); typetorch notify key rotate for backend.notify (Notifications: keys)
analyticsthe analytics sink settings, derived from backendtypetorch backend setup
gameyour own live values, read with TypeTorch.liveConfig, plus two reserved keystypetorch settings set game.<key>

The whole record is at most 32 KB, game at most 16 KB.

Commands ​

From the game folder:

sh
bun run typetorch settings status                  # seq, age, whether your keys signed it, the fields (tokens hidden)
bun run typetorch settings get game.shop.price     # one field as JSON; `get` alone prints all of them
bun run typetorch settings set game.shop.price 75  # change one game value
bun run typetorch settings set game.event '{"name":"halloween","ends":1793000000}'
bun run typetorch settings set game.event - < event.json   # `-` reads the JSON from stdin
bun run typetorch settings unset game.event
bun run typetorch settings unset backend           # drops backend and the fleet and analytics sections derived from it
bun run typetorch settings push                    # defaultBranch, channels and access from typetorch.json
bun run typetorch access push                      # access only
bun run typetorch backend setup --url https://backend.example.com
bun run typetorch experiment weights onboarding 3,1   # an analytics experiment override

Every write reads the record, checks that your keys signed it, changes one field, raises seq by one, signs it with both keys and writes it back (if another machine wrote in between, it starts over on the newer record). Then it pings the servers, and sends the record's owners to the backend (PUT /v1/access with TYPETORCH_ADMIN_TOKEN; only owners may Sign in with Roblox there). A backend that is down or refuses is a warning, never a failed write: typetorch access push sends the owners again.

  • settings set takes game.<key> only. The other fields have their own commands (table above).
  • --dry-run shows the result without writing.
  • --no-ping: servers still pick it up within about a minute.
  • --force replaces a record your keys didn't sign (you lost both keys, or game code wrote junk). Its old fields are dropped, never re-signed, so write them again afterwards. For backend setup it also writes a value whose endpoint checks failed.
  • Every write needs both signing key files (else: "run typetorch keys init") and the deploy key's DataStore scopes (universe-datastores.objects:read, :create, :update) plus universe-messaging-service:publish for the ping.
  • typetorch keys rotate and keys resign re-sign the record with the new keys.
  • typetorch notify key rotate writes backend.notify.keys first, then sends the new key to the backend (the previous key stays valid 14 days). The keys are secrets like the game key: settings status and get hide them, and backend setup keeps them.
  • typetorch doctor reads it, checks the signature and tests the backend address and keys in it.

Checked before it is signed ​

A wrong address or key in backend isn't rejected by anything else: game servers fail every request (NetFail, HTTP 401, HTTP 530) until someone looks at the dev menu. So backend setup tests the value first (also with --dry-run), and typetorch doctor runs the same tests against the live record:

  1. url: it parses, is https (game servers accept nothing else), has no user info or query, uses only the characters the kernel accepts, and is the server's base address (not a /v1/... path). doctor accepts http on localhost for typetorch.json backend.url, which only the CLI reads.
  2. healthz: GET <url>/healthz answers {"ok":true} within 5 seconds.
  3. key and admin token: GET <url>/v1/auth/check (it changes nothing) says the game key has role game (write-only) and that the fleet and analytics parts run, and the admin token has role admin. Refused even with --force: the admin token in the game key's place, or the same value for both.

If one fails, the command prints it in red with a fix, writes nothing and exits 1; --force writes anyway. The checks need a backend with GET /v1/auth/check (@typetorch/backend).

Reserved game keys ​

Two game keys are read by TypeTorch itself. Write them with settings set like any other value.

KeyWhatRead by
tt.eventsthe scheduled live events: { "v": 1, "events": [{ "id", "name", "start", "end", "set": { "<key>": <json> } }] }, times in unix seconds (UTC). While an event runs, its set values replace those game keys for liveConfig, switched on each server's own clock. The backend explorer's Live events page builds the schedule and gives the exact command: typetorch settings set game.tt.events - with the JSON on stdin. Cancelling an event is signing the schedule without it. Details and limits: Live eventsframework 0.7.1+ (TypeTorch.liveEvents(), liveConfig)
feedbackthe player feedback rating scale (Player feedback). It wins over the game's configure(...); an invalid value turns feedback offframework 0.7.0+ (TypeTorch.feedback())

The analytics dials (backend.analytics) ​

Next to url and key, the record's backend.analytics holds the analytics dials. Frameworks 0.4+ read them; servers apply a change within seconds. More in Analytics.

FieldWhatWritten by
flushSecondsseconds between sends, 5-300 (default 15)backend setup --flush-seconds <s>
recordShareshare (0-1) of new players whose first session is recorded (default 1)backend setup --record-share <0-1>
experimentsthe live experiment overridestypetorch experiment
techEveryseconds between tech health samples, 15-3600 (default 60)no command
recordSecondsseconds a first-session recording runs after the first input, 10-600 (default 60; framework 0.7.1+)no command
positionsup to 64 event names whose game-code events always carry the player's position (framework 0.7.1+)no command
joinData, joinLaunchPrefixhow much join data the join row keeps (Join data, below)no command

No CLI command writes the last four yet. backend setup keeps whatever is already in backend.analytics.

Join data (backend.analytics.joinData) ​

How much of Roblox's join data (Player:GetJoinData()) the analytics join row keeps. It sits in the record's backend.analytics next to the other analytics dials (frameworks reading the old analytics field, and new AnalyticsEngine({ settings }) in tests, take the same value). Servers apply a change within seconds.

ValueThe join row keeps
"full" (default)the summary props (Analytics: the join row) and jd, a field-by-field copy of GetJoinData() (jd)
"summary"the summary props only, no jd
"off"only what it kept before notifications (plans/29): from, ctx, tp, party, the age bucket, Premium, country, friends in the server, returning or not. No jd, and none of the newer join props

Any other value is refused with a warning and read as "full".

Why "full" keeps LaunchData. It is at most 200 bytes (Roblox's own limit), and it is the only way to debug notification and share-link routing: whether the launch data reached the server at all, and what it was. A notification tap whose launch data never arrived looks like any direct join without it.

What jd never holds, in any mode:

  • another player's UserId: party members, the referrer and the followed player are pids, or "other";
  • TeleportData's content: only its size, type and top-level key names;
  • the value of a join-data key TypeTorch doesn't know: only its type.

Personal data in your launch data? If your game's share links or notifications put anything personal in the launch data (a name, an email, an invite code tied to a person), set "joinData": "summary".

joinLaunchPrefix is separate. It only decides whether the summary prop launch keeps the first 32 characters of your game's own launch data (default off). jd.LaunchData follows joinData alone, so with joinData: "full" the launch data is in jd even when joinLaunchPrefix is off.

No CLI command writes joinData yet: backend setup keeps any value already in backend.analytics.

Live values in game code ​

ts
import { t } from "@rbxts/t";
import { TypeTorch } from "@typetorch/framework";

// in a server module
private readonly price = TypeTorch.liveConfig("shop.price", {
	default: 50,
	parse: (raw) => (t.number(raw) && raw >= 0 ? raw : error("not a price")),
});

onStart() {
	this.reprice(this.price.get());
	this.trove.add(this.price.onChanged((value, previous) => this.reprice(value)));
}
  • get() is cheap: call it whenever you need the value.
  • onChanged fires only when this key's value really changes; it returns a disconnect for the trove.
  • The default is used when there is no record or no such key, on kernels before 0.3.8 (with one warning), or when parse throws (with one warning per bad value). The cloud test has no record either, so liveConfig gives defaults there (frameworks before 0.5.2 also print the "needs kernel 0.3.8" warning in it, wrongly).
  • Values are JSON. Keys are 1-64 characters of letters, digits and _ . : / -.
  • Server only. A client needs a value? Send it through your own network.
  • TypeTorch.settings() returns the whole verified record (server only; it holds tokens: never send it to a client).

How servers read it ​

  • At boot, alongside the other boot reads; it never holds up the 15 s boot budget.
  • About every minute, and within seconds of a ping from the CLI.
  • A server keeps the last good copy. It refuses a record that isn't signed, doesn't verify, is malformed, or has a lower seq than the one it holds, and says why.
  • Which signature counts: once the server loaded your key asset, only sig (the main key); before that, only sigF (the fallback key). The same rule as prod deploys.
  • No record yet: servers use the defaults: defaultBranch prod, only the experience owner is a dev, no fleet API, no analytics sending.

Check it in game ​

The dev menu's Server > Status shows "Settings #12, 3m old (sig)". Attention lists:

ItemMeaning
No settingsthere is no record: typetorch settings push
Settings refusedthe record isn't signed or doesn't verify, and the server has no good copy
Settings unreadablethe DataStore read fails
Settings copy refuseda newer read was refused while a good copy is kept (game code wrote it, or an old copy was put back)

Moving from ConfigService (kernel 0.3.8) ​

Kernel 0.3.8 doesn't read the old ConfigService keys, and they can't be read back to copy them. After the place runs kernel 0.3.8:

  1. bun run typetorch settings push (defaultBranch, channels, dev access).
  2. bun run typetorch backend setup --url <your backend> if you use one (or bun run local -- --game <game repo> in the backend folder for a local test: it runs backend setup for you).
  3. bun run typetorch access push: the owners also go to the backend (only they may Sign in with Roblox there).
  4. bun run typetorch settings status to check. Then delete the old keys in Creator Hub (Configs) if you like.

Until step 1, only the experience owner is a dev on 0.3.8 servers.

Released under the MIT License.