PocketBase 0.40 changes backups and file handling
The new release reduces database locking during backups, but Go extensions need careful testing before upgrading.
Backups should not hold the application up
Once a small application gets real traffic, backups stop being an afterthought. Scheduling them is not enough: it also matters what happens while they run and whether normal writes are affected. PocketBase 0.40 removes the transactional database lock used while generating backups. That is a meaningful change for instances that keep receiving writes while backups are automated.
I would not read this as a promise of instant backups or as a general consistency guarantee for every setup. The release notes specifically say that backup generation no longer transaction-locks the database. They do not define backup duration, and they do not replace retention, restore testing, or recovery procedures. My practical takeaway is simple: backups may interfere less, but they still need to be restored and verified.
If PocketBase backups run through cron, systemd, Docker, or an external tool, this improvement does not require a new command. What I would do is repeat a realistic test against a database close to the production workload. I would check the restore process, the related files, and how the application behaves while a backup is running. Having a backup file is not the same thing as having a recovery plan.
Writing files from an io.Reader
The other important part of PocketBase 0.40 is its filesystem layer.
NewWriter(fileKey, opts) returns a writer that can create or replace a stored file.
Its ReadFrom method can then copy data from any io.Reader, which is useful for HTTP bodies, downloads, and import jobs.
When the write is done, Close() must be called and its error must be handled too.
The following example uses the filesystem configured by PocketBase, whether that storage is local or S3-backed.
app.NewFilesystem() creates an instance, and that instance should be closed when it is no longer needed.
Because body implements io.Reader, the content does not need to be loaded fully into memory before writing it.
The file key remains my responsibility and should match the storage structure I have chosen.
fsys, err := app.NewFilesystem()
if err != nil {
return err
}
defer fsys.Close()
writer, err := fsys.NewWriter("imports/source.json", nil)
if err != nil {
return err
}
if _, err := writer.ReadFrom(body); err != nil {
_ = writer.Close()
return err
}
return writer.Close()This does not replace the regular workflow for record file fields.
PocketBase still handles persistence, validation, and file deletion when a record with a file field is saved.
I would use NewWriter when I need direct storage access and I know which key should be written.
For attaching a file to a record, I would normally keep using models and app.Save(record).
Hooks around storage operations
A filesystem instance now exposes OnNewWriter() and OnDelete().
They are low-level hooks for reacting to writer initialization and to each Delete(fileKey) call.
They can be useful for internal auditing, policy checks, or instrumentation around storage operations performed through that instance.
I would not treat them as application-wide events.
Two details make a real difference in how I would use them.
OnNewWriter() runs when a writer is created, including through operations such as Upload, but it currently does not run for Copy.
OnDelete() does not run when a file is overwritten, because replacing a file does not invoke Delete.
If an audit trail must cover copies and replacements, these hooks alone are not sufficient.
It is also important that the hooks belong to a filesystem.System instance.
The release notes state that they are not exposed through core.App, to avoid a compatibility break.
Registering a hook on a short-lived instance created with app.NewFilesystem() therefore does not create a global PocketBase observer.
Before building critical logic around them, I would test which paths in my own code actually use that instance.
The upgrade issue for Go extensions
The less comfortable part of this release affects developers who compile PocketBase as a Go application with custom extensions.
PocketBase 0.40 raises the minimum Go version to 1.27.0 and migrates to encoding/json/v2.
Even if JSON sits below the APIs I call directly, the release notes warn that full backward compatibility is not guaranteed.
I would not deploy an updated custom binary to production without a test run.
The first step is aligning the build environment and dependencies. In an embedded PocketBase application, the module declaration can look like this:
module example.com/my-pocketbase
go 1.27.0
require github.com/pocketbase/pocketbase v0.40.0I would then rebuild from scratch and test the areas where the extension serializes or deserializes data. Custom endpoints, webhook payloads, structs with JSON tags, and integrations that depend on exact formats deserve special attention. There is no need to predict a particular breakage to be cautious: a new JSON implementation is enough reason to run proper tests. If I only use prebuilt binaries and have no Go extensions, this part has less direct impact, but I would still validate the deployment.
The small print before upgrading
This release does not turn backups into a complete disaster-recovery strategy. It does not remove the need for off-server copies, restore tests, and storage monitoring. It also does not make filesystem hooks cover every possible way files can change. These are useful primitives, not a complete auditing layer.
With NewWriter, closing the writer matters as much as writing to it.
An error from Close() can mean that the file was not fully persisted, so ignoring it is a bad pattern.
The method also replaces an existing file when the key already exists, so keys should be generated or validated before writing starts.
I would not build keys directly from untrusted user input without normalizing and reviewing them.
My upgrade order would be straightforward: test a copy of the application, rebuild extensions with Go 1.27.0, review JSON-related flows, and run a backup followed by a restore. After that, I would validate the paths that write files, especially if I add hooks for auditing or internal controls. PocketBase 0.40 brings specific improvements, but it is a release to upgrade with tests rather than by habit. That preparation is cheaper than finding a compatibility issue after deployment.