> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/pocketbase/pocketbase/llms.txt
> Use this file to discover all available pages before exploring further.

# File upload

> Upload files to PocketBase records using the file field type

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:

<Steps>
  <Step title="Validate file constraints">
    PocketBase checks the file against the field's `MaxSize`, `MaxSelect`, and `MimeTypes` constraints.
  </Step>

  <Step title="Normalize filename">
    The original filename is sanitized and a random 10-character suffix is appended for uniqueness.
  </Step>

  <Step title="Upload to storage">
    The file is uploaded to the storage backend (local or S3) at the path: `{collection}/{recordId}/{filename}`
  </Step>

  <Step title="Save to database">
    Only the filename is stored in the record field, not the full path.
  </Step>

  <Step title="Generate thumbnails">
    If the file is an image and thumbnails are configured, they're generated on first request.
  </Step>
</Steps>

## Creating File instances

PocketBase provides several methods to create `File` objects for upload:

### From multipart form data

```go theme={null}
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

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

record.Set("document", file)
```

### From bytes

```go theme={null}
imageData := []byte{...} // Your file data

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

record.Set("photo", file)
```

### From URL

```go theme={null}
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)

```go theme={null}
// 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)

```go theme={null}
// 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:

```go theme={null}
// 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:

```go theme={null}
// 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:

```go theme={null}
// 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"]
```

<Warning>
  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.
</Warning>

## File validation

PocketBase automatically validates files based on the field configuration:

### Size validation

```go theme={null}
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
```

<Info>
  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.
</Info>

### MIME type validation

```go theme={null}
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

```go theme={null}
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:

```go theme={null}
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

```go theme={null}
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

```go theme={null}
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)
```

<Note>
  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.
</Note>

## Error handling

File upload errors are handled during record validation and save:

```go theme={null}
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:

```go theme={null}
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

<CardGroup cols={2}>
  <Card title="File download" icon="download" href="/files/download">
    Learn how to serve and download files
  </Card>

  <Card title="S3 storage" icon="cloud" href="/files/s3-storage">
    Configure cloud storage for files
  </Card>
</CardGroup>
