File Storage
Store and retrieve files with an S3/R2-like API.
File Storage
Ploy File Storage provides an S3/R2-like object store for your workers. It is designed for storing and retrieving file content such as user uploads, images, JSON documents, and any binary or text data. Unlike state and cache which store simple string values, file storage supports content types, file listing, and is optimized for larger payloads.
Configuration
Add a file storage binding in your ploy.yaml:
kind: dynamic
build: pnpm build
out: dist
fs:
FILES: defaultThe key (FILES) is the binding name available in your worker's env. The value (default) is the file storage identifier.
Run ploy types to generate TypeScript types:
import type { FileStorageBinding } from "@meetploy/types";
export interface Env {
FILES: FileStorageBinding;
}Basic Example
export default {
async fetch(request, env) {
const url = new URL(request.url);
if (url.pathname === "/upload" && request.method === "POST") {
const body = await request.text();
await env.FILES.put("hello.txt", body, { contentType: "text/plain" });
return Response.json({ success: true });
}
if (url.pathname === "/download") {
const file = await env.FILES.get("hello.txt");
if (!file) {
return new Response("Not Found", { status: 404 });
}
return new Response(file.body, {
headers: { "Content-Type": file.contentType },
});
}
if (url.pathname === "/delete") {
await env.FILES.delete("hello.txt");
return Response.json({ success: true });
}
return new Response("File Storage Worker");
},
} satisfies Ploy;API Methods
put(key, value, options?) - Store a File
Stores a file with the given key. Overwrites any existing file at the same key. The optional options parameter lets you specify a content type.
await env.FILES.put("report.pdf", pdfBuffer, {
contentType: "application/pdf",
});
// Content type defaults to "application/octet-stream" if not specified
await env.FILES.put("data.bin", binaryData);get(key) - Retrieve a File
Returns the file object or null if the key doesn't exist. The returned object includes body (the file content) and contentType.
const file = await env.FILES.get("report.pdf");
// file is { body: ReadableStream | string, contentType: string } | null
if (file) {
console.log(file.contentType); // "application/pdf"
// Use file.body for the content
}delete(key) - Delete a File
Removes a file from the store.
await env.FILES.delete("report.pdf");list(options?) - List Files
Returns a list of keys in the store. Use the optional prefix parameter to filter results.
// List all files
const allFiles = await env.FILES.list();
// allFiles is { keys: string[] }
// List files under a prefix
const userFiles = await env.FILES.list({ prefix: "uploads/user-123/" });File storage supports any content type. Use the contentType option in
put() to ensure files are served with the correct MIME type when retrieved.
Common Patterns
Storing User Uploads
async function handleUpload(
request: Request,
env: PloyEnv,
userId: string,
): Promise<Response> {
const contentType =
request.headers.get("Content-Type") || "application/octet-stream";
const filename = request.headers.get("X-Filename") || "file";
const key = `uploads/${userId}/${Date.now()}-${filename}`;
const body = await request.arrayBuffer();
await env.FILES.put(key, body, { contentType });
return Response.json({ key });
}Storing JSON Documents
// Store a JSON document
const config = { theme: "dark", layout: "grid", version: 2 };
await env.FILES.put("config/app.json", JSON.stringify(config), {
contentType: "application/json",
});
// Retrieve and parse
const file = await env.FILES.get("config/app.json");
if (file) {
const config = JSON.parse(file.body);
}Organizing Files with Prefixes
// Store files under organized prefixes
await env.FILES.put("images/avatars/user-123.png", avatarData, {
contentType: "image/png",
});
await env.FILES.put("images/avatars/user-456.png", avatarData, {
contentType: "image/png",
});
await env.FILES.put("documents/invoices/inv-001.pdf", invoiceData, {
contentType: "application/pdf",
});
// List all avatars
const avatars = await env.FILES.list({ prefix: "images/avatars/" });
// avatars.keys = ["images/avatars/user-123.png", "images/avatars/user-456.png"]
// List all documents
const docs = await env.FILES.list({ prefix: "documents/" });Serving Files with Correct Headers
async function serveFile(env: PloyEnv, key: string): Promise<Response> {
const file = await env.FILES.get(key);
if (!file) {
return new Response("Not Found", { status: 404 });
}
return new Response(file.body, {
headers: {
"Content-Type": file.contentType,
"Cache-Control": "public, max-age=3600",
},
});
}Multiple File Storage Bindings
You can configure multiple file stores for different use cases:
fs:
FILES: default
MEDIA: media// General file storage
await env.FILES.put("data/export.csv", csvContent, {
contentType: "text/csv",
});
// Media-specific storage
await env.MEDIA.put("images/hero.jpg", imageData, {
contentType: "image/jpeg",
});Differences from State and Cache
| File Storage | State | Cache | |
|---|---|---|---|
| Designed for | File content (binary/text) | Simple key-value strings | Temporary key-value strings |
| Content type | Supports contentType metadata | Strings only | Strings only |
| Listing | list() with prefix filtering | No listing | No listing |
| TTL | None (persists until deleted) | None (persists until deleted) | Required on every set() |
| Use case | Uploads, documents, images, exports | Preferences, flags, counters | Sessions, rate limiting, caching |
Next Steps
How is this guide?
Last updated on