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 strings and bytes. Everything else, objects, numbers, booleans, is a serialization decision you make. The writer and the reader must agree on it.
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.
Be careful with that convenience. Use the json type only when your own code wrote the value. If parsing fails because someone stored malformed data, the read throws. A value typed 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 on purpose:
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 the owner and the purpose, such as tenant:123:settings. A consistent format makes prefix listings useful and keeps unrelated data from colliding.
Two safety rules. First, validate sizes. Keys are limited to 512 bytes and values to 25 MiB, and a user-supplied fragment can blow past the key limit.
Second, never let a user pick 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. That makes it good for small per-key facts like a version or an owner. It is not a query system. You can’t filter or join on it.
Try this: write and read one typed settings record, then test two failure paths. Store malformed JSON under the key and see the read throw. Then read a missing key and confirm your fallback runs.
Lesson completed