The core.App interface is the main entry point for all PocketBase functionality. It provides methods for database operations, file storage, settings management, and event hooks.
Creating an app instance
New()
Create a new PocketBase instance with default configuration:
NewWithConfig()
Create a PocketBase instance with custom configuration:
app := pocketbase.NewWithConfig(pocketbase.Config{
DefaultDev: true,
DefaultDataDir: "./pb_data",
HideStartBanner: false,
})
Lifecycle methods
Bootstrap()
Initialize the application (create data directory, open database connections, load settings):
if err := app.Bootstrap(); err != nil {
log.Fatal(err)
}
The application is automatically bootstrapped when you call Start(). You only need to manually call Bootstrap() if you need to initialize the app before starting.
Start()
Start the application and register default system commands:
if err := app.Start(); err != nil {
log.Fatal(err)
}
ResetBootstrapState()
Release initialized resources (close database connections, stop cron ticker):
if err := app.ResetBootstrapState(); err != nil {
log.Fatal(err)
}
App properties
DataDir()
Get the app data directory path:
dataDir := app.DataDir() // "./pb_data"
IsDev()
Check if the app is in development mode:
if app.IsDev() {
log.Println("Running in development mode")
}
IsBootstrapped()
Check if the application was initialized:
if app.IsBootstrapped() {
// App is ready
}
Logger()
Get the default app logger:
app.Logger().Info("Application started", "version", pocketbase.Version)
Settings()
Get the loaded app settings:
settings := app.Settings()
log.Println("App name:", settings.Meta.AppName)
Store()
Get the app runtime store (for temporary data):
app.Store().Set("custom_key", "custom_value")
value := app.Store().Get("custom_key")
Database access
DB()
Get the main database builder instance (automatically routes SELECT to concurrent pool):
var count int64
err := app.DB().Select("count(*)").From("_collections").Row(&count)
ConcurrentDB()
Get the concurrent database instance (for read operations):
NonconcurrentDB()
Get the nonconcurrent database instance (for write operations):
db := app.NonconcurrentDB()
Transactions
RunInTransaction()
Execute a function within a database transaction:
err := app.RunInTransaction(func(txApp core.App) error {
record := core.NewRecord(collection)
record.Set("name", "John")
if err := txApp.Save(record); err != nil {
return err // Transaction will rollback
}
return nil // Transaction will commit
})
Always use the txApp instance passed to your callback function, not the original app instance, to ensure operations are part of the transaction.
Filesystem
NewFilesystem()
Create a new filesystem instance for managing files:
fs, err := app.NewFilesystem()
if err != nil {
log.Fatal(err)
}
defer fs.Close()
// Use the filesystem
exists, _ := fs.Exists("path/to/file.txt")
NewBackupsFilesystem()
Create a new filesystem instance for managing backups:
fs, err := app.NewBackupsFilesystem()
if err != nil {
log.Fatal(err)
}
defer fs.Close()
Mail client
NewMailClient()
Create a new mail client based on current settings:
mailer := app.NewMailClient()
message := &mailer.Message{
From: mail.Address{
Name: "Support",
Address: "support@example.com",
},
To: []mail.Address{
{Address: "user@example.com"},
},
Subject: "Welcome!",
HTML: "<h1>Welcome to our app</h1>",
}
if err := mailer.Send(message); err != nil {
log.Fatal(err)
}
Backups
CreateBackup()
Create a backup of the current app data:
ctx := context.Background()
err := app.CreateBackup(ctx, "backup-2024.zip")
if err != nil {
log.Fatal(err)
}
RestoreBackup()
Restore a backup and restart the application:
ctx := context.Background()
err := app.RestoreBackup(ctx, "backup-2024.zip")
if err != nil {
log.Fatal(err)
}
This feature is experimental and currently expected to work only on UNIX-based systems. The application process will be replaced using execve.
Other utilities
Cron()
Get the app cron instance for scheduling tasks:
app.Cron().Add("daily_cleanup", "0 0 * * *", func() {
// Cleanup logic
})
SubscriptionsBroker()
Get the realtime subscriptions broker:
broker := app.SubscriptionsBroker()
ReloadSettings()
Reinitialize and reload app settings:
if err := app.ReloadSettings(); err != nil {
log.Fatal(err)
}
Auxiliary database methods
PocketBase uses two databases: data.db for main data and auxiliary.db for logs and temporary data.
AuxDB()
Get the auxiliary database builder instance (automatically routes SELECT to concurrent pool):
var count int64
err := app.AuxDB().Select("count(*)").From("_logs").Row(&count)
AuxConcurrentDB()
Get the concurrent auxiliary database instance (for read operations):
db := app.AuxConcurrentDB()
AuxNonconcurrentDB()
Get the nonconcurrent auxiliary database instance (for write operations):
db := app.AuxNonconcurrentDB()
AuxHasTable()
Check if a table or view exists in the auxiliary database:
if app.AuxHasTable("_logs") {
log.Println("Logs table exists")
}
AuxVacuum()
Reclaim unused disk space in the auxiliary database:
if err := app.AuxVacuum(); err != nil {
log.Fatal(err)
}
AuxModelQuery()
Create a preconfigured SELECT query for the auxiliary database:
log := &core.Log{}
query := app.AuxModelQuery(log)
err := query.One(log)
AuxSave()
Validate and save a model to the auxiliary database:
log := &core.Log{
Level: int(slog.LevelInfo),
Message: "Application started",
}
if err := app.AuxSave(log); err != nil {
log.Fatal(err)
}
AuxSaveNoValidate()
Save a model to the auxiliary database without validation:
if err := app.AuxSaveNoValidate(log); err != nil {
log.Fatal(err)
}
AuxDelete()
Delete a model from the auxiliary database:
if err := app.AuxDelete(log); err != nil {
log.Fatal(err)
}
AuxRunInTransaction()
Execute a function within an auxiliary database transaction:
err := app.AuxRunInTransaction(func(txApp core.App) error {
log1 := &core.Log{Message: "First log"}
log2 := &core.Log{Message: "Second log"}
if err := txApp.AuxSave(log1); err != nil {
return err
}
if err := txApp.AuxSave(log2); err != nil {
return err
}
return nil // Both logs will be committed together
})
It’s safe to nest AuxRunInTransaction calls as long as you use the callback’s txApp instance.
Table operations
HasTable()
Check if a table or view exists in the main database:
if app.HasTable("users") {
log.Println("Users table exists")
}
TableColumns()
Get all column names of a table:
columns, err := app.TableColumns("users")
if err != nil {
log.Fatal(err)
}
log.Println("Columns:", columns) // ["id", "email", "username", "created", ...]
TableInfo()
Get detailed information about table columns:
info, err := app.TableInfo("users")
if err != nil {
log.Fatal(err)
}
for _, col := range info {
log.Printf("Column: %s, Type: %s, NotNull: %v\n",
col.Name, col.Type, col.NotNull)
}
TableIndexes()
Get all indexes for a table:
indexes, err := app.TableIndexes("users")
if err != nil {
log.Fatal(err)
}
for name, sql := range indexes {
log.Printf("Index: %s\nSQL: %s\n", name, sql)
}
DeleteTable()
Drop a table from the database:
if err := app.DeleteTable("old_table"); err != nil {
log.Fatal(err)
}
This method is vulnerable to SQL injection. The table name must come only from trusted input.
DeleteView()
Drop a view from the database:
if err := app.DeleteView("users_view"); err != nil {
log.Fatal(err)
}
This method is vulnerable to SQL injection. The view name must come only from trusted input.
SaveView()
Create or update a persistent SQL view:
viewQuery := `
SELECT id, username, email, created
FROM users
WHERE verified = TRUE
`
if err := app.SaveView("verified_users", viewQuery); err != nil {
log.Fatal(err)
}
This method is vulnerable to SQL injection. Arguments must come only from trusted input.
CreateViewFields()
Create a FieldsList from a SELECT query for use in view collections:
query := `
SELECT
(ROW_NUMBER() OVER()) as id,
users.username,
COUNT(posts.id) as post_count
FROM users
LEFT JOIN posts ON posts.author = users.id
GROUP BY users.id
`
fields, err := app.CreateViewFields(query)
if err != nil {
log.Fatal(err)
}
// Use fields when creating a view collection
collection := &core.Collection{
Name: "user_stats",
Type: "view",
Fields: fields,
}
The SELECT query must have an “id” column and cannot use wildcard (*) columns.
Transaction helpers
TxInfo()
Get the transaction info associated with the current app instance:
if app.IsTransactional() {
info := app.TxInfo()
// Execute a callback after the transaction completes
info.OnComplete(func(txErr error) error {
if txErr != nil {
log.Println("Transaction failed:", txErr)
} else {
log.Println("Transaction succeeded")
}
return nil
})
}
This is useful when you need to execute logic after a transaction completes, such as sending notifications or cleaning up resources.
IsTransactional()
Check if the current app instance is part of a transaction:
if app.IsTransactional() {
log.Println("Running inside a transaction")
} else {
log.Println("Not in a transaction")
}
UnsafeWithoutHooks()
Get a shallow copy of the app without any registered hooks:
unsafeApp := app.UnsafeWithoutHooks()
// Use with caution - no validation or normalization hooks will run
Using the unsafe app instance may cause data integrity errors since record validations and normalizations rely on hooks.
Additional model methods
SaveNoValidate()
Save a model to the database without running validations:
record := core.NewRecord(collection)
record.Set("name", "John")
// Skip validation hooks
if err := app.SaveNoValidate(record); err != nil {
log.Fatal(err)
}
Use Save() instead if you want to run validations before persisting.
SaveWithContext()
Save a model with a context to limit execution time:
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := app.SaveWithContext(ctx, record); err != nil {
log.Fatal(err)
}
DeleteWithContext()
Delete a model with a context to limit execution time:
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := app.DeleteWithContext(ctx, record); err != nil {
log.Fatal(err)
}
Validate()
Trigger the OnModelValidate hook for a model without saving:
record := core.NewRecord(collection)
record.Set("email", "invalid-email")
if err := app.Validate(record); err != nil {
log.Println("Validation failed:", err)
}
ValidateWithContext()
Validate a model with a custom context:
ctx := context.WithValue(context.Background(), "requestId", "12345")
if err := app.ValidateWithContext(ctx, record); err != nil {
log.Fatal(err)
}
Type signature
The PocketBase struct embeds the core.App interface:
type PocketBase struct {
core.App
RootCmd *cobra.Command
}
The core.App interface is implemented by core.BaseApp:
type App interface {
// Lifecycle methods
Bootstrap() error
IsBootstrapped() bool
ResetBootstrapState() error
// Configuration
DataDir() string
IsDev() bool
Logger() *slog.Logger
Settings() *Settings
// Database
DB() dbx.Builder
RunInTransaction(fn func(txApp App) error) error
// ... and many more methods
}