Read, write, and expire values

Read and write typed values

Serialize values deliberately, handle missing keys, and use metadata for small information needed during reads or lists.

KV stores byte or string values. Everything else — objects, numbers, booleans — is a serialization decision you make and must keep consistent between the writer and the reader.

For structured data, write JSON and read it back typed:

await env.SETTINGS.put(
  'tenant:123:settings',
  JSON.stringify({ theme: 'dark', locale: 'it' })
)

const settings = await env.SETTINGS.get('tenant:123:settings', 'json')

Passing json as the type makes get parse the value for you. Use get with a type such as json only when the stored format is controlled — when your own code wrote the value. If parsing fails because someone stored malformed data, the read throws, so a value written by hand in the dashboard can break a production read path.

Missing keys are normal

get returns null for a missing key. Not an error, not an empty string. Handle it explicitly:

const settings = await env.SETTINGS.get('tenant:123:settings', 'json')
if (settings === null) {
  return defaults()
}

Decide what “missing” means for each key — fall back to defaults, treat it as logged out, recompute. Code that assumes the key always exists works right up to the first new tenant.

Key names are a design surface

Choose a key format that includes tenant and purpose, such as tenant:123:settings. A consistent format makes prefix listings useful and keeps unrelated data from colliding.

Two safety rules. Validate sizes: keys are limited to 512 bytes and values to 25 MiB, and a user-supplied fragment can blow past the key limit. And never let an untrusted user select another tenant’s prefix — if the tenant ID in the key comes from the request body instead of the verified identity, one customer can read another’s data.

Metadata rides along

A write can attach small JSON metadata, and getWithMetadata returns both:

await env.SETTINGS.put('tenant:123:settings', body, {
  metadata: { version: 3, updatedBy: 'flavio' },
})

const { value, metadata } = await env.SETTINGS.getWithMetadata('tenant:123:settings', 'json')

Metadata also comes back in list results without reading each value, which makes it good for small per-key facts. It is not a relational query system — you cannot filter or join on it.

Now write and read one typed settings record, then test two failure paths: malformed JSON stored under the key, and a missing key.

Lesson completed

Take this course offline

Get every free book, course edition, and software download.

Get the download library →