Request.rt

The rt property of the Request that a worker receives holds platform metadata: the signed-in user, the client IP address, the domain and route being served, and where the code runs. Every request that reaches a fetch() handler has it, in the playground and on RTEdge.net.

Value

An object. Some fields exist only on RTEdge.net and some only in the playground, so detect the runtime with edge or code before you rely on them.

edge RTEdge.net only

true on RTEdge.net. The property is absent in the playground, so request.rt.edge tells you the code runs on the edge.

code Playground only

true in the playground. The property is absent on RTEdge.net.

ip

A string, or null: the client's IP address. On RTEdge.net it is the visitor's IPv4 or IPv6 address. In the playground it is the public IP address of the device that runs the output, looked up through ip.rt.ht; it is null until a lookup succeeds.

ray RTEdge.net only

A string: the Cloudflare ray ID of the request, in the form <hex>-<COLO>.

colo RTEdge.net only

A string: the IATA code of the data centre that served the request, such as ICN, or ### when it is unknown.

country RTEdge.net only

A string: the visitor's two-letter country code.

user

A string, or null: the email address of the visitor who is signed in on this host. It is null for a visitor who is not signed in. See The signed-in user.

domain

A string. On RTEdge.net it is the bare label name for a name.rt.ht deployment, and the full hostname for a custom domain. In the playground it is the hostname of the output origin, out.rtcode.io or out.rtcode.net.

route

A string. On RTEdge.net it is the route key, the domain plus the deployment path: name, name/docs, example.com or example.com/docs. In the playground it equals domain.

Pass domain and route to env.KV.open() to scope storage. Because both equal the output hostname in the playground, every playground in a browser shares those scopes; see KV in the playground.

vendor

The string 'Elefunc, Inc.'.

server

A string: 'RTCode.io' in the playground and 'RTEdge.net' on the edge.

worker

An object that describes the worker:

owner RTEdge.net only

A string: the email address of the account that owns the route, fixed when the worker is deployed. A redeploy by an editor keeps the owner.

version

An object with two strings. shell is the version date of the wrapper that RTEdge.net compiles around your code: 2026-04-30 is the current one, and the playground reports 2025-06-08v. fetch is an ISO timestamp: on RTEdge.net, the time the worker was compiled at deploy; in the playground, the time the current source was first compiled, which is set on the first request after an edit. See Shell versions.

bindings

An object whose KV member is ['edge'] on RTEdge.net and ['code'] in the playground. Use env.KV for storage rather than reading this value.

Description

Detecting the runtime

The same worker code can run in both places. Test request.rt.edge for RTEdge.net and request.rt.code for the playground. request.rt.server carries the same information as a name.

The signed-in user

On RTEdge.net, user comes from the session that the platform verified for the host. Visitors sign in with GitHub, Google, Microsoft or Apple through the ::si URL suffix. Sessions are signed with ECDSA P-384 and checked at the edge. A session is valid for 24 hours, and there is no silent refresh: after that, user is null until the visitor signs in again. Sessions are per host, so a visitor signed in on a.rt.ht is not signed in on b.rt.ht.

On a page, await fetch('::su').then(r => r.json()) returns the same value. Read request.rt.user rather than anything you parse from headers or cookies yourself.

Warning: edge:user rules protect the page and its files, but not the worker URLs that contain :. A worker that needs a signed-in user must check request.rt.user itself. Also never pass a null user to env.KV.open(): it opens the scope that all visitors share.

Compare email addresses in lower case if you keep your own list of allowed users.

Simulating users in the playground

In the playground, user is the email of the account you are signed in to RTCode.io with, or null. The editor supplies the whole rt object through the sync.requestRT hook, which you can set from io.eval.js or from the browser's DevTools console on the playground tab. The default is ({ rt }) => ({ ...rt, user: window.user?.email ?? null }).

js
sync.requestRT = ({ rt }) => ({ ...rt, user: 'ada@example.com' })  // signed in as someone else
sync.requestRT = ({ rt }) => ({ ...rt, user: null })               // signed out
sync.requestRT = ({ rt }) => ({ ...rt, user: window.user?.email ?? null })  // the default

The playground emulates only ::su and request.rt. The sign-in suffixes ::si, ::sa and ::so work on deployments only.

Headers that moved into rt

On RTEdge.net, the Cloudflare headers CF-Connecting-IP, CF-IPCountry and CF-Ray are not in request.headers when your handler runs; shells from 2025-09-29 on remove them. Read request.rt.ip, request.rt.country, request.rt.ray and request.rt.colo instead. Older deployments keep the headers until you redeploy them.

Do not rely on request.cf at the edge either: use request.rt.colo and request.rt.country. The playground emulates request.cf, which is one more reason to read rt in code that must run in both places.

Copies of the request

rt is an ordinary own property of the Request object your handler receives. Copies made with request.clone() or new Request(request) do not carry it, so read request.rt from the request you were given.

Examples

Where does the code run?

The page asks its worker for a few rt fields. In the playground it prints "where":"playground"; after you deploy it, the same code prints "where":"edge".

html
<output id=out></output>
<script type=module>
  out.value = JSON.stringify(await fetch(':where').then(r => r.json()))
</script>
<script type=worker edge>
  export default {
    fetch: request => Response.json({
      where: request.rt.edge ? 'edge' : request.rt.code ? 'playground' : '?',
      user: request.rt.user,
      route: request.rt.route
    })
  }
</script>

Requiring a signed-in user

The worker answers 401 unless request.rt.user is set. In the playground it sees your RTCode.io account; on RTEdge.net it sees the visitor's session.

html
<output id=reply></output>
<script type=module>
  const r = await fetch(':hello')
  reply.value = `${r.status} ${await r.text()}`
</script>
<script type=worker edge>
  export default {
    fetch: request => request.rt.user
      ? Response.json({ hello: request.rt.user })
      : new Response('Sign in', { status: 401 })
  }
</script>

Specifications

Not part of any specification. request.rt is an RTCode.io and RTEdge.net extension to the Fetch Standard Request. On RTEdge.net the worker runs in the Cloudflare Workers runtime.

Availability

FeaturePlaygroundRTEdge.netNotes
request.rtYesYesOn every request a worker receives.
edgeNoYesAlways true on RTEdge.net.
codeYesNoAlways true in the playground.
ipPartialYesIn the playground, the IP address of the device that runs the output.
ray, colo, countryNoYes
userYesYesIn the playground, your RTCode.io account, or the value that sync.requestRT returns.
domain, routePartialYesIn the playground, both are the output hostname.
vendor, serverYesYesserver names the runtime.
worker.ownerNoYes
worker.version, worker.bindingsYesYesValues differ.
sync.requestRTYesNoAn editor-side hook for simulating users.

See also