KV Store

The KV Store provides fast, namespaced key-value storage. Access it through platform.env.KV.{namespace} where {namespace} is any name you choose to organize your data.

import { getPlatform } from '@maravilla-labs/platform';

const platform = getPlatform();

// "demo" is the namespace
await platform.env.KV.demo.put('greeting', 'hello world');
const value = await platform.env.KV.demo.get('greeting');

API Reference

get(key)

Retrieves a value by key. Returns the stored value as a string, or null if the key does not exist.

const value = await platform.env.KV.myapp.get('user:abc');

if (value === null) {
  console.log('Key not found');
}

put(key, value, options?)

Stores a value under the given key. Overwrites any existing value for that key.

Parameters:

  • key (string) — the key to store the value under
  • value (string) — the value to store
  • options (object, optional):
    • ttl (number) — time-to-live in seconds; the key will be automatically deleted after this duration
// Store a value permanently
await platform.env.KV.sessions.put('session:xyz', JSON.stringify({ userId: '123' }));

// Store a value that expires in 1 hour
await platform.env.KV.sessions.put('session:xyz', JSON.stringify({ userId: '123' }), {
  ttl: 3600
});

delete(key)

Deletes a key and its associated value. No error is thrown if the key does not exist.

await platform.env.KV.demo.delete('todo:abc123');

list(options?)

Lists keys in the namespace. Supports prefix filtering and cursor-based pagination for iterating over large datasets.

Parameters (options object):

  • prefix (string, optional) — only return keys that start with this prefix
  • limit (number, optional) — maximum number of keys to return (max 1000)
  • cursor (string, optional) — pagination cursor from a previous list call

Returns:

{
  result: Array<{ name: string; expiration?: number }>;
  success: boolean;
  result_info: {
    cursor?: string;  // present if there are more results
    count: number;    // number of keys returned
  };
}
// List all keys with a prefix
const result = await platform.env.KV.demo.list({ prefix: 'todo:', limit: 50 });

for (const key of result.result) {
  console.log(key.name);         // e.g. "todo:abc123"
  console.log(key.expiration);   // Unix timestamp (seconds), if set
}

Paginating Through All Keys

Use the returned cursor to fetch subsequent pages:

let cursor = undefined;
const allKeys = [];

do {
  const result = await platform.env.KV.myapp.list({
    prefix: 'user:',
    limit: 100,
    cursor
  });

  allKeys.push(...result.result);
  cursor = result.result_info.cursor;
} while (cursor);

console.log(`Found ${allKeys.length} keys`);

Data Serialization

The KV Store stores values as strings. To store objects, arrays, or other complex types, serialize them with JSON.stringify and deserialize with JSON.parse:

// Storing an object
const todo = { id: 'abc', text: 'Buy groceries', done: false };
await platform.env.KV.demo.put('todo:abc', JSON.stringify(todo));

// Retrieving an object
const raw = await platform.env.KV.demo.get('todo:abc');
const parsed = raw ? JSON.parse(raw) : null;

Namespace Patterns

Namespaces let you logically separate different types of data. Use descriptive names that reflect the purpose:

// Different namespaces for different concerns
platform.env.KV.sessions    // user sessions
platform.env.KV.cache       // application cache
platform.env.KV.config      // configuration values
platform.env.KV.demo        // demo/example data

Within a namespace, use key prefixes with a delimiter (typically :) to create a hierarchical structure:

todo:abc123        -- a specific todo item
todo:def456        -- another todo item
user:123:profile   -- user profile
user:123:prefs     -- user preferences

This makes prefix-based listing very effective:

// Get all todos
const todos = await platform.env.KV.demo.list({ prefix: 'todo:' });

// Get everything for user 123
const userData = await platform.env.KV.myapp.list({ prefix: 'user:123:' });

Real-World Example: Todo Application

This example is taken from the Maravilla demo application and shows a complete CRUD implementation using the KV Store.

Loading Todos

import { getPlatform } from '@maravilla-labs/platform';

const platform = getPlatform();

// List all todo keys
const res = await platform.env.KV.demo.list({ prefix: 'todo:' });
const keys = res?.keys || [];

// Fetch each todo's value
const todos = [];
for (const key of keys) {
  const raw = await platform.env.KV.demo.get(key.name);
  if (raw) {
    try {
      todos.push(typeof raw === 'string' ? JSON.parse(raw) : raw);
    } catch {
      // skip malformed entries
    }
  }
}

// Sort by creation date (newest first)
todos.sort((a, b) => b.createdAt.localeCompare(a.createdAt));

Creating a Todo

const id = crypto.randomUUID().slice(0, 8);
const todo = {
  id,
  text: 'Buy groceries',
  done: false,
  createdAt: new Date().toISOString()
};

await platform.env.KV.demo.put(`todo:${id}`, JSON.stringify(todo));

Toggling a Todo

const raw = await platform.env.KV.demo.get(`todo:${id}`);
if (!raw) throw new Error('Todo not found');

const todo = typeof raw === 'string' ? JSON.parse(raw) : raw;
todo.done = !todo.done;

await platform.env.KV.demo.put(`todo:${id}`, JSON.stringify(todo));

Deleting a Todo

await platform.env.KV.demo.delete(`todo:${id}`);

REST API Endpoint

import { getPlatform } from '@maravilla-labs/platform';

// GET /api/todos -- list all todos
export const GET = async () => {
  const platform = getPlatform();
  const res = await platform.env.KV.demo.list({ prefix: 'todo:' });
  const keys = res?.keys || [];

  const todos = [];
  for (const key of keys) {
    const raw = await platform.env.KV.demo.get(key.name);
    if (raw) {
      todos.push(typeof raw === 'string' ? JSON.parse(raw) : raw);
    }
  }

  return json({ todos, count: todos.length });
};

// POST /api/todos -- create a new todo
export const POST = async ({ request }) => {
  const platform = getPlatform();
  const body = await request.json();

  const id = crypto.randomUUID().slice(0, 8);
  const todo = {
    id,
    text: body.text.trim(),
    done: false,
    createdAt: new Date().toISOString()
  };

  await platform.env.KV.demo.put(`todo:${id}`, JSON.stringify(todo));
  return json(todo, { status: 201 });
};

Limits

ParameterDevelopmentProduction
Max value size1 MB16 MB
Max keys per list1,0001,000
Key formatUTF-8 stringUTF-8 string

Next Steps