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.

8 minute lesson

~~~

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 and course as PDF and EPUB files.

Get the download library →