Skip to main content
env.<BINDING> (a CloudStorageBucket) is the in-function handle to a Cloud Storage bucket, declared with a [storage.cloudstorage.<name>] block. 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.
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 nullget 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 readx-amz-meta-* header names are stored lower-cased, so customMetadata keys come back lower-cased (uploadedByuploadedby).
  • No multipart on the binding — the surface is get/put/head/delete/list. For very large objects, use multipart upload or a presigned URL over the S3 API.

get(key)

Read an object and its body. Returns null if the key does not exist.
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 — 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 describing the write.
interface CloudStoragePutOptions {
  httpMetadata?: CloudStorageHTTPMetadata;    // Content-Type, Cache-Control, …
  customMetadata?: Record<string, string>;    // x-amz-meta-* — keys lower-cased on read
}
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.
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 including size and uploaded.

delete(key)

Remove an object. Idempotent — deleting a missing key resolves normally.
await env.MY_BUCKET.delete("uploads/logo.png");

list(options?)

Enumerate objects (metadata only — list does not return bodies).
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
}
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

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.