KV-backed Durable Object Storage (Legacy)
Last updated Apr 21, 2026 Note
This page documents the storage API for legacy KV-backed Durable Objects.
For the newer SQLite-backed Durable Object storage API, refer to SQLite-backed Durable Object Storage.
The Durable Object Storage API allows Durable Objects to access transactional and strongly consistent storage. A Durable Object's attached storage is private to its unique instance and cannot be accessed by other objects.
The Durable Object Storage API comes with several methods, including SQL, point-in-time recovery (PITR), key-value (KV), and alarm APIs. Available API methods depend on the storage backend for a Durable Objects class, either SQLite or KV.
| Methods <sup>1</sup> | SQLite-backed Durable Object class | KV-backed Durable Object class |
|---|---|---|
| SQL API | ✅ | ❌ |
| PITR API | ✅ | ❌ |
| Synchronous KV API | ✅ <sup>2, 3</sup> | ❌ |
| Asynchronous KV API | ✅ <sup>3</sup> | ✅ |
| Alarms API | ✅ | ✅ |
<details>
<summary>
Footnotes
</summary>
<sup>1</sup> Each method is implicitly wrapped inside a transaction, such that its results are atomic and isolated from all other storage operations, even when accessing multiple key-value pairs.
<sup>2</sup> KV API methods like <code>get()</code>, <code>put()</code>, <code>delete()</code>, or <code>list()</code> store data in a hidden SQLite table <code>__cf_kv</code>. Note that you will be able to view this table when listing all tables, but you will not be able to access its content through the SQL API.
<sup>3</sup> SQLite-backed Durable Objects also use <a href="https://developers.cloudflare.com/durable-objects/api/sqlite-storage-api/#synchronous-kv-api">synchronous KV API methods</a> using <code>ctx.storage.kv</code>, whereas KV-backed Durable Objects only provide <a href="https://developers.cloudflare.com/durable-objects/api/legacy-kv-storage-api/#asynchronous-kv-api">asynchronous KV API methods</a>.
</details>
Recommended SQLite-backed Durable Objects
Cloudflare recommends all new Durable Object namespaces use the SQLite storage backend. These Durable Objects can continue to use storage key-value API.
Additionally, SQLite-backed Durable Objects allow you to store more types of data (such as tables), and offer Point In Time Recovery API which can restore a Durable Object's embedded SQLite database contents (both SQL data and key-value data) to any point in the past 30 days.
Creating new namespaces with the key-value storage backend is no longer supported for accounts without an existing key-value-backed namespace. The key-value storage backend remains available for existing namespaces, and a migration path from the key-value storage backend to the SQLite storage backend will be available in the future.
Access storage
Durable Objects gain access to Storage API via the DurableObjectStorage interface and accessed by the DurableObjectState::storage property. This is frequently accessed via this.ctx.storage with the ctx parameter passed to the Durable Object constructor.
The following code snippet shows you how to store and retrieve data using the Durable Object Storage API.
export class Counter extends DurableObject {
constructor(ctx, env) {
super(ctx, env);
}
async increment() {
let value = (await this.ctx.storage.get("value")) || 0;
value += 1;
await this.ctx.storage.put("value", value);
return value;
}
}
export class Counter extends DurableObject {
constructor(ctx: DurableObjectState, env: Env) {
super(ctx, env);
}
async increment(): Promise<number> {
let value: number = (await this.ctx.storage.get("value")) || 0;
value += 1;
await this.ctx.storage.put("value", value);
return value;
}
}
from workers import DurableObject
class Counter(DurableObject):
def __init__(self, ctx, env):
super().__init__(ctx, env)
async def increment(self):
value = (await self.ctx.storage.get("value")) or 0
value += 1
await self.ctx.storage.put("value", value)
return value
JavaScript is a single-threaded and event-driven programming language. This means that JavaScript runtimes, by default, allow requests to interleave with each other which can lead to concurrency bugs. The Durable Objects runtime uses a combination of input gates and output gates to avoid this type of concurrency bug when performing storage operations. Learn more in our blog post ↗︎.
Asynchronous KV API
KV-backed Durable Objects provide KV API methods which are asynchronous.
get
ctx.storage.get(key string, options Object optional):Promise<any>- Retrieves the value associated with the given key. The type of the returned value will be whatever was previously written for the key, or undefined if the key does not exist.
ctx.storage.get(keys Array<string>, options Object optional):Promise<Map<string, any>>- Retrieves the values associated with each of the provided keys. The type of each returned value in the
Map↗︎ will be whatever was previously written for the corresponding key. Results in theMapwill be sorted in increasing order of their UTF-8 encodings, with any requested keys that do not exist being omitted. Supports up to 128 keys at a time.
Supported options
allowConcurrency:boolean- By default, the system will pause delivery of I/O events to the Object while a storage operation is in progress, in order to avoid unexpected race conditions. Pass
allowConcurrency: trueto opt out of this behavior and allow concurrent events to be delivered. noCache:boolean- If true, then the key/value will not be inserted into the in-memory cache. If the key is already in the cache, the cached value will be returned, but its last-used time will not be updated. Use this when you expect this key will not be used again in the near future. This flag is only a hint. This flag will never change the semantics of your code, but it may affect performance.
put
put(key string, value any, options Object optional):Promise- Stores the value and associates it with the given key. The value can be any type supported by the structured clone algorithm ↗︎, which is true of most types.
The size of keys and values have different limits depending on the Durable Object storage backend you are using. Refer to either: - SQLite-backed Durable Object limits - KV-backed Durable Object limits.
On a KV-backed Durable Object, if the serialized value exceeds the 128 KiB (131072 bytes) value-size limit, put() throws a RangeError (for example, Values cannot be larger than 131072 bytes.) before the write is applied.
put(entries Object, options Object optional):Promise- Takes an Object and stores each of its keys and values to storage.
- Each value can be any type supported by the structured clone algorithm ↗︎, which is true of most types.
- Supports up to 128 key-value pairs at a time. The size of keys and values have different limits depending on the flavor of Durable Object you are using. Refer to either:
delete
delete(key string, options Object optional):Promise<boolean>- Deletes the key and associated value. Returns
trueif the key existed orfalseif it did not. delete(keys Array<string>, options Object optional):Promise<number>- Deletes the provided keys and their associated values. Supports up to 128 keys at a time. Returns a count of the number of key-value pairs deleted.
Supported options
put(),delete()anddeleteAll()support the following options:allowUnconfirmedboolean- By default, the system will pause outgoing network messages from the Durable Object until all previous writes have been confirmed flushed to disk. If the write fails, the system will reset the Object, discard all outgoing messages, and respond to any clients with errors instead.
- This way, Durable Objects can continue executing in parallel with a write operation, without having to worry about prematurely confirming writes, because it is impossible for any external party to observe the Object's actions unless the write actually succeeds.
- After any write, subsequent network messages may be slightly delayed. Some applications may consider it acceptable to communicate on the basis of unconfirmed writes. Some programs may prefer to allow network traffic immediately. In this case, set
allowUnconfirmedtotrueto opt out of the default behavior. - If you want to allow some outgoing network messages to proceed immediately but not others, you can use the allowUnconfirmed option to avoid blocking the messages that you want to proceed and then separately call the
sync()method, which returns a promise that only resolves once all previous writes have successfully been persisted to disk. noCacheboolean- If true, then the key/value will be discarded from memory as soon as it has completed writing to disk.
- Use
noCacheif the key will not be used again in the near future.noCachewill never change the semantics of your code, but it may affect performance. - If you use
get()to retrieve the key before the write has completed, the copy from the write buffer will be returned, thus ensuring consistency with the latest call toput().
Automatic write coalescing
If you invoke put() (or delete()) multiple times without performing any await in the meantime, the operations will automatically be combined and submitted atomically. In case of a machine failure, either all of the writes will have been stored to disk or none of the writes will have been stored to disk.
