puter.kv.list()Returns an array of all keys in the user's key-value store for the current app. If the user has no keys, the array will be empty.
Results are sorted lexicographically (string order) by key.
puter.kv.list()
puter.kv.list(pattern)
puter.kv.list(returnValues = false)
puter.kv.list(pattern, returnValues = false)
puter.kv.list(options)
pattern (String) (optional)
If set, only keys that match the given pattern will be returned. The pattern is prefix-based and can include a * wildcard only at the end. For example, abc and abc* both match keys that start with abc (such as abc, abc123, abc123xyz). If you need to match a literal * in the prefix, use * at the end (for example, key** matches keys that start with key*, or k*y* will match k*y prefixes). Default is *, which matches all keys.
returnValues (Boolean) (optional)
If set to true, the returned array will contain objects with both key and value properties. If set to false, the returned array will contain only the keys. Default is false.
options (Object) (optional)
An object with the following optional properties:
pattern (String): Same as the pattern parameter.returnValues (Boolean): Same as the returnValues parameter.limit (Number): Maximum number of items to return in a single call.cursor (String): A pagination cursor from a previous call. Pass the cursor value returned by the previous page to fetch the next one.offset (Number): Skips the given number of items before the page starts. Not recommended — requests get slower and more expensive the larger the offset; prefer cursor. Maximum 5000, and cannot be combined with cursor.includeTotal (Boolean): If true, the result includes a total count of every item matching the query (across all pages). The count is metered and its cost grows with the size of your store — request it once (on the first page) and avoid it in hot paths. If you only need to know whether more pages exist, check for cursor instead of counting.fetchUntilFull (Boolean): A page can come back with fewer than limit items even when more exist (for example when expired keys are excluded). If true, the page is filled up to limit items when possible. Requires limit.stream (Boolean): If true, the method returns an async iterator of KVListPage objects instead of a promise, for use with for await ... of. Combine with limit to control the page size, or cursor to resume from a previous page. Cannot be combined with offset. With includeTotal, only the first page carries total.A Promise that will resolve to either:
KVPair objects containing the user's key-value pairs for the current app, orKVListPage object when using any of limit, cursor, offset, includeTotal, or fetchUntilFull in optionsIf the user has no keys, the array will be empty.
When paginating, iterate until the result has no cursor — a page may hold fewer than limit items while more pages still exist.
Full (non-paginated) listings keep resolving to a plain array, so existing code is unaffected — under the hood the SDK now fetches them page by page. They still read the entire store, though: every page is metered, so on large stores a bare list() gets slow and costly (the SDK logs a one-time console warning when a full listing spans multiple pages). Prefer stream: true or explicit limit/cursor pages, and narrow the scan with a pattern.
With stream: true, the method returns an async iterator of KVListPage objects instead:
for await (const page of puter.kv.list({ pattern: 'log:*', stream: true })) {
for (const key of page.items) {
console.log(key);
}
}
Retrieve all keys in the user's key-value store for the current app
<html>
<body>
<script src="https://js.puter.com/v2/"></script>
<script>
(async () => {
// (1) Create a number of key-value pairs
await puter.kv.set('name', 'Puter Smith');
await puter.kv.set('age', 21);
await puter.kv.set('isCool', true);
puter.print("Key-value pairs created/updated<br><br>");
// (2) Retrieve all keys
const keys = await puter.kv.list();
puter.print(`Keys are: ${keys}<br><br>`);
// (3) Retrieve all keys and values
const key_vals = await puter.kv.list(true);
puter.print(`Keys and values are: ${(key_vals).map((key_val) => key_val.key + ' => ' + key_val.value)}<br><br>`);
// (4) Match keys with a pattern
const keys_matching_pattern = await puter.kv.list('is*');
puter.print(`Keys matching pattern are: ${keys_matching_pattern}<br>`);
// (5) Delete all keys (cleanup)
await puter.kv.del('name');
await puter.kv.del('age');
await puter.kv.del('isCool');
})();
</script>
</body>
Paginate results with a cursor
<html>
<body>
<script src="https://js.puter.com/v2/"></script>
<script>
(async () => {
// Create sample data
for (let i = 1; i <= 6; i++) {
await puter.kv.set(`item-${i}`, `value-${i}`);
}
puter.print('Created 6 key-value pairs<br><br>');
// Paginate with cursor (2 items per page)
let currentCursor = undefined;
let page = 1;
do {
const result = await puter.kv.list({
limit: 2,
returnValues: true,
cursor: currentCursor,
});
const items = result.items;
puter.print(`<b>Page ${page}:</b><br>`);
for (const item of items) {
puter.print(` ${item.key} => ${item.value}<br>`);
}
puter.print('<br>');
currentCursor = result.cursor;
page++;
} while (currentCursor);
puter.print('Done paginating.<br><br>');
// Cleanup
for (let i = 1; i <= 6; i++) {
await puter.kv.del(`item-${i}`);
}
puter.print('Cleaned up sample data.');
})();
</script>
</body>
</html>
Sort keys lexicographically
<html>
<body>
<script src="https://js.puter.com/v2/"></script>
<script>
(async () => {
await puter.kv.set('log:2025-03-15T10:00:00Z', { msg: 'third' });
await puter.kv.set('log:2025-01-01T00:00:00Z', { msg: 'first' });
await puter.kv.set('log:2025-02-14T08:00:00Z', { msg: 'second' });
const logs = await puter.kv.list('log:*');
puter.print('Sorted keys: <br/>');
puter.print(logs.join('<br/>'));
// Cleanup
await puter.kv.del('log:2025-03-15T10:00:00Z');
await puter.kv.del('log:2025-01-01T00:00:00Z');
await puter.kv.del('log:2025-02-14T08:00:00Z');
})();
</script>
</body>
</html>
Sort numeric keys with zero-padding
<html>
<body>
<script src="https://js.puter.com/v2/"></script>
<script>
(async () => {
// Wrong — will sort as 1, 10, 100, 2, 20
await puter.kv.set('item:1', '...');
await puter.kv.set('item:10', '...');
await puter.kv.set('item:2', '...');
// Correct — zero-pad to a fixed width
await puter.kv.set('item:001', '...');
await puter.kv.set('item:002', '...');
await puter.kv.set('item:010', '...');
await puter.kv.set('item:100', '...');
const items = await puter.kv.list('item:*');
puter.print('Items with zero-padding: <br/>');
puter.print(items.join('<br/>'));
// Cleanup
await puter.kv.del('item:1');
await puter.kv.del('item:10');
await puter.kv.del('item:2');
await puter.kv.del('item:001');
await puter.kv.del('item:002');
await puter.kv.del('item:010');
await puter.kv.del('item:100');
})();
</script>
</body>
</html>
Design keys for query-like filtering with prefix patterns
<html>
<body>
<script src="https://js.puter.com/v2/"></script>
<script>
(async () => {
const orders = [
{ id: '0001', status: 'pending', customer: 'alice', total: 48 },
{ id: '0002', status: 'shipped', customer: 'alice', total: 72 },
{ id: '0003', status: 'pending', customer: 'bob', total: 15 },
];
// In KV, key design is your query plan.
// We store the same order under multiple prefixes so each read path
// becomes a simple prefix query with puter.kv.list().
for (const order of orders) {
await puter.kv.set(`demo:order:by-id:${order.id}`, order);
await puter.kv.set(`demo:order:by-status:${order.status}:${order.id}`, order);
await puter.kv.set(`demo:order:by-customer:${order.customer}:${order.id}`, order);
await puter.kv.set(`demo:order:by-status-customer:${order.status}:${order.customer}:${order.id}`, order);
}
puter.print('<b>Stored read paths</b><br>');
puter.print('demo:order:by-status:pending:*<br>');
puter.print('demo:order:by-customer:alice:*<br>');
puter.print('demo:order:by-status-customer:pending:alice:*<br><br>');
const pendingOrders = await puter.kv.list('demo:order:by-status:pending:*', true);
puter.print('<b>Query: status = pending</b><br>');
pendingOrders.forEach(({ key, value }) => {
puter.print(`${key} => ${value.customer} ($${value.total})<br>`);
});
puter.print('<br>');
const aliceOrders = await puter.kv.list('demo:order:by-customer:alice:*', true);
puter.print('<b>Query: customer = alice</b><br>');
aliceOrders.forEach(({ key, value }) => {
puter.print(`${key} => ${value.status} ($${value.total})<br>`);
});
puter.print('<br>');
const alicePendingOrders = await puter.kv.list('demo:order:by-status-customer:pending:alice:*', true);
puter.print('<b>Query: status = pending AND customer = alice</b><br>');
alicePendingOrders.forEach(({ key, value }) => {
puter.print(`${key} => order ${value.id} ($${value.total})<br>`);
});
puter.print('<br>');
puter.print('<b>Takeaway</b><br>');
puter.print('With puter.kv.list(), filtering comes from key prefixes.<br>');
puter.print('If you need another query path, add another prefix-friendly key.<br><br>');
// Cleanup
for (const order of orders) {
await puter.kv.del(`demo:order:by-id:${order.id}`);
await puter.kv.del(`demo:order:by-status:${order.status}:${order.id}`);
await puter.kv.del(`demo:order:by-customer:${order.customer}:${order.id}`);
await puter.kv.del(`demo:order:by-status-customer:${order.status}:${order.customer}:${order.id}`);
}
})();
</script>
</body>
</html>