Preview
KV Store is a distributed key-value storage service that runs across Azion’s network. It lets you persist and retrieve small pieces of data with very low latency from anywhere your users are, without managing servers.
The JavaScript API is designed to be compatible with the Cloudflare Workers KV API, making it easier to migrate existing applications to Azion’s platform.
Typical use cases include:
- Session and authentication tokens
- Feature flags and A/B testing configurations
- User preferences and personalization
- Caching API responses and computed fragments
- Rate-limit counters and idempotency keys
- Shopping cart or draft state
How it works
KV Store organizes data into namespaces, where each namespace contains an independent set of keys. Here’s how the system operates:
- Data is organized into namespaces that contain independent sets of keys.
- Each item is addressed by a key that is unique within its namespace.
- Values can be stored as text, JSON, ArrayBuffer, or ReadableStream.
- Data is replicated across global points of presence to maximize availability and read performance.
- Single-key operations are atomic per key. Multi-key transactions aren’t supported.
Implementation resources
| Scope | Resource |
|---|---|
| Manage KV Store with Functions | How to manage KV Store with Functions |
| KV Store API reference | Azion API - KV Store |
Data resilience
KV Store uses a distributed architecture with replication across Azion nodes. New writes are accepted and propagated to replicas to ensure durability and high availability. Reads are served from the closest healthy replica to minimize latency.
Namespaces
A namespace is an isolated key space. Use namespaces to segment data by application, environment, or workload.
Recommended patterns
- Use separate namespaces for production and staging environments.
- Prefix keys to model hierarchy, for example:
users/123/profile,flags/new-ui,carts/region-br/user-42. - Keep keys short and meaningful; prefer a few path-like segments over long opaque identifiers.
Naming conventions
- Names must be unique within your account.
- Names must be between 3 and 63 characters.
- Use lowercase letters, numbers, dashes, and underscores.
- Names must match the pattern:
^[a-zA-Z0-9_-]+$
Managing namespaces via API
You can manage namespaces using the Azion API. The base endpoint is:
https://api.azion.com/v4/workspace/kv/namespacesCreate a namespace
curl -X POST "https://api.azion.com/v4/workspace/kv/namespaces" \ -H "Authorization: Token YOUR_API_TOKEN" \ -H "Content-Type: application/json" \ -d '{"name": "my-namespace"}'Response (201 Created):
{ "name": "my-namespace", "created_at": "2025-01-24T14:15:22Z", "last_modified": "2025-01-24T14:15:22Z"}List namespaces
curl -X GET "https://api.azion.com/v4/workspace/kv/namespaces" \ -H "Authorization: Token YOUR_API_TOKEN" \ -H "Accept: application/json"Query parameters:
| Parameter | Type | Description |
|---|---|---|
fields | string | Comma-separated list of field names to include in the response |
page | integer | Page number within the paginated result set |
page_size | integer | Number of items per page |
Retrieve a namespace
curl -X GET "https://api.azion.com/v4/workspace/kv/namespaces/{namespace}" \ -H "Authorization: Token YOUR_API_TOKEN" \ -H "Accept: application/json"Interacting with KV Store via Functions
You can interact with KV Store directly from your functions using the
Azion.KV class. The examples below illustrate common patterns for creating,
reading, updating, and deleting data.
Initializing the KV client
You can initialize the KV client using the constructor or the open method:
// Using the default namespaceconst kv = new Azion.KV();
// Using a specific namespaceconst kv = new Azion.KV("my-namespace");
// Using the open methodconst kv = await Azion.KV.open("my-namespace");Storing data (put)
Store values in different formats:
async function handleRequest(request) { const kv = new Azion.KV(); const key = "user-session";
// Store a string value await kv.put(key, "session-data-here");
// Store a JSON object await kv.put(key, { userId: 123, role: "admin" });
// Store an ArrayBuffer const encoder = new TextEncoder(); const buffer = encoder.encode("binary data").buffer; await kv.put(key, buffer);
// Store with options (metadata and expiration) await kv.put(key, "value", { metadata: { created_by: "user-42" }, expiration: Math.floor(Date.now() / 1000) + 3600, // expires in 1 hour expirationTtl: 3600 // alternative: TTL in seconds });
return new Response("Data stored", { status: 200 });}
addEventListener("fetch", (event) => { event.respondWith(handleRequest(event.request));});Storing large data with streams
For large payloads, use ReadableStream to stream data efficiently:
async function handleRequest(request) { const kv = new Azion.KV(); const key = "large-file"; const dataSizeInKB = 500; const totalSize = dataSizeInKB * 1024; const chunkSize = 64 * 1024; // 64KB chunks
const stream = new ReadableStream({ async start(controller) { const encoder = new TextEncoder(); const numChunks = Math.ceil(totalSize / chunkSize);
for (let i = 0; i < numChunks; i++) { const currentChunkSize = Math.min(chunkSize, totalSize - i * chunkSize); const chunk = "a".repeat(currentChunkSize); controller.enqueue(encoder.encode(chunk)); } controller.close(); }, });
await kv.put(key, stream);
return new Response("Large data stored", { status: 200 });}
addEventListener("fetch", (event) => { event.respondWith(handleRequest(event.request));});Retrieving data (get)
Retrieve values in different formats:
async function handleRequest(request) { const kv = new Azion.KV(); const key = "user-session";
// Get as text (default) const textValue = await kv.get(key, "text");
// Get as JSON const jsonValue = await kv.get(key, "json"); console.log(`User ID: ${jsonValue.userId}`);
// Get as ArrayBuffer const bufferValue = await kv.get(key, "arrayBuffer"); const decoder = new TextDecoder("utf-8"); const decodedString = decoder.decode(bufferValue);
// Get as ReadableStream const streamValue = await kv.get(key, "stream");
// Handle non-existent keys (returns null) const missing = await kv.get("non-existent-key", "text"); if (missing === null) { console.log("Key not found"); }
return new Response(textValue, { status: 200 });}
addEventListener("fetch", (event) => { event.respondWith(handleRequest(event.request));});Retrieving multiple keys
Retrieve multiple values in a single operation:
async function handleRequest(request) { const kv = new Azion.KV();
const keys = ["user-1", "user-2", "user-3"]; const data = await kv.get(keys, "text");
// Returns an object with key-value pairs // Non-existent keys have null values console.log(JSON.stringify(data));
return new Response(JSON.stringify(data), { headers: { "Content-Type": "application/json" }, status: 200, });}
addEventListener("fetch", (event) => { event.respondWith(handleRequest(event.request));});Retrieving data with metadata
Use getWithMetadata to retrieve both the value and its associated metadata:
async function handleRequest(request) { const kv = new Azion.KV(); const key = "user-session";
// Store with metadata await kv.put(key, "session-value", { metadata: { created_by: "user-42", version: 1 } });
// Retrieve with metadata const data = await kv.getWithMetadata(key, "text"); console.log(`Value: ${data.value}`); console.log(`Metadata: ${JSON.stringify(data.metadata)}`);
// Works with multiple keys too const keys = ["key1", "key2"]; const multiData = await kv.getWithMetadata(keys, "text");
return new Response(JSON.stringify(data), { headers: { "Content-Type": "application/json" }, status: 200, });}
addEventListener("fetch", (event) => { event.respondWith(handleRequest(event.request));});Reading streams
Process streamed data incrementally:
async function handleRequest(request) { const kv = new Azion.KV(); const key = "large-file";
const stream = await kv.get(key, "stream");
if (stream instanceof ReadableStream) { const decoder = new TextDecoder(); let text = "";
for await (const chunk of stream) { text += decoder.decode(chunk, { stream: true }); } text += decoder.decode();
console.log(`Retrieved ${text.length} characters`); }
// Or return the stream directly in a response return new Response(stream);}
addEventListener("fetch", (event) => { event.respondWith(handleRequest(event.request));});Deleting data
Remove a key from the store:
async function handleRequest(request) { const kv = new Azion.KV(); const key = "user-session";
await kv.delete(key);
return new Response("Key deleted", { status: 200 });}
addEventListener("fetch", (event) => { event.respondWith(handleRequest(event.request));});Working with Unicode and special characters
KV Store supports UTF-8 and UTF-16 encoded keys and values:
async function handleRequest(request) { const kv = new Azion.KV();
// UTF-16 key (Chinese: "My Key") const key = "我的钥匙";
// UTF-16 value with emojis const value = "🌍🌎🌏 Hello World 你好 世界";
await kv.put(key, value);
const retrieved = await kv.get(key, "text"); console.log(`Key: ${key}, Value: ${retrieved}`);
return new Response("OK", { status: 200 });}
addEventListener("fetch", (event) => { event.respondWith(handleRequest(event.request));});Deleting namespaces
Delete an entire namespace programmatically:
async function handleRequest(request) { const namespaceName = "my-namespace";
// Delete the namespace await Azion.KV.delete(namespaceName);
return new Response("Namespace deleted", { status: 200 });}
addEventListener("fetch", (event) => { event.respondWith(handleRequest(event.request));});Methods reference
The Azion.KV class provides the following methods:
Constructor and initialization
| Method | Description |
|---|---|
new Azion.KV() | Creates a KV instance using the default namespace |
new Azion.KV(name) | Creates a KV instance for the specified namespace |
Azion.KV.open(name) | Opens a namespace asynchronously |
Azion.KV.delete(name) | Deletes a namespace |
Data operations
| Method | Parameters | Returns | Description |
|---|---|---|---|
put(key, value, options?) | key: stringvalue: string | object | ArrayBuffer | ReadableStreamoptions: { metadata?, expiration?, expirationTtl? } | Promise<void> | Stores a value |
get(key, type?, options?) | key: string | string[]type: “text” | “json” | “arrayBuffer” | “stream”options: { cacheTtl? } | Promise<value | null> | Retrieves a value |
getWithMetadata(key, type?, options?) | key: string | string[]type: “text” | “json” | “arrayBuffer” | “stream”options: { cacheTtl? } | Promise<{'{value, metadata}'}> | Retrieves value with metadata |
delete(key) | key: string | Promise<void> | Deletes a key |
Put options
| Option | Type | Description |
|---|---|---|
metadata | object | Custom metadata to associate with the key (max 1024 bytes JSON-serialized) |
expiration | number | Unix timestamp (seconds) when the key expires |
expirationTtl | number | Time-to-live in seconds from now |
Get options
| Option | Type | Description |
|---|---|---|
cacheTtl | number | Time in seconds to cache the result locally (minimum 60 seconds) |
Get types
| Type | Returns | Description |
|---|---|---|
"text" | string | Returns the value as a UTF-8 string (default) |
"json" | object | Parses the value as JSON |
"arrayBuffer" | ArrayBuffer | Returns raw binary data |
"stream" | ReadableStream | Returns a readable stream for large values |
Limits
These are the default limits:
| Limit | Value |
|---|---|
| Per-key write rate | Up to 1 write per second to the same key |
| Key size | Up to 512 bytes (UTF-8) |
| Metadata size | Up to 1024 bytes (JSON-serialized) |
| Value size | Up to 25 MB per item |
| Namespace name length | 3-63 characters |
Minimum expirationTtl | 60 seconds |
Minimum cacheTtl | 60 seconds |
These are the default limits for each Service Plan:
| Scope | Developer | Business | Enterprise | Mission Critical |
|---|---|---|---|---|
| Namespaces | 1000 | 1000 | 1000 | 1000 |
| Maximum file size | 200 MB | 500 MB | 2 GB | 2 GB |
| Maximum storage per account | 5 GB | 50 GB | 300 GB | 300 GB |
Limitations
The following operations are not supported:
- List keys: There is no
list()method to enumerate keys within a namespace. Design your application to track keys externally if needed. - Multi-key transactions: Operations are atomic per key, but there is no support for transactions spanning multiple keys.
- Namespace management from functions: Namespaces must be created and managed via the Azion API or Console, not from within functions.