Skip to content
dply/edge

Resources

State (Durable Objects)

Small, strongly consistent values for counters, locks, and flags, stored in one Durable Object per app.

State keeps small values that stay put between requests and deploys: a counter, a lock, a flag, a rate-limit bucket. Every call goes to one place, in order, so two requests never both win. It runs on a Cloudflare Durable Object.

Use State when a value must be exact. A key-value store is faster to read from everywhere but is eventually consistent, so two requests that increment the same KV key can lose an update. State cannot.

State is available on every plan, on container apps and on SSR and hybrid apps.

Add State

  1. Open your app. On Overview, choose Add resource, then State.
  2. Enter a Name, such as visits.
  3. Choose Attach, then redeploy the app.

State is not created or shared like a bucket: it belongs to the app. Every State you add to one app shares the same set of keys. Adding two gives you two names for the same store. The data outlives deploys.

Use it from your code

State answers the same paths on every runtime:

Request Result
GET / Lists up to 100 keys as {"keys": [...]}.
GET /{key} The value as text. A missing key is a 404.
PUT /{key} Stores the request body as text.
DELETE /{key} Removes the key.
POST /incr/{key} Adds one and returns the new number. A missing key starts at 0.

Container apps

Call the private host. Copy the exact host from the State sheet's Connect tab. It looks like dply.my-app.visits.internal.

php
use Illuminate\Support\Facades\Http;

Http::withBody('hello', 'text/plain')->put('http://dply.my-app.visits.internal/greeting');
$value = Http::get('http://dply.my-app.visits.internal/greeting')->body();
$visits = (int) Http::post('http://dply.my-app.visits.internal/incr/visits')->body();
js
const host = 'http://dply.my-app.visits.internal';

await fetch(`${host}/greeting`, { method: 'PUT', body: 'hello' });
const value = await (await fetch(`${host}/greeting`)).text();
const visits = Number(await (await fetch(`${host}/incr/visits`, { method: 'POST' })).text());

Worker apps (SSR and hybrid)

Call env.NAME.fetch() with a path. The host in the URL does not matter.

js
export default {
  async fetch(request, env) {
    const n = await (await env.VISITS.fetch('https://state/incr/visits', { method: 'POST' })).text();
    return new Response('Visit #' + n);
  },
};

The raw Durable Object namespace is available as env.NAME.namespace if you need it.

Try it

State lives inside the running app and only the app can reach it, so the dashboard cannot read or write it. The sheet's Try it tab gives a command to run from the app instead:

bash
curl -X POST http://dply.my-app.visits.internal/incr/visits
curl http://dply.my-app.visits.internal/

Pricing

State bills every unit from the first:

Meter Price Unit
Requests $0.195 per million
Duration $16.25 per million GB-seconds
Rows read $0.0013 per million
Rows written $1.30 per million
Storage $0.26 per GB-month

Duration is the time the object is active, at 128 MB. Storage is the largest size during the month. Usage is collected daily and billed to your organization. This bills at the invoice line Workers CPU, Durable Objects, object storage and images.

Note

On a container app, the Durable Object that runs the container itself is metered at these same rates, whether or not you add State. Its usage and State's are reported together.

Delete

Choose Delete on the card, or Delete State in the sheet. The app loses this State on the next deploy, and code that calls it gets errors.

The keys are not wiped. Every State on the app shares one store, so another State on the app, or one you add later, sees the same keys again. They are deleted only with the app.

View as Markdown llms.txt

dply/edge