Skip to main content
PocketBase handles file uploads through the file field type. When you create or update a record with file fields, you can upload new files, manage existing ones, and delete unwanted files.

Upload workflow

The file upload process follows these steps:
1

Validate file constraints

PocketBase checks the file against the field’s MaxSize, MaxSelect, and MimeTypes constraints.
2

Normalize filename

The original filename is sanitized and a random 10-character suffix is appended for uniqueness.
3

Upload to storage

The file is uploaded to the storage backend (local or S3) at the path: {collection}/{recordId}/{filename}
4

Save to database

Only the filename is stored in the record field, not the full path.
5

Generate thumbnails

If the file is an image and thumbnails are configured, they’re generated on first request.

Creating File instances

PocketBase provides several methods to create File objects for upload:

From multipart form data

import (
    "github.com/pocketbase/pocketbase/tools/filesystem"
)

func uploadFromForm(e *core.RequestEvent) error {
    // Parse multipart form
    file, err := filesystem.NewFileFromMultipart(e.Request.FormValue("avatar"))
    if err != nil {
        return err
    }
    
    // Set on record
    record.Set("avatar", file)
    
    return e.App.Save(record)
}

From local file path

file, err := filesystem.NewFileFromPath("/path/to/image.png")
if err != nil {
    return err
}

record.Set("document", file)

From bytes

imageData := []byte{...} // Your file data

file, err := filesystem.NewFileFromBytes(imageData, "photo.jpg")
if err != nil {
    return err
}

record.Set("photo", file)

From URL

import (
    "context"
    "time"
)

ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()

file, err := filesystem.NewFileFromURL(ctx, "https://example.com/image.png")
if err != nil {
    return err
}

record.Set("banner", file)

Single vs. multiple files

The field’s MaxSelect property determines whether it accepts single or multiple files.

Single file field (MaxSelect ≤ 1)

// Single file - stored as string in database
field := &core.FileField{
    Name:      "avatar",
    MaxSelect: 1,
}

// Upload single file
record.Set("avatar", file)

// The last file wins if you set multiple times
record.Set("avatar", file1)
record.Set("avatar", file2) // file2 overwrites file1

Multiple files field (MaxSelect > 1)

// Multiple files - stored as JSON array in database
field := &core.FileField{
    Name:      "documents",
    MaxSelect: 5,
}

// Upload multiple files
record.Set("documents", []*filesystem.File{file1, file2, file3})

Advanced file operations

PocketBase provides special setter keys for managing multiple files:

Append files

Add new files to the end of existing files:
// Existing: ["old1.txt", "old2.txt"]
record.Set("documents+", []*filesystem.File{new1, new2})
// Result: ["old1.txt", "old2.txt", "new1_ajkvass.txt", "new2_klhfnwd.txt"]

Prepend files

Add new files to the beginning:
// Existing: ["old1.txt", "old2.txt"]
record.Set("+documents", []*filesystem.File{new1, new2})
// Result: ["new1_ajkvass.txt", "new2_klhfnwd.txt", "old1.txt", "old2.txt"]

Remove files

Delete specific files from the field:
// Existing: ["old1.txt", "old2.txt", "old3.txt"]
record.Set("documents-", "old1.txt")
// Result: ["old2.txt", "old3.txt"]

// Remove multiple files
record.Set("documents-", []string{"old1.txt", "old2.txt"})
// Result: ["old3.txt"]
When you remove files using the - operator or by replacing the field value, the actual files are deleted from storage after the record is successfully saved.

File validation

PocketBase automatically validates files based on the field configuration:

Size validation

field := &core.FileField{
    Name:    "upload",
    MaxSize: 2 << 20, // 2MB limit
}

// This will fail if file is larger than 2MB
record.Set("upload", largeFile)
err := app.Save(record) // Returns validation error
The default maximum file size is 5MB. You can set MaxSize to 0 to use the default, or specify a custom value up to 2^53-1 bytes.

MIME type validation

field := &core.FileField{
    Name: "image",
    MimeTypes: []string{
        "image/png",
        "image/jpeg",
        "image/webp",
    },
}

// This will fail if file is not PNG, JPEG, or WebP
record.Set("image", pdfFile)
err := app.Save(record) // Returns validation error

Count validation

field := &core.FileField{
    Name:      "gallery",
    MaxSelect: 3,
}

// This will fail - too many files
record.Set("gallery", []*filesystem.File{f1, f2, f3, f4})
err := app.Save(record) // Returns "too many files" error

Direct filesystem upload

For advanced use cases, you can upload files directly to the filesystem without using record fields:
func directUpload(app core.App) error {
    // Initialize filesystem
    fsys, err := app.NewFilesystem()
    if err != nil {
        return err
    }
    defer fsys.Close()
    
    // Upload file
    file, _ := filesystem.NewFileFromPath("/path/to/file.txt")
    err = fsys.UploadFile(file, "custom/path/file.txt")
    if err != nil {
        return err
    }
    
    return nil
}

Upload raw bytes

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

content := []byte("Hello, World!")
err = fsys.Upload(content, "custom/hello.txt")

Upload multipart file directly

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

// fh is *multipart.FileHeader from request
err = fsys.UploadMultipart(fh, "uploads/" + fh.Filename)
When using direct filesystem uploads, you’re responsible for managing file paths, cleanup, and access control. For most use cases, use the file field type instead.

Error handling

File upload errors are handled during record validation and save:
record.Set("avatar", file)

err := app.Save(record)
if err != nil {
    // Check for specific validation errors
    if strings.Contains(err.Error(), "validation_invalid_file") {
        // Invalid file format or type
    }
    if strings.Contains(err.Error(), "validation_too_many_files") {
        // Exceeds MaxSelect
    }
    if strings.Contains(err.Error(), "UploadedFileSize") {
        // File too large
    }
    if strings.Contains(err.Error(), "UploadedFileMimeType") {
        // Invalid MIME type
    }
    
    return err
}

Complete example

Here’s a complete example of handling file uploads in a custom endpoint:
package main

import (
    "net/http"
    
    "github.com/pocketbase/pocketbase"
    "github.com/pocketbase/pocketbase/core"
    "github.com/pocketbase/pocketbase/tools/filesystem"
)

func main() {
    app := pocketbase.New()
    
    app.OnServe().BindFunc(func(e *core.ServeEvent) error {
        // Custom upload endpoint
        e.Router.POST("/api/custom-upload", func(e *core.RequestEvent) error {
            // Get authenticated user
            authRecord := e.Auth
            if authRecord == nil {
                return e.UnauthorizedError("Authentication required", nil)
            }
            
            // Parse multipart form
            if err := e.Request.ParseMultipartForm(32 << 20); err != nil {
                return e.BadRequestError("Invalid form data", err)
            }
            
            // Get file from form
            fh, err := e.Request.FormFile("avatar")
            if err != nil {
                return e.BadRequestError("Missing avatar file", err)
            }
            
            // Create File instance
            file, err := filesystem.NewFileFromMultipart(fh)
            if err != nil {
                return e.BadRequestError("Invalid file", err)
            }
            
            // Update user record
            authRecord.Set("avatar", file)
            
            if err := e.App.Save(authRecord); err != nil {
                return e.BadRequestError("Failed to save", err)
            }
            
            return e.JSON(http.StatusOK, authRecord)
        }).Bind(
            // Require authentication
            RequireAuth(),
        )
        
        return e.Next()
    })
    
    if err := app.Start(); err != nil {
        panic(err)
    }
}

Next steps

File download

Learn how to serve and download files

S3 storage

Configure cloud storage for files