---
title: KV
description: Read, write, list, and delete keys in a Cloudflare Workers KV namespace from Go.
---

[Workers KV](https://developers.cloudflare.com/kv/) is a global, low-latency key-value store. The `cloudflare/kv` package wraps a bound KV namespace.

## Declare the binding

```toml
[[kv_namespaces]]
binding = "COUNTER"
id = "<your namespace id>"
```

## Connect to a namespace

`kv.NewNamespace` resolves the binding by name and returns an error when no binding with that name exists.

```go
counterKV, err := kv.NewNamespace("COUNTER")
if err != nil {
	// no binding named COUNTER
}
```

## Get a value

`GetString` reads a value as a string and `GetReader` streams it as an `io.ReadCloser` — the caller is responsible for closing it. Both accept `*kv.GetOptions`; pass `nil` for defaults. If the key doesn't exist, both return `kv.ErrNotFound`; check for it with `errors.Is`.

```go
value, err := counterKV.GetString("count", nil)
if errors.Is(err, kv.ErrNotFound) {
	// no value for "count"
} else if err != nil {
	return err
}

r, err := counterKV.GetReader("image", &kv.GetOptions{
	CacheTTL: 3600, // cache the value at the edge for this many seconds
})
if err != nil {
	return err
}
defer r.Close()
```

### Get multiple values

`GetStrings` reads up to 100 keys in a single call. A missing key is simply omitted from the result map instead of causing an error.

```go
values, err := counterKV.GetStrings([]string{"count", "other"}, nil)
if err != nil {
	return err
}
for key, value := range values {
	fmt.Println(key, value)
}
```

## Put a value

`PutString` stores a string and `PutReader` stores the contents of an `io.Reader`. `PutReader` copies the full body into memory.

```go
err := counterKV.PutString("count", "42", nil)

err = counterKV.PutString("session", token, &kv.PutOptions{
	ExpirationTTL: 3600, // expire after this many seconds
	// Expiration: 1735689600, // or an absolute Unix timestamp
})
```

## Delete a key

```go
err := counterKV.Delete("count")
```

## List keys

`List` returns up to `Limit` keys, optionally filtered by `Prefix`. When `ListComplete` is false, pass the returned `Cursor` back to fetch the next page.

```go
res, err := counterKV.List(&kv.ListOptions{
	Prefix: "user:",
	Limit:  100,
})
if err != nil {
	return err
}
for _, key := range res.Keys {
	fmt.Println(key.Name, key.Expiration)
}
if !res.ListComplete {
	nextCursor := res.Cursor
	_ = nextCursor // pass as ListOptions.Cursor for the next page
}
```

:::note
The `PutOptions.Metadata` field and `ListKey.Metadata` are not implemented yet. See the [feature support table](/#feature-support).
:::

## Example project

[_examples/kv-counter](https://github.com/syumai/workers-go/tree/main/_examples/kv-counter) implements a page-view counter backed by KV.
