A Bucket is a logical store for files. Encore provisions the underlying object storage (S3 on AWS, GCS on GCP, in-memory locally). Declare buckets as package-level variables.
package uploads
import "encore.dev/storage/objects"
// Private bucket (default)
var Uploads = objects.NewBucket("user-uploads", objects.BucketConfig{})
// Public bucket — files accessible via public URL
var PublicAssets = objects.NewBucket("public-assets", objects.BucketConfig{
Public: true,
})
import (
"fmt"
"io"
)
// Upload (streaming pattern)
writer := Uploads.Upload(ctx, "path/to/file.jpg")
_, err := io.Copy(writer, dataReader)
if err != nil {
writer.Abort()
return err
}
err = writer.Close()
if err != nil {
return err
}
// Download
reader := Uploads.Download(ctx, "path/to/file.jpg")
if err := reader.Err(); err != nil {
return err
}
defer reader.Close()
data, _ := io.ReadAll(reader)
// Existence check
exists, err := Uploads.Exists(ctx, "path/to/file.jpg")
// Attributes (size, content type, ETag)
attrs, err := Uploads.Attrs(ctx, "path/to/file.jpg")
// List
for err, entry := range Uploads.List(ctx, &objects.Query{}) {
if err != nil {
return err
}
fmt.Println(entry.Key, entry.Size)
}
// Delete
err := Uploads.Remove(ctx, "path/to/file.jpg")
// Public URL (only for public buckets)
url := PublicAssets.PublicURL("image.jpg")
Each snippet above is an independent example. When combining them in one function, adapt the variable names so the code compiles and every error check is kept, including the check on the full read of a download. The fixed keys shown are illustrative; real keys should be unique per user.
Generate temporary URLs so clients can upload/download directly without going through your service:
import "time"
// Signed upload URL (expires in 2 hours)
url, err := Uploads.SignedUploadURL(ctx, "user-uploads/avatar.jpg",
objects.WithTTL(time.Duration(7200)*time.Second))
// Signed download URL
url, err := Uploads.SignedDownloadURL(ctx, "documents/report.pdf",
objects.WithTTL(time.Duration(7200)*time.Second))
The two hour lifetime above is only an example; use the shortest practical lifetime, usually minutes.
Pass bucket access to library code with a specific permission set:
// Create a reference with download permission only
ref := objects.BucketRef[objects.Downloader](Uploads)
// Combine permissions via an interface
type myPerms interface {
objects.Downloader
objects.Uploader
}
ref := objects.BucketRef[myPerms](Uploads)
// Permission types: Downloader, Uploader, Lister, Attrser, Remover,
// SignedDownloader, SignedUploader, ReadWriter
- Declare buckets as package-level variables.
- Default to private buckets; opt into
Public: true only for assets meant for unauthenticated download.
- A public bucket makes every object in it readable by anyone, with no control per file; keep public buckets dedicated to assets meant to be public, and store user uploads and private data in private buckets.
- Use signed URLs for browser uploads/downloads instead of streaming through your service.
- Treat files uploaded through signed URLs as untrusted: after upload, the service checks their size and content type against an allowlist, and removes or quarantines any object that fails before it is used.
- Build object keys on the server from the signed in user and a generated identifier, and reject client keys that contain unexpected prefixes, parent directory segments, or a leading slash.
- Confirm the caller owns a key before any upload, download, removal, attribute lookup, or signed URL call.
- Issue signed URLs only from authenticated endpoints after that ownership check, keep their lifetime short, and treat them as bearer secrets that stay out of logs.
- Set an explicit content type that the server has validated when storing user files, and serve user content from a separate domain or as an attachment download, keeping user HTML and SVG files off public buckets on a trusted origin.
- Check the object size before reading it fully into memory, stream large files instead, and check the read error.
- Scope list queries to a prefix the caller is allowed to see, with a result limit, and keep full bucket listings internal.
- Use bucket references when passing access to helpers — they encode the permission contract in the type system.