---
name: azion-manage-key-value-data-from-a-function
description: >-
  Store, read, and delete keys in a KV Store namespace from a function, with metadata, binary values, streams, and several namespaces.
---

# Manage key-value data from a function

You store, read, and delete keys in a [KV Store](/en/documentation/platform/kv-store/) namespace from a [function](/en/documentation/platform/functions/), with the `Azion.KV` client. Every specimen on this page opens the namespace with `await Azion.KV.open(name)`: the constructor is private, and there is no default namespace.

Creating the namespace itself is an Azion API task, and a function cannot do it. For more information, refer to [Namespaces](/en/documentation/platform/kv-store/namespaces/).

---

## Prerequisites

- A function you can edit. To create one, refer to [Functions quickstart](/en/documentation/platform/functions/quickstart/).
- An application the function is instantiated on. To bind the function to one, refer to [Instantiate a function on an application](/en/documentation/guides/application-development/getting-started/instantiate-functions/).
- A KV Store namespace, created through the Azion API. To create one, refer to [Namespaces](/en/documentation/platform/kv-store/namespaces/).

---

## Create the function

The client is a global of Azion Runtime, so the code reaches it with no import line and no credential. To create the function that holds one of the specimens below:

1. **Open the Functions page**

   Access [Azion Console](https://console.azion.com/) > **Products Menu** > **Libraries** > **Functions**.

2. **Select + Function**

3. **Name the function**

   Enter a name for the function. For example: `kv-store-handler`.

4. **Paste the code in the Code tab**

   In the **Code** tab, replace the placeholder code with one of the specimens on this page, and replace `my-namespace` with your namespace.

5. **Select Save**

The function is saved. It answers a request once a [Rules Engine](/en/documentation/platform/applications/rules-engine/) rule runs its instance on the application.

---

## Store a value

`put` creates a key and replaces the value of a key that already exists, so one call covers both. A string is stored as it is, an object is serialized to JSON, and a third argument carries `metadata` and an expiry. To store three keys in one invocation:

```javascript
export default {
  async fetch(request, env, ctx) {
    const kv = await Azion.KV.open('my-namespace');

    await kv.put('greeting', 'Hello, World!');

    // An object is serialized to JSON, so it needs no stringify call.
    await kv.put('user-preferences', {
      theme: 'dark',
      language: 'en',
      notifications: true,
    });

    // metadata travels beside the value; expirationTtl expires the key after 3600 seconds.
    await kv.put('session-token', 'abc123xyz', {
      metadata: { userId: 'user-42' },
      expirationTtl: 3600,
    });

    return new Response('Values stored', { status: 201 });
  },
};
```

The handler writes three keys and answers `201`. For every option `put` accepts, refer to [KV client](/en/documentation/devtools/runtime/api-reference/kv-store/).

---

## Read a value

`get` reads one key and returns the value in the type its second argument names, which defaults to `text`. A key the namespace does not hold returns `null`, so the handler checks for it rather than catching an error. To read a string, an object, and a key that may be absent:

```javascript
export default {
  async fetch(request, env, ctx) {
    const kv = await Azion.KV.open('my-namespace');

    const greeting = await kv.get('greeting', 'text');
    const preferences = await kv.get('user-preferences', 'json');

    if (greeting === null) {
      return new Response('Key not found', { status: 404 });
    }

    return new Response(JSON.stringify({ greeting, preferences }), {
      headers: { 'Content-Type': 'application/json' },
      status: 200,
    });
  },
};
```

The handler answers `404` when `greeting` is absent, and otherwise returns both values in one JSON body. A key the namespace does not hold reaches the `null` branch rather than throwing, which is why the check is an equality test and not a `try`.

---

## Read several keys at once

An array as the first argument of `get` reads several keys in one call and returns a plain object keyed by key name. A key the namespace does not hold appears in that object with the value `null`. To write three keys and read four:

```javascript
export default {
  async fetch(request, env, ctx) {
    const kv = await Azion.KV.open('my-namespace');

    await kv.put('user-1', 'Alice');
    await kv.put('user-2', 'Bob');
    await kv.put('user-3', 'Charlie');

    const users = await kv.get(['user-1', 'user-2', 'user-3', 'user-4'], 'text');

    // { "user-1": "Alice", "user-2": "Bob", "user-3": "Charlie", "user-4": null }

    return new Response(JSON.stringify(users), {
      headers: { 'Content-Type': 'application/json' },
      status: 200,
    });
  },
};
```

The three writes address three different keys, and `user-4` comes back as `null` because the namespace holds no key by that name. The array form accepts only `text` and `json`: any other return type throws `INVALID_MULTIPLE_GET_RETURN_TYPE`.

---

## Read a value with its metadata

`getWithMetadata` returns an object carrying `value` and `metadata`, so one call reads both. The metadata is whatever the write passed in the `metadata` option. To store a key with metadata and read the pair back:

```javascript
export default {
  async fetch(request, env, ctx) {
    const kv = await Azion.KV.open('my-namespace');

    await kv.put('session', 'active-session-data', {
      metadata: {
        createdAt: new Date().toISOString(),
        userId: 'user-42',
      },
    });

    const result = await kv.getWithMetadata('session', 'text');

    return new Response(JSON.stringify(result), {
      headers: { 'Content-Type': 'application/json' },
      status: 200,
    });
  },
};
```

The handler returns `result.value` and `result.metadata` in the same response body.

---

## Delete a key

`delete` removes one key and takes no array form, so several keys are removed with one call each. A read before the delete tells the handler which of the keys the namespace holds. To delete two keys and answer `404` when it holds neither:

```javascript
export default {
  async fetch(request, env, ctx) {
    const kv = await Azion.KV.open('my-namespace');
    const keys = ['session-token', 'session-token-2'];

    try {
      const found = await kv.get(keys, 'text');
      const present = keys.filter((key) => found[key] !== null);

      if (present.length === 0) {
        return new Response('Key not found', { status: 404 });
      }

      await Promise.all(present.map((key) => kv.delete(key)));

      return new Response(`Deleted ${present.length} keys`, { status: 200 });
    } catch (error) {
      return new Response(error.message, { status: 500 });
    }
  },
};
```

The handler answers `200` with the number of keys it removed, `404` when the namespace holds neither key, and `500` carrying the message of a call that rejected.

---

## Store and read binary data

`put` accepts an `ArrayBuffer`, and `arrayBuffer` as the return type of `get` reads the bytes back. `TextEncoder` and `TextDecoder` convert between a string and that buffer. To store a string as bytes and decode it on the way out:

```javascript
export default {
  async fetch(request, env, ctx) {
    const kv = await Azion.KV.open('my-namespace');

    const binaryData = new TextEncoder().encode('Binary content here').buffer;
    await kv.put('binary-key', binaryData);

    const retrieved = await kv.get('binary-key', 'arrayBuffer');
    const decoded = new TextDecoder('utf-8').decode(retrieved);

    return new Response(decoded, {
      headers: { 'Content-Type': 'text/plain' },
      status: 200,
    });
  },
};
```

The handler returns the decoded string, which is the text the write encoded.

---

## Read a value as a stream

`stream` as the return type returns a `ReadableStream` instead of the assembled value, so a large value reaches the response without the handler holding it. An absent key still returns `null`, so the handler checks before it builds the response. To pass a value straight into the response body:

```javascript
export default {
  async fetch(request, env, ctx) {
    const kv = await Azion.KV.open('my-namespace');

    const stream = await kv.get('large-content', 'stream');

    if (stream === null) {
      return new Response('Key not found', { status: 404 });
    }

    return new Response(stream, {
      headers: { 'Content-Type': 'application/octet-stream' },
      status: 200,
    });
  },
};
```

The handler answers `404` when the key is absent, and otherwise streams the value as the response body.

---

## Use a specific namespace

`Azion.KV.open` binds a client to the namespace it names, so a function that reaches two namespaces opens two clients. The same key name in two namespaces addresses two separate values. To write and read one key in each of two namespaces:

```javascript
export default {
  async fetch(request, env, ctx) {
    const productionKv = await Azion.KV.open('production-data');
    const stagingKv = await Azion.KV.open('staging-data');

    await productionKv.put('config', 'production-value');
    await stagingKv.put('config', 'staging-value');

    const production = await productionKv.get('config', 'text');
    const staging = await stagingKv.get('config', 'text');

    return new Response(JSON.stringify({ production, staging }), {
      headers: { 'Content-Type': 'application/json' },
      status: 200,
    });
  },
};
```

Each `config` key belongs to its own namespace, so neither write overwrites the other. A name that belongs to no namespace on the account throws `NotFound: KV namespace "staging-data" does not exist`.

---

## Next steps

- [KV client](/en/documentation/devtools/runtime/api-reference/kv-store.md): Every method, option, return type, and error of the Azion.KV client.
- [Namespaces](/en/documentation/platform/kv-store/namespaces.md): Create, list, and retrieve a namespace through the Azion API.
- [Best practices](/en/documentation/platform/kv-store/best-practices.md): The recommendations to follow before the first key goes in.
- [Troubleshooting](/en/documentation/platform/kv-store/troubleshooting.md): What a KV Store call reports when it fails, and what to do about it.
