Skip to content
dply/edge

Resources

Key-value (KV)

A globally replicated key-value store for caches, sessions, and settings, billed per read, write, and GB stored.

A key-value store keeps short values under string keys, replicated across Cloudflare's network so reads are fast wherever your app runs. Use it for cache entries, feature flags, rendered fragments, and other data that is read far more often than it is written. It runs on Cloudflare Workers KV.

KV is eventually consistent: a write can take a short time to be seen everywhere. For a counter or a lock that must be exact, use State. For a Redis-compatible cache with atomic commands, use Valkey (Redis).

Note

You need a card on file to create a key-value store. Every plan takes a card at Checkout, so an organization with a plan has one. Without a card, the Create new form shows "Add a card before starting a key-value store. Reads, writes, and storage are billed to that card." and a Billing button.

Create a store

  1. Open your app. On Overview, choose Add resource, then Key-value store.
  2. Keep Create new selected and enter a Name, such as cache.
  3. Choose Create, then redeploy the app.

To use a store your organization already has, choose Attach existing, pick it under Existing Store, and choose Attach.

Key-value stores are available to container apps and to SSR and hybrid apps. See which kinds each runtime supports.

Use it from your code

Laravel (container apps)

On the next deploy dply sets DPLY_KV_HOST and registers a cache store named after the resource, in lower case. Your app's default cache does not change. To make this store the default, turn on Default cache in the store's Settings: the next deploy then sets CACHE_STORE to it (unless Redis is attached, which stays the default).

php
use Illuminate\Support\Facades\Cache;

Cache::store('cache')->put('session', 'hello', 3600);
Cache::store('cache')->get('session');
Cache::store('cache')->forget('session');

// Only when it is the default cache (Settings, Default cache):
Cache::put('greeting', 'hello', 3600);

A value expires only when its TTL is at least 60 seconds. A shorter TTL stores the value with no expiry.

Cache::many() reads up to 100 keys in one request, and Cache::flush() removes every key in the store.

Warning

Cache::increment() and Cache::decrement() throw an error on a key-value store, and so do Cache::lock() and anything built on them, such as rate limiting and WithoutOverlapping. The store is eventually consistent, so it can't count or lock safely. Attach Valkey (Redis) or use State for those.

Important

The dply/laravel package provides the cache store. dply adds it on the next deploy when the app does not require it, but only when dply builds the image. If your repository has its own Dockerfile, run composer require dply/laravel.

Rails (container apps)

Add gem "dply-rails". The next deploy sets DPLY_KV_HOST, and Dply::Rails::Kv reads and writes the store:

ruby
Dply::Rails::Kv.write('session', 'hello', expires_in: 3600)
Dply::Rails::Kv.read('session')
Dply::Rails::Kv.delete('session')

Turn on Default cache in the store's Settings and Rails.cache uses it too, unless Redis is attached. read_multi reads up to 100 keys in one request, clear removes every key, and increment / decrement raise, as in Laravel.

Any language (container apps)

The store answers at its private host. Copy the exact host from the store's sheet. It looks like dply.my-app.cache.internal. The host belongs to this app: only this app's containers can reach it, and it needs no credentials. The same store attached to another app has a different host there. To reach a store from anywhere else, see From outside the app.

Request Result
GET http://{host}/ Lists up to 1,000 keys as {"keys": [...], "cursor": ...}. Add ?prefix= to filter, and pass ?cursor= from the previous page for the next one. cursor is null on the last page. Add &detail=1 to get {"name", "expiration", "metadata"} for each key instead of just its name.
POST http://{host}/ Reads up to 100 keys at once. Send {"keys": ["a", "b"]} and get {"values": {"a": "…", "b": null}} back. A missing key is null.
GET http://{host}/{key} The value as text. A missing key is a 404. The key's metadata, if any, comes back in an x-dply-metadata header.
PUT http://{host}/{key} Stores the request body.
DELETE http://{host}/{key} Removes the key.

A PUT takes these optional headers:

Header Value
x-dply-ttl Seconds until the key expires, 60 or more. A shorter value stores it with no expiry.
x-dply-expires-at A unix time at least 60 seconds ahead, when the key expires. Send this or x-dply-ttl, not both.
x-dply-metadata JSON of up to 1024 bytes kept alongside the value. It comes back when you read the key or list with detail=1.

A GET for a key can send x-dply-cache-ttl: {seconds} (30 or more) to let each location cache the value for that long. Reads get faster, but a change can take that long to show. A header that breaks these rules gets a 400 that says which rule.

js
const host = 'http://dply.my-app.cache.internal';

await fetch(`${host}/session`, {
  method: 'PUT',
  body: 'hello',
  headers: { 'x-dply-ttl': '3600' },
});

const res = await fetch(`${host}/session`);
const value = res.status === 404 ? null : await res.text();

await fetch(`${host}/session`, { method: 'DELETE' });

Worker apps (SSR and hybrid)

The store is a KV namespace binding at env.NAME, where NAME is the resource name in upper case.

js
export default {
  async fetch(request, env) {
    await env.CACHE.put('session', 'hello', { expirationTtl: 3600 });
    const value = await env.CACHE.get('session');
    await env.CACHE.delete('session');
    return new Response(value ?? 'missing');
  },
};

Browse and edit keys

Choose the store's card on the resource map. The sheet has these tabs:

  • Overview: reads, writes, storage and this month's cost so far, and how the app reaches the store.
  • Connect: Laravel, Rails, and HTTP samples with this store's names filled in, and Test from the app, which has the running app write, read and forget a key through Cache::store() (Laravel apps with dply/laravel).
  • Keys: lists keys, with each key's expiry and metadata under its name. Use Keys starting with… to filter and Load more for the next page. Choose a key to load it and its value into Try a key, change the Value, and choose Write to save it. Under Try a key you can also enter a Key, a Value, and optionally Expire after (seconds) (at least 60), then choose Write, Read, or Delete key. This writes the store directly and does not call your app. Demo values must be under 8 KB, and a larger value isn't loaded into the field.
  • Settings: rename the store's binding (the app uses the new host and name after the next deploy; the store keeps its data), make it the Default cache, sleep it, or detach or delete it.

Delete keys by prefix

  1. On Keys, search for a prefix, such as user:.
  2. Choose Delete keys starting with user:. The sheet shows how many keys match, or "1,000+" when there are more than one page.
  3. Type the prefix to confirm, then choose Delete keys.

The keys are deleted in the background, a page of 1,000 at a time, and the sheet shows how many have gone so far. Only one delete runs on a store at a time. It cannot be undone. To remove every key, delete the store instead: an empty prefix is not allowed.

From outside the app

The private host only works inside the app. To seed, inspect or fix keys from your machine or CI, use the HTTP API or the CLI with an API token:

bash
dply kv list
dply kv keys cache --prefix user:
dply kv get cache user:1
dply kv put cache user:1 hello --ttl 3600 --metadata '{"plan":"pro"}'
dply kv delete cache user:1

Reading needs edge.read, and writing or deleting needs edge.write. Everything except listing stores also needs you to be an organization owner or admin. These calls share 60 requests per minute per organization across all its tokens. Use them for admin work, not app traffic.

Sleep

Choose Sleep on the card to take the store off the app. On the next deploy the app loses the address and cannot read or write it. The store keeps its data, and that data is still billed as storage; operations until the next deploy are billed too. Delete the store to stop all charges. Choose Wake and redeploy to bring it back.

Pricing

Key-value stores are billed per operation and for storage, every unit from the first:

Meter Price Unit
Reads $0.65 per million
Writes, deletes and lists $6.50 per million
Storage $0.65 per GB-month

Storage is the store's largest size during the month. Usage is collected hourly from Cloudflare, and each day is collected again in full the next morning. A sleeping store is billed for what it uses: its storage, and any operations before the next deploy takes it off the app. This bills at the invoice line SQL, queues and key-value.

Delete a store

Choose Delete on the card, or Delete store under Settings. This removes the store and every key in it. It cannot be undone.

If the app's live deploy still uses the store, dply detaches it now and deletes it after your next deploy. If another app still uses it, it is kept, and billed, until no app does: redeploy this app, detach or delete it on the other app too, and it is deleted after the next deploy. See Detach or delete. To remove the store from this app but keep it, choose Detach instead.

View as Markdown llms.txt

dply/edge