KVNamespace

The KVNamespace interface is a key–value namespace scoped to a user, a domain or a route. env.KV.open() returns one, and its methods get(), getWithMetadata(), put(), delete() and list() read and write the keys of that scope. On RTEdge.net it behaves like a Cloudflare Workers KV namespace; in the playground the same API stores data in your browser.

Instance properties

None. A namespace is a proxy that answers every property name, then included, with a function. Call open() synchronously and await the methods, never the namespace: do not await it, return it from an async function, put it in Promise.all(), serialize it or log it. await env.KV.open(…) never settles.

Instance methods

All methods are asynchronous, so their errors arrive as rejections. Keys are always relative to the scope you opened.

KVNamespace.get()

Reads one value, or up to 100 values at once, as text, JSON, an ArrayBuffer or a stream.

KVNamespace.getWithMetadata()

Reads a value together with the metadata stored with it.

KVNamespace.put()

Writes a value, with an optional expiry and metadata.

KVNamespace.delete()

Removes a key.

KVNamespace.list()

Lists the key names of the scope, optionally under a prefix.

get()

js
await kv.get(key)
await kv.get(key, type)
await kv.get(key, { type, cacheTtl })
await kv.get(keys, type)
key

A string.

type Optional

How to read the value: 'text' (the default), 'json', 'arrayBuffer' or 'stream'.

cacheTtl Optional RTEdge.net only

The Cloudflare KV option that sets how long a location may cache the value. It is part of the options-object form.

keys

An array of up to 100 keys. A bulk read supports only the types 'text' and 'json'.

Return value. A promise that resolves to the value, or to null when the key is missing or has expired. With an array of keys, it resolves to a Map that is keyed by your keys, each value being null for a missing key.

Errors. In the playground, invalid JSON with type 'json' throws Error: Invalid JSON data; more than 100 keys throw Error: Maximum of 100 keys supported; and a bulk read of another type throws Error: Bulk operations only support text and json types. See Errors for the rules that apply to every method.

getWithMetadata()

js
await kv.getWithMetadata(key)
await kv.getWithMetadata(key, type)
await kv.getWithMetadata(key, { type, cacheTtl })
await kv.getWithMetadata(keys, type)

The parameters are those of get().

Return value. A promise that resolves to { value, metadata }, where metadata is null when none was stored. For a missing key, value and metadata are null. The result may carry an extra cacheStatus field, which you should ignore. With an array of keys, it resolves to a Map keyed by your keys, whose values are such objects, or null in the playground for a missing key.

put()

js
await kv.put(key, value)
await kv.put(key, value, { expiration, expirationTtl, metadata })
key

A string.

value

A string, an ArrayBuffer, any ArrayBufferView or a ReadableStream. Store strings, such as JSON.stringify(x), for code that must run in both places: the playground also keeps other cloneable values, like objects and numbers, but RTEdge.net may reject them.

expirationTtl Optional

A number of seconds after which the key expires. It wins over expiration. On RTEdge.net it must be at least 60. The playground has no minimum and deletes an expired key on the next read.

expiration Optional

The moment the key expires, in seconds since the Unix epoch. On RTEdge.net it must be at least 60 seconds in the future.

metadata Optional

A JSON-serializable value stored with the key. On RTEdge.net it may be at most 1024 bytes of serialized JSON.

Return value. A promise that resolves to undefined.

Errors. If the size of the value plus the size of the metadata exceeds the limit, put() throws Error: KV storage limits exceeded and writes nothing. The limit is 65,536 bytes (64 KiB) in the playground and 10,485,760 bytes (10 MiB) on RTEdge.net. Strings count their UTF-8 bytes, ArrayBuffer and Uint8Array values their byteLength, streams the bytes they deliver, and metadata and every other type the length of their JSON.stringify(). Before it writes, put() reads the existing entry, which counts as one more KV operation.

delete()

js
await kv.delete(key)

Removes key. It resolves to undefined, whether or not the key existed. Like put(), it reads the existing entry first.

list()

js
await kv.list()
await kv.list({ prefix, limit, cursor })
prefix Optional

A string: list only the keys that start with it. It is relative to the scope, like every returned name.

limit Optional

The most keys to return. On RTEdge.net the default and the maximum are both 1000, so keep it at 1000 or less in code that must run in both places.

cursor Optional

The cursor of the previous page, to read the next one. On RTEdge.net it works as in Cloudflare KV. In the playground, paging with a cursor is not supported: use a narrower prefix to reach more keys.

Return value. A promise that resolves to { keys, list_complete, cursor }. keys is an array of { name, expiration?, metadata? } objects, sorted by the UTF-8 bytes of the name and holding only keys that have not expired. list_complete is false while more keys exist. cursor is set when there is a next page on RTEdge.net. A cacheStatus field may also appear and can be ignored.

Errors

These rules apply to every method.

Limits and differences

TopicPlaygroundRTEdge.net
StorageIndexedDB on the output origin. out.rtcode.io and out.rtcode.net hold separate data.Cloudflare KV, under the owner of the deployment.
Value plus metadata64 KiB (65,536 bytes)10 MiB (10,485,760 bytes)
domain and route scopesBoth equal the output hostname, so every playground in the browser shares them.The real domain and route of the deployment.
ExpiryNo minimum; deleted on the next read.At least 60 seconds.
list() pagingUse narrower prefixes.cursor, up to 1000 keys per page.
ConsistencyImmediate, in your browser.Eventually consistent: see below.

On RTEdge.net, a write is usually visible at once in the same Cloudflare location, but it may take 60 seconds or more to appear elsewhere, and a lookup of a missing key can be cached too. There are no atomic increments and no transactions, so concurrent read-modify-write counters lose updates. Playground data never syncs with RTEdge.net.

Deployments built with an older shell behave differently until you redeploy them: the cap per key was 64 KiB before shell 2025-09-29, and open() itself dates from shell 2024-10-30. See Shell versions.

Examples

A per-user counter

The worker opens the namespace without await, refuses signed-out visitors, and stores the count as a string.

html
<iframe src=:visits></iframe>
<script type=worker edge>
  export default {
    fetch: async (request, { KV }) => {
      const { user, route } = request.rt
      if (!user) return Response.json({ error: 'Sign in first' }, { status: 401 })
      const kv = KV.open({ user, route })
      const visits = (await kv.get('visits', 'json') ?? 0) + 1
      await kv.put('visits', JSON.stringify(visits), { metadata: { at: Date.now() } })
      return Response.json({ user, visits })
    }
  }
</script>

Expiry, metadata, bulk reads and prefixes

js
const kv = KV.open({ route: request.rt.route })
await kv.put('session:ada', 'signed-in', { expirationTtl: 3600, metadata: { by: 'demo' } })

const { value, metadata } = await kv.getWithMetadata('session:ada')
const both = await kv.get(['session:ada', 'session:alan'], 'text')  // a Map: session:alan → null if missing
const { keys, list_complete } = await kv.list({ prefix: 'session:', limit: 100 })
const names = keys.map(k => k.name)                                 // scope-relative names

Specifications

Not part of any specification. KVNamespace follows Cloudflare Workers KV, with the scopes, size limit and playground storage that RTCode.io and RTEdge.net add. See also the Cloudflare KV limits and how KV works. The playground stores data with IndexedDB.

Availability

FeaturePlaygroundRTEdge.netNotes
get(), getWithMetadata()YesYesBulk reads take up to 100 keys.
put()YesYes64 KiB per value plus metadata in the playground, 10 MiB on RTEdge.net.
delete()YesYes
list()PartialYesPaging with cursor works on RTEdge.net only.
cacheTtlNoYes
Sharing data between the twoNoNoPlayground data and deployed data are separate stores.

See also