runlot

env.storage

Provides the put, get, head, delete, list, and presign APIs.

export interface Storage {
  put(key: string, body: BodyInit | null, opts?: { contentType?: string }): Promise<StorageObject>;
  /** Returns `null` if the key does not exist. It is not a 404 error. */
  get(key: string): Promise<(StorageObject & { body: ReadableStream<Uint8Array> | null }) | null>;
  head(key: string): Promise<StorageObject | null>;
  /** Safe to call repeatedly. Deleting a key that does not exist still succeeds. */
  delete(key: string): Promise<void>;
  list(opts?: { prefix?: string; cursor?: string; limit?: number }): Promise<StorageListPage>;
  /** A signed URL. Only GET and PUT are supported. ttl is in seconds (default 300, max 3600). */
  presign(key: string, opts?: { method?: "GET" | "PUT"; ttl?: number }): Promise<string>;
}

Object metadata has the following shape.

export interface StorageObject {
  key: string;
  size: number;
  etag: string;
  contentType: string;
  /** RFC 1123 format, and may be empty. */
  lastModified: string;
}

A missing key returns null

get and head return null when the key does not exist. Checking whether an object exists is a common operation, so we designed the API so you don't have to wrap every call in try/catch to handle a 404.

delete is safe to call repeatedly. Deleting a key that does not exist still succeeds.

Key rules

Write keys as paths separated by /.

await env.storage.put("users/42/avatar.png", body);

Empty path segments and . or .. are not allowed.

await env.storage.put("a//b", body);   // TypeError
await env.storage.put("a/../b", body); // TypeError

Once a URL parser normalizes /o/../x to /x, the original path can no longer be inspected. That is why the worker rejects these keys before the request is sent.

A key can be at most 1024 bytes long.

Files are isolated between projects

The project identifier is added to storage keys automatically. You cannot address another project's objects by key.

On this page