Skip to content

Storage API

The 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.

Durable Objects gain access to a persistent Storage API via ctx.storage, on the ctx parameter passed to the Durable Object constructor.

While access to a Durable Object instance is single-threaded, request executions can still interleave with each other when they wait on I/O, such as when waiting on the promises returned by persistent storage methods or fetch() requests.

The following code snippet shows you how to store and retrieve data using the Storage API.

export class Counter {
constructor(ctx, env) {
this.ctx = ctx;
}
async fetch(request) {
let url = new URL(request.url);
// retrieve data
let value = (await this.ctx.storage.get("value")) || 0;
// increment counter and get a new value
value += 1;
// store data
await this.ctx.storage.put("value", value);
return new Response(value);
}
}

Methods

The Storage API comes with several methods, including key-value (KV) API, SQL API, and point-in-time-recovery (PITR) API.

  • Durable Object classes with the default, key-value storage backend can use KV API.
  • Durable Object classes with the SQLite storage backend can use KV API, SQL API, and PITR API. KV API methods like get(), put(), delete(), or list() store data in a hidden SQLite table.

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.

get

  • get(keystring, optionsObjectoptional) : 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.
  • get(keysArray<string>, optionsObjectoptional) : 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 the Map will 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

  • allowConcurrencyboolean

    • 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: true to opt out of this behavior and allow concurrent events to be delivered.
  • noCacheboolean

    • 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(keystring, valueany, optionsObjectoptional) : 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. Keys are limited to a max size of 2,048 bytes and values are limited to 128 KiB (131,072 bytes).

  • put(entriesObject, optionsObjectoptional) : 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. Each key is limited to a maximum size of 2,048 bytes and each value is limited to 128 KiB (131,072 bytes).

delete

  • delete(keystring, optionsObjectoptional) : Promise<boolean>

    • Deletes the key and associated value. Returns true if the key existed or false if it did not.
  • delete(keysArray<string>, optionsObjectoptional) : 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.

deleteAll

  • deleteAll(optionsObjectoptional) : Promise

    • Deletes all keys and associated values, effectively deallocating all storage used by the Durable Object. In the event of a failure while the deleteAll() operation is still in flight, it may be that only a subset of the data is properly deleted. deleteAll() does not proactively delete Alarms. Use deleteAlarm() to delete an alarm.

Supported options

  • put(), delete() and deleteAll() 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 allowUnconfirmed to true to 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 noCache if the key will not be used again in the near future. noCache will 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 to put().

list

  • list(optionsObjectoptional) : Promise<Map<string, any>>

    • Returns all keys and values associated with the current Durable Object in ascending sorted order based on the keys’ UTF-8 encodings.

    • The type of each returned value in the Map will be whatever was previously written for the corresponding key.

    • Be aware of how much data may be stored in your Durable Object before calling this version of list without options because all the data will be loaded into the Durable Object’s memory, potentially hitting its limit. If that is a concern, pass options to list as documented below.

Supported options

  • startstring

    • Key at which the list results should start, inclusive.
  • startAfterstring

    • Key after which the list results should start, exclusive. Cannot be used simultaneously with start.
  • endstring

    • Key at which the list results should end, exclusive.
  • prefixstring

    • Restricts results to only include key-value pairs whose keys begin with the prefix.
  • reverseboolean

    • If true, return results in descending order instead of the default ascending order.
    • Enabling reverse does not change the meaning of start, startKey, or endKey. start still defines the smallest key in lexicographic order that can be returned (inclusive), effectively serving as the endpoint for a reverse-order list. end still defines the largest key in lexicographic order that the list should consider (exclusive), effectively serving as the starting point for a reverse-order list.
  • limitnumber

    • Maximum number of key-value pairs to return.
  • allowConcurrencyboolean

    • Same as the option to get(), above.
  • noCacheboolean

    • Same as the option to get(), above.

transaction

  • transaction(closureFunction(txn)) : Promise

    • Runs the sequence of storage operations called on txn in a single transaction that either commits successfully or aborts.

    • Explicit transactions are no longer necessary. Any series of write operations with no intervening await will automatically be submitted atomically, and the system will prevent concurrent events from executing while await a read operation (unless you use allowConcurrency: true). Therefore, a series of reads followed by a series of writes (with no other intervening I/O) are automatically atomic and behave like a transaction.

  • txn
    • Provides access to the put(), get(), delete() and list() methods documented above to run in the current transaction context. In order to get transactional behavior within a transaction closure, you must call the methods on the txn Object instead of on the top-level state.storage Object.

      Also supports a rollback() function that ensures any changes made during the transaction will be rolled back rather than committed. After rollback() is called, any subsequent operations on the txn Object will fail with an exception. rollback() takes no parameters and returns nothing to the caller.

sync

  • sync() : Promise

    • Synchronizes any pending writes to disk.

    • This is similar to normal behavior from automatic write coalescing. If there are any pending writes in the write buffer (including those submitted with the allowUnconfirmed option), the returned promise will resolve when they complete. If there are no pending writes, the returned promise will be already resolved.

getAlarm

  • getAlarm(optionsObjectoptional) : Promise<Number | null>

    • Retrieves the current alarm time (if set) as integer milliseconds since epoch. The alarm is considered to be set if it has not started, or if it has failed and any retry has not begun. If no alarm is set, getAlarm() returns null.

Supported options

  • Same options as get(), but without noCache.

setAlarm

  • setAlarm(scheduledTimeDate | number, optionsObjectoptional) : Promise

    • Sets the current alarm time, accepting either a JavaScript Date, or integer milliseconds since epoch.


      If setAlarm() is called with a time equal to or before Date.now(), the alarm will be scheduled for asynchronous execution in the immediate future. If the alarm handler is currently executing in this case, it will not be canceled. Alarms can be set to millisecond granularity and will usually execute within a few milliseconds after the set time, but can be delayed by up to a minute due to maintenance or failures while failover takes place.

deleteAlarm

  • deleteAlarm(optionsObjectoptional) : Promise

    • Deletes the alarm if one exists. Does not cancel the alarm handler if it is currently executing.

Supported options

  • setAlarm() and deleteAlarm() support the same options as put(), but without noCache.

sql.exec

ctx.storage.sql.exec(query: string, …bindings: any[]) : SqlStorageCursor

Parameters

  • query: string
    • The SQL query string to be executed. query can contain ? placeholders for parameter bindings.
  • bindings: any[] Optional
    • Optional variable number of arguments that correspond to the ? placeholders in query.

Returns

A cursor (SqlStorageCursor) to iterate over query row results as objects. SqlStorageCursor is a JavaScript Iterable, which supports iteration using for (let row of cursor). SqlStorageCursor is also a JavaScript Iterator, which supports iteration using cursor.next().

SqlStorageCursor supports the following methods:

  • next()
    • Returns an object representing the next value of the cursor. The returned object has done and value properties adhering to the JavaScript Iterator. done is set to false when a next value is present, and value is set to the next row object in the query result. done is set to true when the entire cursor is consumed, and no value is set.
  • toArray()
    • Iterates through remaining cursor value(s) and returns an array of returned row objects.
  • one()
    • Returns a row object if query result has exactly one row. If query result has zero rows or more than one row, one() throws an exception.
  • raw(): Iterator
    • Returns an Iterator over the same query results, with each row as an array of column values (with no column names) rather than an object.
    • Returned Iterator supports next(), toArray(), and one() methods above.
    • Returned cursor and raw() iterator iterate over the same query results and can be combined. For example:
let cursor = this.sql.exec("SELECT * FROM artist ORDER BY artistname ASC;");
let rawResult = cursor.raw().next();
if (!rawResult.done) {
console.log(rawResult.value); // prints [ 123, 'Alice' ]
} else {
// query returned zero results
}
console.log(cursor.toArray()); // prints [{ artistid: 456, artistname: 'Bob' },{ artistid: 789, artistname: 'Charlie' }]

SqlStorageCursor had the following properties:

  • columnNames: string[]
    • The column names of the query in the order they appear in each row array returned by the raw iterator.
  • rowsRead: number
  • The number of rows read so far as part of this SQL query. This may increase as you iterate the cursor. The final value is used for SQL billing.
  • rowsWritten: number
  • The number of rows written so far as part of this SQL query. This may increase as you iterate the cursor. The final value is used for SQL billing.

Examples

SQL API examples below use the following SQL schema:

export class MyDurableObject extends DurableObject{
sql: SqlStorage
constructor(ctx: DurableObjectState, env: Env) {
super(ctx, env);
this.sql = ctx.storage.sql;
this.sql.exec(`CREATE TABLE IF NOT EXISTS artist(
artistid INTEGER PRIMARY KEY,
artistname TEXT
);INSERT INTO artist (artistid, artistname) VALUES
(123, 'Alice'),
(456, 'Bob'),
(789, 'Charlie');`
);
}
}

Iterate over query results as row objects:

let cursor = this.sql.exec("SELECT * FROM artist;");
for (let row of cursor) {
// Iterate over row object and do something
}

Convert query results to an array of row objects:

// Return array of row objects: [{"artistid":123,"artistname":"Alice"},{"artistid":456,"artistname":"Bob"},{"artistid":789,"artistname":"Charlie"}]
let resultsArray1 = this.sql.exec("SELECT * FROM artist;").toArray();
// OR
let resultsArray2 = Array.from(this.sql.exec("SELECT * FROM artist;"));
// OR
let resultsArray3 = [...this.sql.exec("SELECT * FROM artist;")]; // JavaScript spread syntax

Convert query results to an array of row values arrays:

// Returns [[123,"Alice"],[456,"Bob"],[789,"Charlie"]]
let cursor = this.sql.exec("SELECT * FROM artist;");
let resultsArray = cursor.raw().toArray();
// Returns ["artistid","artistname"]
let columnNameArray = this.sql.exec("SELECT * FROM artist;").columnNames.toArray();

Get first row object of query results:

// Returns {"artistid":123,"artistname":"Alice"}
let firstRow = this.sql.exec("SELECT * FROM artist ORDER BY artistname DESC;").toArray()[0];

Check if query results have exactly one row:

// returns error
this.sql.exec("SELECT * FROM artist ORDER BY artistname ASC;").one();
// returns { artistid: 123, artistname: 'Alice' }
let oneRow = this.sql.exec("SELECT * FROM artist WHERE artistname = ?;", "Alice").one()

Returned cursor behavior:

let cursor = this.sql.exec("SELECT * FROM artist ORDER BY artistname ASC;");
let result = cursor.next();
if (!result.done) {
console.log(result.value); // prints { artistid: 123, artistname: 'Alice' }
} else {
// query returned zero results
}
let remainingRows = cursor.toArray();
console.log(remainingRows); // prints [{ artistid: 456, artistname: 'Bob' },{ artistid: 789, artistname: 'Charlie' }]

Returned cursor and raw() iterator iterate over the same query results:

let cursor = this.sql.exec("SELECT * FROM artist ORDER BY artistname ASC;");
let result = cursor.raw().next();
if (!result.done) {
console.log(result.value); // prints [ 123, 'Alice' ]
} else {
// query returned zero results
}
console.log(cursor.toArray()); // prints [{ artistid: 456, artistname: 'Bob' },{ artistid: 789, artistname: 'Charlie' }]

sql.exec().rowsRead():

let cursor = this.sql.exec("SELECT * FROM artist;");
cursor.next()
console.log(cursor.rowsRead); // prints 1
cursor.toArray(); // consumes remaining cursor
console.log(cursor.rowsRead); // prints 3

sql.databaseSize

ctx.storage.sql.databaseSize : number

Returns

The current SQLite database size in bytes.

let size = ctx.storage.sql.databaseSize;

Point in time recovery

For Durable Objects classes with SQL storage, the following point-in-time-recovery (PITR) API methods are available to restore a Durable Object’s embedded SQLite database to any point in time in the past 30 days. These methods apply to the entire SQLite database contents, including both the object’s stored SQL data and stored key-value data using the key-value put() API. The PITR API is not supported in local development because a durable log of data changes is not stored locally.

The PITR API represents points in times using “bookmarks”. A bookmark is a mostly alphanumeric string like 0000007b-0000b26e-00001538-0c3e87bb37b3db5cc52eedb93cd3b96b. Bookmarks are designed to be lexically comparable: a bookmark representing an earlier point in time compares less than one representing a later point, using regular string comparison.

ctx.storage.getCurrentBookmark() : Promise<string>

  • Returns a bookmark representing the current point in time in the object’s history.

ctx.storage.getBookmarkForTime(timestamp: number | Date): Promise<string>

  • Returns a bookmark representing approximately the given point in time, which must be within the last 30 days. If the timestamp is represented as a number, it is converted to a date as if using new Date(timestamp).

ctx.storage.onNextSessionRestoreBookmark(bookmark: string): Promise<string>

  • Configure the Durable Object so that the next time it restarts, it should restore its storage to exactly match what the storage contained at the given bookmark. After calling this, the application should typically invoke ctx.abort() to restart the Durable Object, thus completing the point-in-time recovery.

This method returns a special bookmark representing the point in time immediately before the recovery takes place (even though that point in time is still technically in the future). Thus, after the recovery completes, it can be undone by performing a second recovery to this bookmark.

let now = new Date();
// restore to 2 days ago
let bookmark = ctx.storage.getBookmarkForTime(now - 2);
ctx.storage.onNextSessionRestoreBookmark(bookmark);