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.

storage provides access to your project’s R2 buckets. Import it from edgespark and pass bucket declarations from @defs.

storage.from(bucket)

server/src/index.ts
import { storage } from "edgespark";
import { buckets } from "@defs";

const uploads = storage.from(buckets.uploads);

S3 URI helpers

Use S3 URIs when you want to persist a file reference in your own database:
server/src/index.ts
import { storage } from "edgespark";
import { buckets } from "@defs";

const avatarS3Uri = storage.createS3Uri(buckets.uploads, "avatars/user-1.png");

if (storage.isS3Uri(avatarS3Uri)) {
  const parsed = storage.parseS3Uri(avatarS3Uri);
  console.log(parsed.bucket.bucket_name, parsed.path);
}
If the input may be invalid, use tryParseS3Uri(...):
const parsed = storage.tryParseS3Uri(inputFromDatabaseOrWebhook);

if (!parsed) {
  throw new Error("Invalid S3 URI");
}

Upload and download

server/src/index.ts
import { storage } from "edgespark";
import { buckets } from "@defs";

await storage
  .from(buckets.uploads)
  .put("reports/q1.pdf", new TextEncoder().encode("report"));

const object = await storage.from(buckets.uploads).get("reports/q1.pdf");
if (!object) throw new Error("Not found");

Read metadata without downloading the file

Use head(...) when you only need metadata:
server/src/index.ts
import { storage } from "edgespark";
import { buckets } from "@defs";

const meta = await storage.from(buckets.uploads).head("reports/q1.pdf");

if (meta) {
  console.log(meta.size, meta.contentType);
}

List and delete

server/src/index.ts
import { storage } from "edgespark";
import { buckets } from "@defs";

const files = await storage.from(buckets.uploads).list({ prefix: "reports/" });
await storage.from(buckets.uploads).delete("reports/q1.pdf");
delete(...) also accepts multiple paths:
await storage.from(buckets.uploads).delete([
  "reports/q1.pdf",
  "reports/q2.pdf",
]);

Paginate or group list results

list(...) supports pagination and delimiter-based grouping:
server/src/index.ts
import { storage } from "edgespark";
import { buckets } from "@defs";

const page = await storage.from(buckets.uploads).list({
  prefix: "reports/",
  limit: 100,
  delimiter: "/",
});

console.log(page.files, page.delimitedPrefixes, page.cursor);

Presigned uploads

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 { uploadUrl, requiredHeaders } = await storage
    .from(buckets.uploads)
    .createPresignedPutUrl(`user-files/${auth.user.id}/${filename}`, 3600, {
      contentType,
    });

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

export default app;

Presigned downloads

server/src/index.ts
import { storage } from "edgespark";
import { Hono } from "hono";
import { buckets } from "@defs";

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

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

export default app;
Use presigned URLs for large client uploads and downloads. See platform limits for file size and runtime constraints.

See also

Use storage

Declare buckets, apply them with the CLI, and use storage from your routes.

Build a REST API

Complete example with presigned uploads and file references.
Last modified on April 7, 2026