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

ScopeResource
Manage KV Store with FunctionsHow to manage KV Store with Functions
KV Store API referenceAzion 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.

  • 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/namespaces

Create a namespace

Terminal window
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

Terminal window
curl -X GET "https://api.azion.com/v4/workspace/kv/namespaces" \
-H "Authorization: Token YOUR_API_TOKEN" \
-H "Accept: application/json"

Query parameters:

ParameterTypeDescription
fieldsstringComma-separated list of field names to include in the response
pageintegerPage number within the paginated result set
page_sizeintegerNumber of items per page

Retrieve a namespace

Terminal window
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 namespace
const kv = new Azion.KV();
// Using a specific namespace
const kv = new Azion.KV("my-namespace");
// Using the open method
const 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

MethodDescription
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

MethodParametersReturnsDescription
put(key, value, options?)key: string
value: string | object | ArrayBuffer | ReadableStream
options: { 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: stringPromise<void>Deletes a key

Put options

OptionTypeDescription
metadataobjectCustom metadata to associate with the key (max 1024 bytes JSON-serialized)
expirationnumberUnix timestamp (seconds) when the key expires
expirationTtlnumberTime-to-live in seconds from now

Get options

OptionTypeDescription
cacheTtlnumberTime in seconds to cache the result locally (minimum 60 seconds)

Get types

TypeReturnsDescription
"text"stringReturns the value as a UTF-8 string (default)
"json"objectParses the value as JSON
"arrayBuffer"ArrayBufferReturns raw binary data
"stream"ReadableStreamReturns a readable stream for large values

Limits

These are the default limits:

LimitValue
Per-key write rateUp to 1 write per second to the same key
Key sizeUp to 512 bytes (UTF-8)
Metadata sizeUp to 1024 bytes (JSON-serialized)
Value sizeUp to 25 MB per item
Namespace name length3-63 characters
Minimum expirationTtl60 seconds
Minimum cacheTtl60 seconds

These are the default limits for each Service Plan:

ScopeDeveloperBusinessEnterpriseMission Critical
Namespaces1000100010001000
Maximum file size200 MB500 MB2 GB2 GB
Maximum storage per account5 GB50 GB300 GB300 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.