Skip to main content
PocketBase provides built-in file management capabilities with support for local and S3-compatible storage, automatic file validation, and thumbnail generation for images.

File fields

File storage is handled through file fields in your collections:
collection.Fields.Add(&core.FileField{
    Name:      "attachments",
    MaxSelect: 5,              // Allow up to 5 files
    MaxSize:   5242880,        // 5MB per file
    MimeTypes: []string{       // Restrict file types
        "image/jpeg",
        "image/png",
        "application/pdf",
    },
    Thumbs: []string{          // Image thumbnails
        "100x100",
        "300x300",
        "0x500",  // Width auto, height 500px
    },
    Protected: false,          // Public access
    Required:  false,
})

File field options

MaxSelect

Controls whether the field stores a single file or multiple files:
// Single file
field.MaxSelect = 1  // or 0
// Value type: string (filename)

// Multiple files
field.MaxSelect = 5
// Value type: []string (array of filenames)

MaxSize

Specifies the maximum file size in bytes:
field.MaxSize = 5 << 20  // 5MB
field.MaxSize = 10485760 // 10MB
If not set, the default maximum size is 5MB per file.

MimeTypes

Restricts allowed file types:
field.MimeTypes = []string{
    "image/jpeg",
    "image/png",
    "image/gif",
    "image/webp",
    "video/mp4",
    "application/pdf",
    "application/zip",
}
Leave empty to allow all file types.

Thumbs

Defines thumbnail sizes for image files:
field.Thumbs = []string{
    "100x100",   // Crop to 100x100 from center
    "100x100t",  // Crop to 100x100 from top
    "100x100b",  // Crop to 100x100 from bottom
    "100x100f",  // Fit inside 100x100 (no cropping)
    "0x300",     // Height 300px, width auto
    "200x0",     // Width 200px, height auto
}
Thumbnails are generated automatically for supported image formats.

Protected

Controls file access:
// Public files (default)
field.Protected = false
// URL: /api/files/{collection}/{record}/{filename}

// Protected files
field.Protected = true
// URL: /api/files/{collection}/{record}/{filename}?token={fileToken}
Even public files have randomized filenames (e.g., document_Ab3Kf9g2.pdf), providing obscurity. Protected files require a short-lived token for access.

Uploading files

Single file upload

file := &filesystem.File{
    Name:   "document.pdf",
    Reader: fileReader,  // io.Reader
}

record.Set("attachment", file)
app.Save(record)  // File is uploaded during save

Multiple file upload

files := []*filesystem.File{
    {Name: "image1.jpg", Reader: reader1},
    {Name: "image2.jpg", Reader: reader2},
    {Name: "image3.jpg", Reader: reader3},
}

record.Set("gallery", files)
app.Save(record)

Modifying uploaded files

// Get files pending upload
unsavedFiles := record.GetUnsavedFiles("documents")

// Modify before saving
for _, file := range unsavedFiles {
    // Add prefix to filename
    file.Name = "doc_" + file.Name
    
    // Wrap reader for processing
    file.Reader = newProcessingReader(file.Reader)
}

app.Save(record)

File modifiers

File fields support special modifier operations:
// Append files
record.Set("images+", newFile)
record.Set("images+", []*filesystem.File{file1, file2})

// Prepend files
record.Set("+images", newFile)

// Remove files by name
record.Set("images-", "old_image.jpg")
record.Set("images-", []string{"file1.jpg", "file2.jpg"})

File storage

Storage structure

Files are stored in the following hierarchy:
storage/
  └── {collectionId}/
      └── {recordId}/
          ├── file1_Ab3Kf9g2.jpg
          ├── file2_Xk8Mn4p1.pdf
          └── thumbs_file1_Ab3Kf9g2.jpg/
              ├── 100x100_file1_Ab3Kf9g2.jpg
              └── 300x300_file1_Ab3Kf9g2.jpg

Filename normalization

PocketBase automatically adds a random suffix to filenames:
Original:  document.pdf
Stored as: document_Ab3Kf9g2.pdf
This prevents:
  • Filename conflicts
  • Guessing file URLs
  • Accidental overwrites

Accessing files

File URLs

// Get filename
filename := record.GetString("avatar")

// Build URL
url := fmt.Sprintf(
    "%s/api/files/%s/%s/%s",
    app.Settings().Meta.AppUrl,
    record.Collection().Id,
    record.Id,
    filename,
)

Public file URLs

For non-protected files:
GET /api/files/{collectionId}/{recordId}/{filename}
Example:
https://example.com/api/files/posts_collection/record123/image_Ab3Kf9g2.jpg

Protected file URLs

For protected files, generate a file token:
// Generate file token (valid for 3 minutes by default)
token, err := core.GenerateFileToken(
    app,
    record,
    filename,
)

// Build URL with token
url := fmt.Sprintf(
    "%s/api/files/%s/%s/%s?token=%s",
    app.Settings().Meta.AppUrl,
    record.Collection().Id,
    record.Id,
    filename,
    token,
)

Thumbnail URLs

Access image thumbnails:
GET /api/files/{collectionId}/{recordId}/thumbs_{filename}/{size}_{filename}
Example:
https://example.com/api/files/posts/record123/thumbs_image_Ab3Kf9g2.jpg/100x100_image_Ab3Kf9g2.jpg

File validation

Automatic validation

File fields automatically validate:
  • File count (MaxSelect)
  • File size (MaxSize)
  • MIME type (MimeTypes)
  • Filename format

Custom validation

app.OnRecordValidate().Bind(&hook.Handler[*core.RecordEvent]{
    Func: func(e *core.RecordEvent) error {
        if e.Record.Collection().Name == "posts" {
            files := e.Record.GetUnsavedFiles("images")
            
            for _, file := range files {
                // Custom validation logic
                if file.Size > 2<<20 {
                    return validation.NewError(
                        "validation_file_too_large",
                        "Images must be under 2MB",
                    )
                }
                
                // Scan for malware, check dimensions, etc.
            }
        }
        return e.Next()
    },
})

File operations

Listing files

// Get all filenames for a field
filenames := record.GetStringSlice("attachments")

for _, filename := range filenames {
    fmt.Println(filename)
}

Finding files

// Find which field contains a file
field := record.FindFileFieldByFile("document_Ab3Kf9g2.pdf")

if field != nil {
    fmt.Println("File is in field:", field.Name)
}

Deleting files

Files are automatically deleted when:
  1. Record is deleted
  2. File field value is updated (old files removed)
  3. File is explicitly removed using modifiers
// Remove specific file
record.Set("images-", "old_image.jpg")
app.Save(record)  // File deleted after save

// Replace all files
record.Set("images", newFiles)
app.Save(record)  // Old files deleted

// Delete record
app.Delete(record)  // All files deleted

S3 storage

PocketBase supports S3-compatible storage (AWS S3, MinIO, DigitalOcean Spaces, etc.):
app.Settings().S3 = settings.S3Config{
    Enabled:        true,
    Bucket:         "my-bucket",
    Region:         "us-east-1",
    Endpoint:       "s3.amazonaws.com",
    AccessKey:      "ACCESS_KEY",
    Secret:         "SECRET_KEY",
    ForcePathStyle: false,
}

app.SaveSettings()
When switching from local to S3 storage, you must manually migrate existing files. New files will automatically use S3.

File hooks

Upload processing

app.OnRecordCreateExecute().Bind(&hook.Handler[*core.RecordEvent]{
    Func: func(e *core.RecordEvent) error {
        files := e.Record.GetUnsavedFiles("images")
        
        for _, file := range files {
            // Process image: resize, optimize, watermark, etc.
            processedReader := processImage(file.Reader)
            file.Reader = processedReader
        }
        
        return e.Next()
    },
})

Post-upload actions

app.OnRecordAfterCreateSuccess().Bind(&hook.Handler[*core.RecordEvent]{
    Func: func(e *core.RecordEvent) error {
        // Files are now saved
        filenames := e.Record.GetStringSlice("attachments")
        
        // Trigger async processing: virus scan, metadata extraction, etc.
        for _, filename := range filenames {
            go processFileAsync(e.App, e.Record, filename)
        }
        
        return e.Next()
    },
})

File deletion

app.OnRecordAfterDeleteSuccess().Bind(&hook.Handler[*core.RecordEvent]{
    Func: func(e *core.RecordEvent) error {
        // Files are already deleted by this point
        // Use this for cleanup in external systems
        
        app.Logger().Info(
            "Record files deleted",
            "collection", e.Record.Collection().Name,
            "record", e.Record.Id,
        )
        
        return e.Next()
    },
})

Direct filesystem access

Getting filesystem instance

fsys, err := app.NewFilesystem()
if err != nil {
    return err
}
defer fsys.Close()

File operations

// Upload file
path := record.BaseFilesPath() + "/" + filename
err := fsys.UploadFile(file, path)

// Download file
reader, err := fsys.GetFile(path)

// Delete file
err := fsys.Delete(path)

// Delete directory
err := fsys.DeletePrefix(record.BaseFilesPath())

// Check if exists
exists, err := fsys.Exists(path)

// Get file attributes
attrs, err := fsys.Attributes(path)
fmt.Println("Size:", attrs.Size)
fmt.Println("Modified:", attrs.ModTime)

List files

// List files in directory
files, err := fsys.List(record.BaseFilesPath())

for _, file := range files {
    fmt.Println(file.Key, file.Size)
}

Performance optimization

Set reasonable MaxSize limits. Smaller files upload faster and consume less storage. Consider compressing large files client-side before upload.
Only generate thumbnails you actually use. Each thumbnail size requires storage space and processing time.
Use a CDN in front of your PocketBase instance to cache and serve files closer to users.
Load images on-demand rather than all at once. Use smaller thumbnails for previews and full-size images only when needed.
S3-compatible storage is generally more reliable and scalable than local filesystem storage for production deployments.
Regularly check for and remove orphaned files or old temporary files to save storage space.

Security best practices

Don’t rely solely on MIME types - they can be spoofed. Verify file contents match expected types.
PocketBase automatically sanitizes filenames, but be cautious when using user-provided filenames in other contexts.
Implement virus scanning for uploaded files, especially for user-facing applications.
Set Protected: true for files containing sensitive information. This requires authentication to access.
Limit file upload frequency and size per user to prevent abuse and resource exhaustion.
Configure CORS properly if files need to be accessed from different domains.