Skip to content
Workers
Visit Workers on GitHub
Set theme to dark (⇧+D)

Durable Objects

Background

Durable Objects provide low-latency coordination and consistent storage for the Workers platform. A given namespace can support essentially unlimited Durable Objects, with each Object having access to a transactional, key-value storage API.

Durable Objects consist of two components: a class that defines a template for creating Durable Objects and a Workers script that instantiates and uses those Durable Objects. The class and the Workers script are linked together with a binding.

Learn more about using Durable Objects.


Durable Object class definition

export class DurableObject {
constructor(state, env){
}
async fetch(request) {
}
}
  • state

    • Passed from the runtime to provide access to the Durable Object's storage as well as various metadata about the Object.
  • state.id DurableObjectId

    • The ID of this Durable Object. It can be converted into a hex string using its .toString() method.
  • state.waitUntil

    • While waitUntil is available within a Durable Object, it has no effect. Refer to Durable Object Lifespan for more information.
  • state.storage

    • Contains methods for accessing persistent storage via the transactional storage API. See Transactional Storage API for a detailed reference.
  • state.blockConcurrencyWhile(callbackFunction()) Promise
    • Executes callback() (which may be async) while blocking any other events from being delivered to the object until the callback completes. This allows you to execute some code that performs I/O (such as a fetch()) with the guarantee that the object's state will not unexpectedly change as a result of concurrent events. All events that were not explicitly initiated as part of the callback itself will be blocked. This includes not only new incoming requests, but also responses to outgoing requests (such as fetch()) that were initiated outside of the callback. Once the callback completes, these events will be delivered.

      state.blockConcurrencyWhile() is especially useful inside the constructor of your object, to perform initialization that must occur before any requests are delivered.

      If the callback throws an exception, the object will be terminated and reset. This ensures that the object cannot be left stuck in an uninitialized state if something fails unexpectedly. To avoid this behavior, wrap the body of your callback in a try/catch block to ensure it cannot throw an exception.

      The value returned by the callback becomes the value returned by blockConcurrencyWhile() itself.

  • env

    • Contains environment bindings configured for the Worker script, such as KV namespaces, secrets, and other Durable Object namespaces. Note that in traditional Workers not using Modules syntax, these same "bindings" appear as global variables within the script. Scripts that export Durable Object classes always use the Modules syntax, and have bindings delivered to the constructor rather than placed in global variables.

Durable Object Lifespan

A Durable Object remains active until all asynchronous I/O, including Promises, within the Durable Object has resolved. This is true for all HTTP and/or WebSocket connections, except for failure scenarios, which may include unhandled runtime exceptions or exceeding the CPU limit.

From a Workers perspective, this is similar to enqueuing tasks with FetchEvent.waitUntil. For example, in order to send a POST request without delaying a Response, a Worker script may include the following code:

worker.mjs
export default {
fetch(req, env, ctx) {
// Send a non-blocking POST request.
// ~> Completes before the Worker exits.
ctx.waitUntil(
fetch('https://.../logs', {
method: 'POST',
body: JSON.stringify({
url: req.url,
// ...
})
})
);
return new Response('OK');
}
}

The same functionality can be achieved in a Durable Object, simply by omitting the await for the POST request. The request will complete before the Durable Object exits:

durable.mjs
export class Example {
fetch(req) {
// NOTE: Omits `await` intentionally.
// ~> Does not block `Response` output
// ~> Will still wait for POST to complete
fetch('https://.../logs', {
method: 'POST',
body: JSON.stringify({
url: req.url,
// ...
})
});
return new Response('OK');
}
}

Transactional Storage API

Accessible via the state.storage object passed to the Durable Object constructor.

Methods

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

      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: it will never change the semantics of your code, but it may affect performance.
  • get(keysArray<string>, optionsObject) 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. Any keys that do not exist will be omitted from the result Map. Supports up to 128 keys at a time.

      Supported options: Same as get(key, options), above.

  • 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 2048 bytes and values are limited to 32 KiB (32768 bytes).

      Supported 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. In the unlikely event that 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 be allowed to continue executing in parallel with a write being performed, 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. However, this does mean that after any write, subsequent network messages may be slightly delayed. Some applications may consider it acceptable to communicate on the basis of unconfirmed writes, and may prefer to permit network traffic immediately. In this case, allowUnconfirmed may be set to true to opt out of the default behavior. Refer to this blog post for an in-depth discussion.
      • noCacheboolean
        • If true, then the key/value will be discarded from memory as soon as it has completed writing to disk. Use this when you expect this key will not be used again in the near future. This flag is only a hint: it will never change the semantics of your code, but it may affect performance. In particular, if you get() the key before the write to disk has completed, the copy from the write buffer will be returned, thus ensuring consistency with the latest call to put().
  • put(entriesObject) 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 max size of 2048 bytes and each value is limited to 32 KiB (32768 bytes).

      Supported options: Same as put(key, value, options), above.

  • delete(keystring) Promise<boolean>
    • Deletes the key and associated value. Returns true if the key existed or false if it didn't.

      Supported options: Same as put(), above.

  • delete(keysArray<string>) Promise<number>
    • Deletes the provided keys and their associated values. Returns a count of the number of key-value pairs deleted.

      Supported options: Same as put(), above.

  • list() Promise<Map<string, any>>
    • Returns all keys and values associated with the current Durable Object in ascending lexicographic sorted order. 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 it will all be loaded into the Durable Object's memory, potentially hitting its limit. If that is a concern, pass options to list as documented below.
  • list(optionsObject) Promise<Map<string, any>>
    • Returns keys associated with the current Durable Object according to the parameters in the provided options object.

      Supported options:

      • startstring
        • Key at which the list results should start, inclusive.
      • 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 lexicographic order instead of the default ascending order.
      • 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(closureFunction(txn)) Promise
    • Runs the sequence of storage operations called on txn in a single transaction that either commits successfully or aborts.

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

  • deleteAll() 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.

      Supported options: Same as put(), above.

fetch() handler method

The fetch() method of a Durable Object namespace is called by the system when an HTTP request is sent to the Object. These requests are not sent from the public Internet, but from other Workers, using a Durable Object namespace binding (see below).

The method takes a Request as the parameter, and returns a Response (or a Promise for a Response).

If the method fails with an uncaught exception, the exception will be thrown into the calling worker that made the fetch() request.


Accessing a Durable Object from a Worker

To access a Durable Object from a worker, you must first configure the worker with a binding for a Durable Object namespace. The namespace is, in turn, configured to use a particular class, and controls access to instances of that class.

Namespace bindings have two jobs: generating object IDs and connecting to objects.

Generating IDs randomly

let id = OBJECT_NAMESPACE.newUniqueId()

Creates a new object ID randomly. This method will never return the same ID twice, and thus it is guaranteed that the object does not yet exist and has never existed at the time the method returns.

When generating an ID randomly, you need to store the ID somewhere in order to be able to reach the same object again in the future. You could, for example, store the ID in Workers KV, in an external database, or in a cookie in the user's browser.

Unique IDs are unguessable, therefore they can be used in URL-based access control, sometimes known as "anyone with the link can access."

To store the ID in external storage, use its .toString() method to convert it into a hex string, and OBJECT_NAMESPACE.idFromString() to convert the string back into an ID later.

Restricting objects to a jurisdiction

Durable Objects can be created so that they only run and store data within a specific jurisdiction to comply with local regulations. You must specify the jurisdiction when generating the Durable Object's id.

let id = OBJECT_NAMESPACE.newUniqueId({jurisdiction: "eu"})

Creates a new object ID that will only run and persist data within the European Union. The jurisdiction feature is useful for building applications that are compliant with regulations such as the GDPR. Jurisdiction constraints can only be used with ids created by newUniqueId() and are not currently compatible with ids created by idFromName(name).

Note that objects that are constrained to a jurisdiction may still be accessed by your Workers from anywhere in the world. The jurisdiction constraint only controls where the Durable Object itself runs and persists data. Consider using Regional Services to control the regions from which Cloudflare responds to requests.

The only jurisdiction currently supported is eu (the European Union).

Deriving IDs from names

let id = OBJECT_NAMESPACE.idFromName(name)

Parameters

  • name string
    • The object name, an arbitrary string from which the ID is derived.

This method derives a unique object ID from the given name string. It will always return the same ID when given the same name as input.

Parsing previously-created IDs from strings

let id = OBJECT_NAMESPACE.idFromString(hexId)

Parameters

  • hexId string
    • An ID string constructed by calling the .toString() method of an existing ID.

This method parses an ID that was previously stringified. This is useful in particular with IDs created using newUniqueId(), as these IDs need to be stored somewhere, probably as as a string.

A stringified object ID is a 64-digit hexadecimal number. However, not all 64-digit hex numbers are valid IDs. This method will throw if it is passed an ID that was not originally created by newUniqueId() or idFromName(). It will also throw if the ID was originally created for a different namespace.

Obtaining an Object Stub

let stub = OBJECT_NAMESPACE.get(id)

Parameters

  • id DurableObjectId
    • An ID constructed using newUniqueId(), idFromName(), or idFromString() on this namespace.

This method constructs an object "stub", which is a local client that provides access to a remote Durable Object.

If the remote object does not already exist, it will be created. Thus, there will always be something for the stub to talk to.

This method always returns the stub immediately, before it has connected to the remote object. This allows you to begin making requests to the object right away, without waiting for a network round trip.

Object Stubs

A Durable Object stub is a client object used to send requests to a remote Durable Object.

A stub is created using OBJECT_NAMESPACE.get(id) (above).

Stubs implement E-order semantics. When you make multiple calls to the same stub, it is guaranteed that the calls will be delivered to the remote object in the order in which you made them. This ordering guarantee often makes many distributed programming problems easier. However, there is a cost: due to random network disruptions or other transient issues, a stub may become disconnected from its remote object. Once a stub is disconnected, it is permanently broken, and all in-flight calls and future calls will fail with exceptions. In order to make new requests to the Durable Object, you must call OBJECT_NAMESPACE.get(id) again to get a new stub, and you must keep in mind that there are no ordering guarantees between requests to the new stub vs. the old one. If you don't care about ordering at all, you can create a new stub for every request.

Sending HTTP requests

let response = await stub.fetch(request)
let response = await stub.fetch(url, options)

The fetch() method of a stub has the exact same signature as the global fetch. However, instead of sending an HTTP request to the internet, the request is always sent to the Durable Object to which the stub points.

Any uncaught exceptions thrown by the Durable Object's fetch() handler are propagated to the caller's fetch() promise.