# Serverpod File Uploads

> File uploads in Serverpod — upload descriptions, verification, storage backends (database, S3, GCP). Use when implementing file uploads or cloud storage.

- Skill: `serverpod/serverpod-file-uploads` (Agent Skill)
- Install (CLI): `npx skillmds@latest add serverpod/serverpod-file-uploads`
- Raw SKILL.md: https://api.skillmd.com/api/skills/serverpod/serverpod-file-uploads/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: DevOps & Infra
- Author: serverpod (https://skillmd.com/u/serverpod)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/serverpod/serverpod-file-uploads

---


# Serverpod File Uploads

Flow: server issues upload description → client uploads → server verifies. Default storage is the database; use S3, GCP, R2, or compatible object storage for production.

## Server: create upload description

```dart
Future<String> getUploadDescription(Session session, String path) async {
  return await session.storage.createUploadDescription(
    storageId: 'public',
    path: path,
  );
}
```

Always authorize the request and derive the path from trusted server-side state (user id, tenant id, object id). Do not accept arbitrary client paths.

## Server: verify upload

```dart
Future<bool> verifyUpload(Session session, String path) async {
  return await session.storage.verifyUpload(
    storageId: 'public', path: path);
}
```

Always verify after client upload when using object storage.

## Client: upload

```dart
var desc = await client.myEndpoint.getUploadDescription('profile/$userId/avatar.png');
var uploader = FileUploader(desc);
await uploader.upload(byteDataOrStream);
await client.myEndpoint.verifyUpload('profile/$userId/avatar.png');
```

Use `Stream` for large files. Paths: no leading slash, object-store compatible, normalized, and scoped to the authenticated user/tenant.

## Security checklist

- Require authentication/authorization for both description and verification endpoints.
- Validate or derive content type, size, and extension before issuing descriptions.
- Never let clients choose cross-tenant paths or storage IDs.
- Store metadata in your database after `verifyUpload` succeeds.

## Accessing stored files

- `session.storage.fileExists(storageId: 'public', path: path)`
- `session.storage.publicDownloadUrl(storageId: 'public', path: path)` (public storage only)
- `session.storage.temporaryDownloadUrl(storageId: 'private', path: path)` (time-limited access to private files)
- `session.storage.retrieveFile(storageId: 'public', path: path)`

## Storage backends

- **Database (default):** `public` and `private` storages. Fine for dev.
- **Google Cloud Storage:** Add `serverpod_cloud_storage_gcp`, set HMAC keys in `passwords.yaml` or env. Register: `pod.addCloudStorage(GoogleCloudStorage(...))`.
- **AWS S3:** Add `serverpod_cloud_storage_s3`, set AWS keys. Register: `pod.addCloudStorage(S3CloudStorage(...))`.
- **S3-compatible/R2:** Use the matching integration package when targeting compatible providers.

Use `storageId: 'public'` or `'private'` when replacing defaults.

