Playground and edge differences

RTCode.io runs your page and your worker in your browser. RTEdge.net runs the deployed copy on Cloudflare's network. The two share the same source, the same handler forms and the same shapes for request.rt and env, but they are different runtimes, so some behavior differs. This article lists every difference in one table, then explains why the differences exist and how to write code that behaves the same in both.

Overview

These parts are the same in both places:

Everything else that differs is in the table. Each row has its own anchor, so you can link to it. The facts are as of 2026-10-04 and describe the current shell, 2026-04-30; see Guarantees and limits for older deployments.

AreaPlaygroundRTEdge.netRead more
Workers
Where worker code runsIn your browser, in the service worker of the output origin. One service worker serves every playground on that origin.On Cloudflare Workers for Platforms: one isolated script for each route, wrapped in a dated shell.Architecture, Edge worker runtime
Module stateTop-level code runs again for every request that reaches the worker, and twice for export default workers. The handler from the first evaluation is cached, so module-level state persists until the source changes, the browser restarts the idle service worker, or you use another editor tab.The module is evaluated once for each isolate. State can persist across requests, but isolates are evicted unpredictably, so do not rely on it.Using workers
Work at the top level of the moduleIn an export default worker, fetch refers to your own handler while the module is evaluated, so a top-level fetch('//…') calls your handler, not the network.Calling fetch() or setting a timer at the top level throws during startup, which fails the upload. Start network requests and timers inside the handler.Using workers
eval() and new Function()Work everywhere.Throw while a request is being handled. Code generated while the module starts is expected to work. Libraries that generate code for each request, such as some template engines and schema compilers, can pass in the playground and fail at the edge.Edge worker runtime
APIs available to the workerThe browser's own Web APIs.Cloudflare Workers APIs. Node.js APIs are switched off: there is no process or Buffer, and node:* modules cannot be imported. cloudflare:* imports work only here. TCP sockets from cloudflare:sockets are unavailable.Edge worker runtime
WebAssemblyAll standard WebAssembly APIs work.Cloudflare does not allow compiling WebAssembly at run time. RTEdge.net extracts embedded bytes, and awaited instantiateStreaming calls with a literal URL, at deploy time. Only those shapes work.Importing modules and WebAssembly
Static URL importsBundled in the browser with esbuild in WebAssembly, and cached for each set of imports. Clear: Worker Import Cache (Ctrl+Shift+C / ⌘+Shift+C) or reloading the editor empties the cache.Bundled by a remote build service at every deploy, without the in-browser cache. Imports are frozen at deploy: a @latest URL resolves once for each deploy. Use full URLs in both places, because a bare specifier such as 'lodash' does not resolve in either.Importing modules and WebAssembly
Post-quantum Web CryptoDepends on the browser.ML-KEM-768, ML-KEM-1024 and ML-DSA-44, 65 and 87 are available to workers uploaded since 2026-10-01.Post-quantum cryptography
io:host and io:failSupported. io:host lists foreign hosts that the worker answers, and io:fail chooses what a failed handler does.Ignored. A deployed worker only receives requests that the router sends to its own route.Mocking third-party APIs
ctx and request.eventctx is the service worker's FetchEvent, so ctx.waitUntil() works. ctx.passThroughOnException does not exist. request.event is deprecated and exists only here.ctx is Cloudflare's ExecutionContext, and ctx.waitUntil() works.The fetch() handler
Telling the runtime apartrequest.rt.code is true, and navigator.userAgent is the browser's.request.rt.edge is true, navigator.userAgent is Cloudflare-Workers, and globalThis.Cloudflare.compatibilityFlags exists.Architecture
Console outputWorker console.* calls appear in the playground console, marked 🛎️⚙️.Output does not appear in responses. It goes to the route's tail logs Experimental.Console and network, Usage and tail logs
Requests and routing
Requests that reach the workerEvery request from the output except GET /, after ::su, your exported files and the files of sibling routes. A path that contains : always reaches it.A path that contains : goes straight to the worker. For other requests the router first applies the source view, the access rules and exported files. A request that is still unanswered reaches the worker only when the root document is empty, the path is a dotted name that is not an export, or the method is not GET.Request routing
Paths such as /api/usersReach the worker, with no : needed.Redirected with 307 to /api/users/, which is a different route and answers 404, "Code does not exist." Use /:api/users.Request routing
GET /Always returns the input document, even when a worker exists. The worker never sees GET /.Serves the root document. The worker answers GET / only when the root document is empty after deploy processing.Request routing
Navigations: iframe src, links and formsReach the worker as a bare GET, without the original method, headers or body. A <form method=post action=:submit> arrives as GET /submit, without its data. Use fetch() from a page script to send a POST.Keep the method, the headers and the body.Using workers
A handler that returns nothingThe request falls through to the network, which answers 404 from the output host.HTTP 501, "Worker response is undefined."Error pages
Errors in the handlerWith io:fail closed, the default, a rejected promise with an Error gives HTTP 500 as text/plain with a filtered stack. Synchronous throws, compile errors and errors in top-level code end in a network error. With io:fail=open, any failure forwards the original request to the network. The failure is also logged in the console.An uncaught exception gives an HTTP 500 HTML page, "Worker encountered an exception.", with the first two stack lines.Error pages
Reserved paths/favicon.ico, /robots.txt and :: paths other than ::su reach the worker.The platform answers /favicon.ico and /robots.txt at the root of a host, and the :: suffix families it owns, before your worker.Reserved paths
Access rulesNot enforced. The output renders and runs everything.Stored with the deployment and enforced. edge:user guards the root document, exported files and fallback requests, but not : calls, so a worker checks request.rt.user itself.Restricting access
URL suffixesOnly ::su is emulated, on the output origin.All of them: sign-in (::si, ::sa, ::so), ?, ??, *, ::qr and the rest.URL suffixes
Request data
request.rt{ ip, code: true, user, domain, route, vendor, server: 'RTCode.io', worker }. domain and route are both the output hostname. There is no edge, ray, colo or country. The editor can replace the object with sync.requestRT.{ edge: true, ip, ray, colo, country, user, domain, route, vendor, server: 'RTEdge.net', worker }. domain and route are the real route.Request.rt
request.cfEmulated from the device's connection to ip.rt.ht, minus a few fields. It describes the first successful lookup, not the current request.Not available. Use request.rt.colo and request.rt.country.ip.rt.ht
request.rt.ipThe public IP address of the device that runs the output, from ip.rt.ht. null until a lookup succeeds.The visitor's IP address, or null.Request.rt
request.rt.userThe email of the account signed in to the editor, or null.The email of the visitor who is signed in on this host, or null. Sessions are per host.Request.rt
Storage
Where KV data livesIn the IndexedDB of the output origin, under the owner guest. out.rtcode.io and out.rtcode.net hold separate data. It never syncs with RTEdge.net.In Cloudflare KV, under the deployment owner.Storing data in KV
Largest KV valueA value plus its metadata may be up to 64 KiB. Error messages also differ from Cloudflare KV's.Up to 10 MiB, for workers deployed since shell 2025-09-29. Before that shell the limit was 64 KiB.KVNamespace
domain and route scopesBoth equal the output hostname, so every playground on that origin shares the OD and OR scopes. Per-user scopes are shared by all playgrounds of the same signed-in editor. Prefix your keys if you need isolation.The real domain and route of the deployment. All of an owner's routes and domains share one keyspace.Storing data in KV
KV expirationexpirationTtl has no 60-second minimum, and an expired key is removed on the next read.expirationTtl and expiration must be at least 60 seconds ahead.KVNamespace
KV value typesAlso stores any structured-cloneable value, such as an object or a number, as it is.Cloudflare KV may reject such values. Store strings, for example with JSON.stringify, to stay portable.KVNamespace
When env.FS and exports updateOn a full output load only. A hot patch leaves them stale until the next full load.On every deploy.Live sync and reloads
Files and the served page
The page itselfGET / returns your input as written, inside the RTCode bootstrap. Every attribute, every <script type=worker> and every redacted element is present.Serves the cleaned source: workers blanked, edge:remove and edge:redact applied, RTCode attributes stripped, then re-serialized. The page starts with a doctype and a generated comment, and gets generated social tags.Anatomy of a deployment
edge:redact, edge:remove and copy:redactNo effect on the output. copy:redact masks content only when you copy from the editor.edge:redact and edge:remove change the served page and keep its line count. copy:redact has no effect on a deploy. Redaction is not secrecy: the source opened with ? still shows everything unless access is restricted.Keeping secrets out of deployments
o:= on <script type=worker>Exported.Not exported: every worker script is removed before exports are extracted. worker:fs on a worker script works in both places.o:
o:= and worker:fs inside <template>Ignored. File Sync and Code: Size ignore them too.Exported and extracted at any depth, so a <template o:=page.html> can hold a whole page with its own exports. Keep shared exports outside templates.Building a multi-page site
Export and worker:fs bodiesServed as authored. Bodies taken with outerHTML or innerHTML keep RTCode attributes and nested edge:remove and edge:redact content.Cleaned and re-serialized: RTCode attributes are stripped, and edge:remove and edge:redact are applied.Exporting files
o:content=value on a <textarea>Exports the decoded text, Def & text <b>.Exports the text with its character references undecoded, Def &amp; text &lt;b&gt;.o:content
h:* headers of exportsh:content-type, h:cache-control and h:origin-agent-cluster are overridden, h:referrer-policy has no effect, and h:set-cookie is dropped. A charset is appended to every type.Honored as written, except for the headers the platform sets itself. A type from the extension table has no charset.h:*
WebAssembly filesThere is no type for .wasm, so it is served as text/plain.application/wasm. A path that ends in .wasm.br is served with content-encoding: br Experimental.Importing modules and WebAssembly
HTML exportsServed as exported. A page exported from a <template> has no doctype, so it renders in quirks mode.Navigations get <!doctype html> prepended, and the response varies by Sec-Fetch-Dest. Start each page with <meta charset=utf-8>, because the type has no charset.Building a multi-page site
Export namesNames with /, spaces or non-ASCII characters are served.Those names are not reachable. Use letters, digits, _ and - with one ., or a leading-dot name such as .css.o:
Line endings in exportsNormalized to LF.CR and CRLF line endings are kept.Exporting files
Absolute paths from a path route/x.js reaches your own export./x.js reaches the file of the host's root route.Request routing
Sibling routesThe exported files of other open playgrounds whose routes share your host are served at /<its path>/<name>. Their root documents and workers never are.Each route's files come from its own deployment.Sibling routes
Outbound requests
Relative URLs in a worker's fetch()Resolve against the output origin. ::su, your own exports and sibling-route exports are answered live by the service worker, and everything else goes to the network.Do not reach your files. Use absolute URLs, or worker:fs assets for your own files.Using workers
Cross-origin requestsOutbound calls are browser fetches. CORS applies to cross-origin reads, and forbidden headers such as Cookie, Host and User-Agent cannot be set.Requests are sent from Cloudflare's network. An API that works from an edge worker can therefore fail in the playground.Mocking third-party APIs
Cross-origin calls to helper hostsCalls from the output to helper hosts such as x.rt.ht or px.rt.ht can succeed where the same call from a deployed page fails.A deployed page needs the host to send CORS headers and to answer preflights. Test cross-origin calls from a deployed page.Utilities
Fetching another RTEdge.net deploymentA browser fetch, which works when the target allows it.Direct requests from an edge worker to another *.rt.ht deployment are unverified and can answer 522. Go through the in.rt.ht gateway: https://in.rt.ht/<route>/….in.rt.ht, Domains and subdomains
Only after a deploy
Versions, screenshots and social tagsNot produced.Every deploy records a version and a screenshot, and the served page gets generated social tags.Versions and undo, Social previews

Why they differ

The playground and RTEdge.net are different runtimes. Worker code runs in a service worker in your browser in the playground, and on Cloudflare Workers for Platforms after a deploy, so the APIs, the limits, the storage and the routing differ. Architecture follows both paths.

Files are extracted twice. The editor extracts exports and worker assets from the live input, in your browser. The deploy compiler extracts them again on the server, with a different DOM implementation and a cleanup step that strips RTCode attributes and applies redaction. Both use the same tables and defaults, which is why most files look the same.

Finally, the platform stands in front of every deployment. Redirects, protection and suffix handlers answer before your code, and the access rules are enforced there. None of that exists in the playground output.

Telling the runtime apart

Branch on request.rt.edge, which is true only on RTEdge.net. In the playground, request.rt.code is true instead.

js
export default {
  fetch: request => new Response(request.rt.edge ? 'RTEdge.net' : 'Playground')
}

For a feature, detect the feature itself. For example, globalThis.Cloudflare?.compatibilityFlags?.webcrypto_modern_algorithms exists only at the edge, and crypto.subtle.constructor.supports?.('encapsulateBits', 'ML-KEM-768') works in both places when supports exists. See Post-quantum cryptography.

Code that behaves the same

Guarantees and limits

The table describes the playground and the current shell, dated 2026-04-30, as of 2026-10-04. The shell is the wrapper that RTEdge.net compiles around your edge worker. A deployed worker keeps the shell, the compatibility date and the compatibility flags of its last deploy until you redeploy it, so an older deployment can differ from the table. The compatibility date is the UTC date of that deploy.

ShellWhat it added
2023-04-09request.rt.vendor, request.rt.server and request.rt.worker.version
2023-09-22request.rt.edge
2023-12-15The first KV getters, with a 64 KiB limit for each value
2024-10-01request.rt.user and request.rt.worker.owner
2024-10-30env.KV.open(), request.rt.domain and request.rt.route
2024-12-30The current KV key format, with key checks and depth bounds
2025-01-03request.rt.ip
2025-09-29The fetch() URL handling, env.FS, request.rt.ray, colo and country, and the 10 MiB KV limit
2026-03-28env.FS[name].response Experimental
2026-04-30An exception summary with the error name, message and two stack lines

Redeploy a route to move it to the current shell.

Architecture

How the editor, the output, the deploy pipeline and the edge fit together.

Request routing

The order in which the playground output and RTEdge.net answer a request.

Anatomy of a deployment

What one deploy produces and how each part is served.

Security and access model

Sessions, owners, editors and permits, which RTEdge.net enforces.

See also