> ## Documentation Index
> Fetch the complete documentation index at: https://developers.telnyx.com/llms.txt
> Use this file to discover all available pages before exploring further.

# CloudStorageBucket

> The Cloud Storage binding surface exported from @telnyx/edge-runtime — get, put, head, delete, and list on the env binding, with option and result types and their runtime behavior.

`env.<BINDING>` (a `CloudStorageBucket`) is the in-function handle to a Cloud Storage bucket, declared with a [`[storage.cloudstorage.<name>]` block](/docs/cloud-storage/bindings). It's a pre-authenticated wrapper over the bucket — the runtime injects the credential, so your code holds **no S3 access key or secret key**.

```ts theme={null}
interface CloudStorageBucket {
  get(key: string): Promise<CloudStorageObjectBody | null>;
  put(
    key: string,
    body: ReadableStream | Uint8Array | string,
    options?: CloudStoragePutOptions,
  ): Promise<CloudStorageObject | null>;
  head(key: string): Promise<CloudStorageObject | null>;
  delete(key: string): Promise<void>;
  list(options?: CloudStorageListOptions): Promise<CloudStorageListResult>;
}
```

Key behaviors:

* **Missing keys read as `null`** — `get` and `head` resolve to `null` for a key that doesn't exist, not an error.
* **`delete` is idempotent** — deleting a missing key succeeds and resolves to `void`.
* **`put` returns partial metadata** — the resolved object carries `key`, `etag`, `httpEtag`, and any metadata you set, but not `size` or `uploaded`. Use `head` to read those after a write.
* **Custom metadata keys are lower-cased on read** — `x-amz-meta-*` header names are stored lower-cased, so `customMetadata` keys come back lower-cased (`uploadedBy` → `uploadedby`).
* **No multipart on the binding** — the surface is `get`/`put`/`head`/`delete`/`list`. For very large objects, use [multipart upload](/docs/cloud-storage/multipart-upload) or a [presigned URL](/docs/cloud-storage/presigned-urls) over the S3 API.

## `get(key)`

Read an object and its body. Returns `null` if the key does not exist.

```ts theme={null}
const obj = await env.MY_BUCKET.get("uploads/logo.png");
if (obj === null) {
  // not found
} else {
  const bytes = await obj.arrayBuffer();   // consume the body once
  // obj.key, obj.size, obj.etag, obj.httpMetadata, obj.customMetadata, …
}
```

The result is a [`CloudStorageObjectBody`](#object-types) — a `CloudStorageObject` plus the body and one-shot readers `arrayBuffer()`, `text()`, `json()`, and `blob()`.

## `put(key, body, options?)`

Write an object. `body` is a `string`, `Uint8Array`, or `ReadableStream`. Resolves to a [`CloudStorageObject`](#object-types) describing the write.

```ts theme={null}
interface CloudStoragePutOptions {
  httpMetadata?: CloudStorageHTTPMetadata;    // Content-Type, Cache-Control, …
  customMetadata?: Record<string, string>;    // x-amz-meta-* — keys lower-cased on read
}
```

```ts theme={null}
await env.MY_BUCKET.put("uploads/logo.png", bytes, {
  httpMetadata: { contentType: "image/png", cacheControl: "max-age=3600" },
  customMetadata: { uploadedby: "alice" },
});

// Strings and streams work too
await env.MY_BUCKET.put("notes/today.txt", "hello world");
```

The resolved object carries `key`, `etag` (unquoted MD5 for a single-part write), `httpEtag` (the quoted, header-ready form), and the metadata you set. `size` and `uploaded` are **not** populated on the `put` result — read them back with `head` if you need them.

## `head(key)`

Read an object's metadata without its body. Returns `null` if the key does not exist.

```ts theme={null}
const meta = await env.MY_BUCKET.head("uploads/logo.png");
// meta?.size, meta?.uploaded (Date), meta?.httpMetadata, meta?.customMetadata
```

Unlike `put`, `head` returns the full [`CloudStorageObject`](#object-types) including `size` and `uploaded`.

## `delete(key)`

Remove an object. Idempotent — deleting a missing key resolves normally.

```ts theme={null}
await env.MY_BUCKET.delete("uploads/logo.png");
```

## `list(options?)`

Enumerate objects (metadata only — `list` does not return bodies).

```ts theme={null}
interface CloudStorageListOptions {
  prefix?: string;
  limit?: number;
  cursor?: string;   // from a previous result's `cursor`
}

interface CloudStorageListResult {
  objects: CloudStorageObject[];
  truncated: boolean;
  cursor?: string;   // present when truncated is true
}
```

```ts theme={null}
let cursor: string | undefined;
do {
  const page = await env.MY_BUCKET.list({ prefix: "uploads/", limit: 100, cursor });
  for (const obj of page.objects) {
    // obj.key, obj.size, obj.etag, …
  }
  cursor = page.truncated ? page.cursor : undefined;
} while (cursor);
```

When `truncated` is `true`, pass the returned `cursor` back in `list({ cursor })` to fetch the next page.

## Object types

```ts theme={null}
interface CloudStorageObject {
  key: string;
  size?: number;
  etag?: string;                              // unquoted (e.g. "5eb63bbbe0…")
  httpEtag?: string;                          // quoted, header-ready (e.g. "\"5eb63bbbe0…\"")
  uploaded?: Date;
  httpMetadata?: CloudStorageHTTPMetadata;
  customMetadata?: Record<string, string>;    // keys lower-cased on read
}

interface CloudStorageObjectBody extends CloudStorageObject {
  body: ReadableStream;
  arrayBuffer(): Promise<ArrayBuffer>;
  text(): Promise<string>;
  json(): Promise<unknown>;
  blob(): Promise<Blob>;
}

interface CloudStorageHTTPMetadata {
  contentType?: string;
  cacheControl?: string;
  contentLanguage?: string;
  contentDisposition?: string;
  contentEncoding?: string;
  cacheExpiry?: Date;
}
```

`head` and each `list` entry populate `size` and `uploaded`; `put`'s result does not. The body readers on `CloudStorageObjectBody` consume the stream once — call a single one per `get`.

## Related

* [Use a bucket from an Edge Function](/docs/cloud-storage/bindings) — declare the binding and get started
* [Bindings overview](/docs/edge-compute/runtime/bindings) — bindings across Telnyx API, Secrets, KV, and Cloud Storage
* [S3-compatible API reference](/docs/cloud-storage/api-endpoints) — the same buckets over HTTP
