Skip to main content

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.

Every EdgeSpark project includes Cloudflare 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:
server/src/defs/storage_schema.ts
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:
edgespark storage apply
Inspect synced buckets with:
edgespark storage bucket list --desc

Store server-generated content

server/src/index.ts
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

server/src/index.ts
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:
server/src/index.ts
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

server/src/index.ts
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:
server/src/index.ts
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;
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.

See also

storage reference

Complete storage API: put, get, list, delete, and presigned URLs.

Platform limits

File size and runtime constraints for uploads and downloads.
Last modified on April 9, 2026