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

# Use EdgeSpark storage

> Declare EdgeSpark storage buckets in your repo, apply them with the CLI, and upload or serve files from the runtime SDK.

Every EdgeSpark project includes [Cloudflare R2](https://developers.cloudflare.com/r2/) object storage. You declare buckets in `server/src/defs/storage_schema.ts`, sync them with `edgespark storage apply`, and use the runtime SDK in your routes.

For browser-facing file transfer, EdgeSpark should usually issue presigned URLs instead of proxying file bytes through your Worker. Use direct `put()` and `get()` when the server itself is creating, reading, or transforming the bytes.

## Declare buckets

Add bucket definitions to `server/src/defs/storage_schema.ts`:

```typescript server/src/defs/storage_schema.ts theme={null}
import type { BucketDef } from "@sdk/server-types";

export const uploads: BucketDef<"uploads"> = {
  bucket_name: "uploads",
  description: "User-uploaded files",
};

export const avatars: BucketDef<"avatars"> = {
  bucket_name: "avatars",
  description: "Profile photos",
};
```

Apply the declarations:

```bash theme={null}
edgespark storage apply
```

Inspect synced buckets with:

```bash theme={null}
edgespark storage bucket list --desc
```

## Store server-generated content

```typescript server/src/index.ts theme={null}
import { storage } from "edgespark";
import { Hono } from "hono";
import { buckets } from "@defs";

const app = new Hono().post("/api/reports/export", async (c) => {
  const csv = [
    "id,title",
    "1,Quarterly report",
    "2,Annual report",
  ].join("\\n");

  const path = `reports/export-${Date.now()}.csv`;
  await storage.from(buckets.uploads).put(path, new TextEncoder().encode(csv), {
    contentType: "text/csv",
  });

  return c.json({ path }, 201);
});

export default app;
```

## Issue a download URL for clients

```typescript server/src/index.ts theme={null}
import { storage } from "edgespark";
import { Hono } from "hono";
import { buckets } from "@defs";

const app = new Hono().get("/api/documents/:filename", async (c) => {
  const filename = c.req.param("filename");
  const { downloadUrl } = await storage
    .from(buckets.uploads)
    .createPresignedGetUrl(`documents/${filename}`, 3600);

  return c.json({ downloadUrl });
});

export default app;
```

## Read a file server-side

When the server itself needs the bytes, use `get()` directly:

```typescript server/src/index.ts theme={null}
import { storage } from "edgespark";
import { Hono } from "hono";
import { buckets } from "@defs";

const app = new Hono().get("/api/reports/:filename/raw", async (c) => {
  const object = await storage
    .from(buckets.uploads)
    .get(`reports/${c.req.param("filename")}`);

  if (!object) return c.json({ error: "Not found" }, 404);

  return new Response(object.body, {
    headers: {
      "Content-Type": object.metadata.contentType ?? "application/octet-stream",
    },
  });
});

export default app;
```

## List and delete files

```typescript server/src/index.ts theme={null}
import { storage } from "edgespark";
import { Hono } from "hono";
import { buckets } from "@defs";

const app = new Hono()
  .get("/api/documents", async (c) => {
    const files = await storage.from(buckets.uploads).list({ prefix: "documents/" });
    return c.json(files);
  })
  .delete("/api/documents/:filename", async (c) => {
    await storage.from(buckets.uploads).delete(`documents/${c.req.param("filename")}`);
    return c.body(null, 204);
  });

export default app;
```

## Use presigned URLs for direct uploads

For client-originated uploads, generate a presigned URL and upload directly to R2:

```typescript server/src/index.ts theme={null}
import { storage } from "edgespark";
import { auth } from "edgespark/http";
import { Hono } from "hono";
import { buckets } from "@defs";

const app = new Hono().post("/api/upload-url", async (c) => {
  const { filename, contentType } = await c.req.json<{
    filename: string;
    contentType?: string;
  }>();

  const path = `uploads/${auth.user.id}/${Date.now()}-${filename}`;
  const { uploadUrl, requiredHeaders } = await storage
    .from(buckets.uploads)
    .createPresignedPutUrl(path, 3600, {
      contentType,
    });

  return c.json({ uploadUrl, requiredHeaders, path });
});

export default app;
```

<Warning>
  Default to presigned PUT and GET URLs for browser-facing file transfer. When you are working near request, file size, or runtime limits, link back to [platform limits](/reference/limits).
</Warning>

## See also

<Columns cols={2}>
  <Card title="storage reference" icon="code" href="/sdk/storage">
    Complete storage API: put, get, list, delete, and presigned URLs.
  </Card>

  <Card title="Platform limits" icon="gauge" href="/reference/limits">
    File size and runtime constraints for uploads and downloads.
  </Card>
</Columns>
