> ## 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.

# Use a bucket from an Edge Function

> Bind a Cloud Storage bucket to a Telnyx Edge Function and read, write, and list objects through a pre-authenticated env binding. No S3 keys in your code.

A **Cloud Storage binding** gives a [Telnyx Edge Function](/docs/edge-compute/overview) a pre-authenticated handle to one of your buckets. You declare the binding in `func.toml`; the runtime resolves it to `env.<BINDING>` and injects the credential — your code holds **no access key or secret key**, and nothing sensitive appears in your bundle or logs.

The handle is a small, focused surface — `get`, `put`, `head`, `delete`, `list`. It is the in-function counterpart of the [S3-compatible API](/docs/cloud-storage/quick-start): same buckets, same objects, reached from inside a function instead of over HTTP.

<Note>
  Cloud Storage bindings are **TypeScript-only** — the typed `env` handle comes from the `@telnyx/edge-runtime` SDK (**≥ 0.3.0**). Other runtimes (JS, Go, Python) don't get a typed binding.
</Note>

This guide builds one complete function end to end: a small **file API** backed by a bucket — `PUT`, `GET`, and `DELETE` an object by key, and list objects by prefix.

## 1. Scaffold the function

Create a TypeScript function. You also need an **existing** bucket for it to use — the binding points at a bucket, it doesn't create one. If you don't have one, create it first via the [Mission Control portal](https://portal.telnyx.com/#/storage/buckets), the [AWS CLI, or an S3 SDK](/docs/cloud-storage/quick-start).

```bash theme={null}
telnyx-edge new-func --language ts --name file-api
cd file-api
```

## 2. Declare the binding

Add a `[storage.cloudstorage.<name>]` block to the generated `func.toml`. The block key is a name **you choose** — it becomes the property on `env`. Here it's `ASSETS`, reached as `env.ASSETS`:

```toml theme={null}
[edge_compute]
func_id   = "…"           # filled in by new-func
func_name = "file-api"

[storage.cloudstorage.ASSETS]
bucket_name = "my-assets"   # an existing bucket
region      = "us-east-1"   # us-central-1 | us-east-1 | us-west-1 | eu-central-1
```

Declare more than one bucket by adding more blocks — each `[storage.cloudstorage.<name>]` becomes `env.<name>`.

## 3. Install and generate types

`new-func` already lists `@telnyx/edge-runtime` and `@aws-sdk/client-s3` in `package.json`. Install them, then generate the typed `env`:

```bash theme={null}
npm install
telnyx-edge types
```

`telnyx-edge types` writes `telnyx-env.d.ts`, which types `env.ASSETS` as `CloudStorageBucket` so the calls below type-check.

## 4. Write the function

Replace `index.ts` with the complete file API. Every bucket operation — `list`, `put`, `get`, `delete` — goes through `env.ASSETS`; there are no credentials anywhere in the code.

```ts theme={null}
// index.ts
import * as http from "node:http";
import { env } from "@telnyx/edge-runtime";

// A small file API backed by a Cloud Storage bucket binding (env.ASSETS):
//   PUT    /files/<key>   store the request body as an object
//   GET    /files/<key>   download an object
//   GET    /files         list objects (optional ?prefix=)
//   DELETE /files/<key>   delete an object
const bucket = env.ASSETS;

function sendJson(res: http.ServerResponse, status: number, body: unknown) {
  res.writeHead(status, { "content-type": "application/json" });
  res.end(JSON.stringify(body));
}

async function readBody(req: http.IncomingMessage): Promise<Buffer> {
  const chunks: Buffer[] = [];
  for await (const chunk of req) chunks.push(chunk as Buffer);
  return Buffer.concat(chunks);
}

const server = http.createServer(async (req, res) => {
  // Health probes must stay unauthenticated
  if (req.url === "/health" || req.url?.startsWith("/health/")) {
    res.writeHead(200);
    res.end();
    return;
  }

  const url = new URL(req.url ?? "/", "http://localhost");
  const isCollection = url.pathname === "/files" || url.pathname === "/files/";
  const key = decodeURIComponent(url.pathname.replace(/^\/files\//, ""));

  try {
    // List: GET /files?prefix=
    if (req.method === "GET" && isCollection) {
      const { objects, truncated, cursor } = await bucket.list({
        prefix: url.searchParams.get("prefix") ?? undefined,
        limit: 100,
      });
      return sendJson(res, 200, {
        objects: objects.map((o) => ({ key: o.key, size: o.size, uploaded: o.uploaded })),
        truncated,
        cursor,
      });
    }

    // Upload: PUT /files/<key>
    if (req.method === "PUT" && key) {
      const body = await readBody(req);
      const put = await bucket.put(key, new Uint8Array(body), {
        httpMetadata: { contentType: req.headers["content-type"] ?? "application/octet-stream" },
      });
      return sendJson(res, 200, { key: put?.key, etag: put?.etag });
    }

    // Download: GET /files/<key>
    if (req.method === "GET" && key) {
      const obj = await bucket.get(key);
      if (obj === null) return sendJson(res, 404, { error: "not found" });
      const bytes = Buffer.from(await obj.arrayBuffer());
      res.writeHead(200, {
        "content-type": obj.httpMetadata?.contentType ?? "application/octet-stream",
        "content-length": String(bytes.byteLength),
      });
      res.end(bytes);
      return;
    }

    // Delete: DELETE /files/<key>  (idempotent)
    if (req.method === "DELETE" && key) {
      await bucket.delete(key);
      res.writeHead(204);
      res.end();
      return;
    }

    sendJson(res, 405, { error: "method not allowed" });
  } catch (err: any) {
    sendJson(res, 500, { error: err?.message ?? "internal error" });
  }
});

server.listen(Number(process.env.PORT ?? 8080), () => console.log("file-api up"));
```

## 5. Ship it

```bash theme={null}
telnyx-edge ship
```

When the deploy finishes, get the function's invoke URL:

```bash theme={null}
telnyx-edge list   # shows STATUS and the INVOKE URL for file-api
```

## 6. Try it

With `URL` set to your function's invoke URL:

```bash theme={null}
# Upload an object
curl -X PUT "$URL/files/hello.txt" -H "content-type: text/plain" --data "hello from the edge"
# → {"key":"hello.txt","etag":"11c9dad6fdb6ae2efe36b9c7aef39031"}

# Download it back
curl "$URL/files/hello.txt"
# → hello from the edge

# List objects
curl "$URL/files"
# → {"objects":[{"key":"hello.txt","size":19,"uploaded":"2026-07-04T…Z"}],"truncated":false}

# Delete it (idempotent — 204 whether or not it existed)
curl -i -X DELETE "$URL/files/hello.txt"
# → HTTP/1.1 204 No Content
```

## Related

* [Binding API reference](/docs/cloud-storage/bindings/reference) — every method, option, and object type
* [Bindings overview](/docs/edge-compute/runtime/bindings) — how bindings work across Telnyx API, Secrets, KV, and Cloud Storage
* [S3-compatible quick start](/docs/cloud-storage/quick-start) — the same buckets over HTTP
