PocketBase provides a unified filesystem abstraction for managing files both locally and on S3-compatible storage.
Creating filesystem instances
NewFilesystem()
Create a filesystem instance based on app settings:
fs, err := app.NewFilesystem()
if err != nil {
log.Fatal(err)
}
defer fs.Close()
Always call Close() on the filesystem when you’re done to release resources.
NewBackupsFilesystem()
Create a filesystem for managing backups:
fs, err := app.NewBackupsFilesystem()
if err != nil {
log.Fatal(err)
}
defer fs.Close()
Local filesystem
Create a local filesystem instance directly:
import "github.com/pocketbase/pocketbase/tools/filesystem"
fs, err := filesystem.NewLocal("./storage")
if err != nil {
log.Fatal(err)
}
defer fs.Close()
S3 filesystem
Create an S3-compatible filesystem:
import "github.com/pocketbase/pocketbase/tools/filesystem"
fs, err := filesystem.NewS3(
"my-bucket", // bucket name
"us-east-1", // region
"s3.amazonaws.com", // endpoint
"ACCESS_KEY", // access key
"SECRET_KEY", // secret key
false, // force path style
)
if err != nil {
log.Fatal(err)
}
defer fs.Close()
S3-compatible services
The S3 filesystem works with any S3-compatible service:
AWS S3
Cloudflare R2
MinIO
fs, err := filesystem.NewS3(
"my-bucket",
"us-east-1",
"s3.amazonaws.com",
"ACCESS_KEY",
"SECRET_KEY",
false,
)
fs, err := filesystem.NewS3(
"my-bucket",
"auto",
"https://ACCOUNT_ID.r2.cloudflarestorage.com",
"ACCESS_KEY",
"SECRET_KEY",
false,
)
fs, err := filesystem.NewS3(
"my-bucket",
"us-east-1",
"minio.example.com",
"minioadmin",
"minioadmin",
true, // force path style
)
File operations
Checking file existence
exists, err := fs.Exists("path/to/file.txt")
if err != nil {
log.Fatal(err)
}
if exists {
log.Println("File exists")
}
Getting file attributes
attrs, err := fs.Attributes("path/to/file.txt")
if err != nil {
log.Fatal(err)
}
log.Printf("Size: %d bytes", attrs.Size)
log.Printf("Content-Type: %s", attrs.ContentType)
log.Printf("Modified: %v", attrs.ModTime)
Reading files
reader, err := fs.GetReader("path/to/file.txt")
if err != nil {
log.Fatal(err)
}
defer reader.Close()
// Read file contents
content, err := io.ReadAll(reader)
if err != nil {
log.Fatal(err)
}
log.Println("Content:", string(content))
Uploading files
Upload from bytes
import "github.com/pocketbase/pocketbase/tools/filesystem"
content := []byte("Hello, World!")
fileKey := "uploads/hello.txt"
err := fs.UploadFile(&filesystem.File{
Reader: bytes.NewReader(content),
Name: "hello.txt",
Size: int64(len(content)),
}, fileKey)
if err != nil {
log.Fatal(err)
}
app.OnServe().BindFunc(func(e *core.ServeEvent) error {
e.Router.POST("/upload", func(re *core.RequestEvent) error {
// Parse multipart form
file, err := re.Request.FormFile("file")
if err != nil {
return re.BadRequestError("", err)
}
defer file.Close()
fs, err := re.App.NewFilesystem()
if err != nil {
return err
}
defer fs.Close()
// Create File from multipart
uploadedFile, err := filesystem.NewFileFromMultipart(file)
if err != nil {
return err
}
// Upload
err = fs.UploadFile(uploadedFile, "uploads/"+uploadedFile.Name)
if err != nil {
return err
}
return re.JSON(200, map[string]string{
"message": "File uploaded successfully",
})
})
return e.Next()
})
Deleting files
Delete a single file
err := fs.Delete("path/to/file.txt")
if err != nil {
log.Fatal(err)
}
Delete multiple files
fileKeys := []string{
"uploads/file1.txt",
"uploads/file2.txt",
"uploads/file3.txt",
}
errors := fs.DeletePrefix("", fileKeys...)
for fileKey, err := range errors {
log.Printf("Failed to delete %s: %v", fileKey, err)
}
Copying files
err := fs.Copy("source/file.txt", "destination/file.txt")
if err != nil {
log.Fatal(err)
}
Listing files
attrs, err := fs.List("uploads/")
if err != nil {
log.Fatal(err)
}
for _, attr := range attrs {
log.Printf("File: %s (Size: %d)", attr.Key, attr.Size)
}
File struct
The File struct represents a file to be uploaded:
type File struct {
Reader io.Reader // File content reader
Name string // File name
OriginalName string // Original uploaded name
Size int64 // File size in bytes
}
Creating File instances
multipartFile, err := request.FormFile("file")
if err != nil {
log.Fatal(err)
}
file, err := filesystem.NewFileFromMultipart(multipartFile)
if err != nil {
log.Fatal(err)
}
From bytes
import "github.com/pocketbase/pocketbase/tools/filesystem"
content := []byte("file content")
file := &filesystem.File{
Reader: bytes.NewReader(content),
Name: "document.txt",
Size: int64(len(content)),
}
From existing file
f, err := os.Open("local-file.txt")
if err != nil {
log.Fatal(err)
}
defer f.Close()
info, _ := f.Stat()
file := &filesystem.File{
Reader: f,
Name: info.Name(),
Size: info.Size(),
}
Working with record files
Uploading files to records
app.OnServe().BindFunc(func(e *core.ServeEvent) error {
e.Router.POST("/posts/:id/upload", func(re *core.RequestEvent) error {
record, err := re.App.FindRecordById("posts", re.Request.PathValue("id"))
if err != nil {
return re.NotFoundError("", err)
}
// Get uploaded file
multipartFile, err := re.Request.FormFile("document")
if err != nil {
return re.BadRequestError("", err)
}
defer multipartFile.Close()
// Convert to filesystem.File
file, err := filesystem.NewFileFromMultipart(multipartFile)
if err != nil {
return err
}
// Assign to record
record.Set("document", file)
// Save record (file is uploaded automatically)
if err := re.App.Save(record); err != nil {
return err
}
return re.JSON(200, record)
})
return e.Next()
})
Accessing record files
record, _ := app.FindRecordById("posts", "RECORD_ID")
// Get file names
filenames := record.GetStringSlice("documents")
for _, filename := range filenames {
log.Println("File:", filename)
}
Deleting record files
Files are automatically deleted when you remove them from a record:
record, _ := app.FindRecordById("posts", "RECORD_ID")
// Remove all files
record.Set("documents", []string{})
// Remove specific file
filenames := record.GetStringSlice("documents")
filtered := []string{}
for _, name := range filenames {
if name != "file-to-delete.pdf" {
filtered = append(filtered, name)
}
}
record.Set("documents", filtered)
app.Save(record) // Files are deleted automatically
File URLs
Generate URLs for accessing record files:
record, _ := app.FindRecordById("posts", "RECORD_ID")
filename := record.GetStringSlice("documents")[0]
// Build file URL
url := fmt.Sprintf(
"%s/api/files/%s/%s/%s",
app.Settings().Meta.AppURL,
record.Collection().Name,
record.Id,
filename,
)
log.Println("File URL:", url)
PocketBase automatically generates thumbnails for images:
// Original image URL
originalURL := fmt.Sprintf(
"%s/api/files/%s/%s/%s",
baseURL, collectionName, recordId, filename,
)
// Thumbnail URL (100x100)
thumbURL := fmt.Sprintf(
"%s/api/files/%s/%s/%s?thumb=100x100",
baseURL, collectionName, recordId, filename,
)
Supported thumbnail formats:
100x100 - Exact size with crop
100x100f - Fit within bounds
100x0 - Width only, auto height
0x100 - Height only, auto width
File tokens
Generate temporary tokens for protected files:
record, _ := app.FindRecordById("posts", "RECORD_ID")
// Generate file token (valid for 2 minutes)
token, err := record.NewFileToken()
if err != nil {
log.Fatal(err)
}
// URL with token
url := fmt.Sprintf(
"%s/api/files/%s/%s/%s?token=%s",
baseURL, collectionName, recordId, filename, token,
)
Context management
Set a custom context for filesystem operations:
import "context"
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
fs, _ := app.NewFilesystem()
defer fs.Close()
fs.SetContext(ctx)
// Operations will use the custom context
err := fs.UploadFile(file, "uploads/large-file.zip")
Best practices
Always close filesystems
fs, err := app.NewFilesystem()
if err != nil {
return err
}
defer fs.Close() // Important!
Validate file types
file, _ := filesystem.NewFileFromMultipart(multipartFile)
allowedTypes := []string{"image/jpeg", "image/png", "image/gif"}
if !slices.Contains(allowedTypes, file.MimeType()) {
return errors.New("invalid file type")
}
Handle large files
const maxFileSize = 10 * 1024 * 1024 // 10MB
file, _ := filesystem.NewFileFromMultipart(multipartFile)
if file.Size > maxFileSize {
return errors.New("file too large")
}
Use transactions for file operations
err := app.RunInTransaction(func(txApp core.App) error {
// Create record
record := core.NewRecord(collection)
record.Set("document", file)
if err := txApp.Save(record); err != nil {
return err // Rollback (file won't be uploaded)
}
return nil // Commit (file is uploaded)
})
Error handling
Check for not found errors
import "github.com/pocketbase/pocketbase/tools/filesystem"
reader, err := fs.GetReader("path/to/file.txt")
if err != nil {
if errors.Is(err, filesystem.ErrNotFound) {
log.Println("File not found")
} else {
log.Fatal(err)
}
}