# https://pocketbase.io/docs/ llms-full.txt
## PocketBase Authentication Guide
Authentication
- [Overview](https://pocketbase.io/docs/authentication/#overview)
- [Authenticate with password](https://pocketbase.io/docs/authentication/#authenticate-with-password)
- [Authenticate with OTP](https://pocketbase.io/docs/authentication/#authenticate-with-otp)
- [Authenticate with OAuth2](https://pocketbase.io/docs/authentication/#authenticate-with-oauth2)
- [Multi-factor authentication](https://pocketbase.io/docs/authentication/#multi-factor-authentication)
- [Users impersonation](https://pocketbase.io/docs/authentication/#users-impersonation)
- [API keys](https://pocketbase.io/docs/authentication/#api-keys)
- [Auth token verification](https://pocketbase.io/docs/authentication/#auth-token-verification)
### [Overview](https://pocketbase.io/docs/authentication/\#overview)
A single client is considered authenticated as long as it sends valid
`Authorization:YOUR_AUTH_TOKEN` header with the request.
The PocketBase Web APIs are fully stateless and there are no sessions in the traditional sense (even the
tokens are not stored in the database).
Because there are no sessions and we don't store the tokens on the server there is also no logout
endpoint. To "logout" a user you can simply disregard the token from your local state (aka.
`pb.authStore.clear()` if you use the SDKs).
The auth token could be generated either through the specific auth collection Web APIs or programmatically
via Go/JS.
All allowed auth collection methods can be configured individually from the specific auth collection
options.
Note that PocketBase admins (aka. `_superusers`) are similar to the regular auth
collection records with 2 caveats:
- OAuth2 is not supported as auth method for the `_superusers` collection
- Superusers can access and modify anything (collection API rules are ignored)
### [Authenticate with password](https://pocketbase.io/docs/authentication/\#authenticate-with-password)
To authenticate with password you must enable the _Identity/Password_ auth collection option
_(see also_
_[Web API reference](https://pocketbase.io/docs/api-records/#auth-with-password)_
_)_.
The default identity field is the `email` but you can configure any other unique field like
"username" (it must have a UNIQUE index).
JavaScript
Dart
`import PocketBase from 'pocketbase';
const pb = new PocketBase('http://127.0.0.1:8090');
...
const authData = await pb.collection("users").authWithPassword('test@example.com', '1234567890');
// after the above you can also access the auth data from the authStore
console.log(pb.authStore.isValid);
console.log(pb.authStore.token);
console.log(pb.authStore.record.id);
// "logout" the last authenticated record
pb.authStore.clear();`
`import 'package:pocketbase/pocketbase.dart';
final pb = PocketBase('http://127.0.0.1:8090');
...
final authData = await pb.collection("users").authWithPassword('test@example.com', '1234567890');
// after the above you can also access the auth data from the authStore
print(pb.authStore.isValid);
print(pb.authStore.token);
print(pb.authStore.record.id);
// "logout" the last authenticated record
pb.authStore.clear();`
### [Authenticate with OTP](https://pocketbase.io/docs/authentication/\#authenticate-with-otp)
To authenticate with email code you must enable the _One-time password (OTP)_
auth collection option
_(see also_
_[Web API reference](https://pocketbase.io/docs/api-records/#auth-with-otp)_
_)_.
The usual flow is the user typing manually the received password from their email but you can also
adjust the default email template from the collection options and add a url containing the OTP and its
id as query parameters
_(you have access to `{OTP}` and `{OTP_ID}` placeholders)_.
Note that when requesting an OTP we return an `otpId` even if a user with the provided email
doesn't exist as a very rudimentary enumeration protection (it doesn't create or send anything).
On successful OTP validation, by default the related user email will be automatically marked as
"verified".
Keep in mind that OTP as a standalone authentication method could be less secure compared to the
other methods because the generated password is usually 0-9 digits and there is a risk of it being
guessed or enumerated (especially when a longer duration time is configured).
For security critical applications OTP is recommended to be used in combination with the other
auth methods and the [Multi-factor authentication](https://pocketbase.io/docs/authentication/#multi-factor-authentication) option.
JavaScript
Dart
`import PocketBase from 'pocketbase';
const pb = new PocketBase('http://127.0.0.1:8090');
...
// send OTP email to the provided auth record
const result = await pb.collection('users').requestOTP('test@example.com');
// ... show a screen/popup to enter the password from the email ...
// authenticate with the requested OTP id and the email password
const authData = await pb.collection('users').authWithOTP(result.otpId, "YOUR_OTP");
// after the above you can also access the auth data from the authStore
console.log(pb.authStore.isValid);
console.log(pb.authStore.token);
console.log(pb.authStore.record.id);
// "logout"
pb.authStore.clear();`
`import 'package:pocketbase/pocketbase.dart';
final pb = PocketBase('http://127.0.0.1:8090');
...
// send OTP email to the provided auth record
final result = await pb.collection('users').requestOTP('test@example.com');
// ... show a screen/popup to enter the password from the email ...
// authenticate with the requested OTP id and the email password
final authData = await pb.collection('users').authWithOTP(result.otpId, "YOUR_OTP");
// after the above you can also access the auth data from the authStore
print(pb.authStore.isValid);
print(pb.authStore.token);
print(pb.authStore.record.id);
// "logout"
pb.authStore.clear();`
### [Authenticate with OAuth2](https://pocketbase.io/docs/authentication/\#authenticate-with-oauth2)
You can also authenticate your users with an OAuth2 provider (Google, GitHub, Microsoft, etc.). See the
section below for example integrations.
Before starting, you'll need to create an OAuth2 app in the provider's dashboard in order to get a
**Client Id** and **Client Secret**, and register a redirect URL
.
Once you have obtained the **Client Id** and **Client Secret**, you can
enable and configure the provider from your PocketBase auth collection options ( _PocketBase > Collections > {YOUR\_COLLECTION} > Edit collection (settings cogwheel) > Options_
_\> OAuth2_).
All in one ( _recommended_)Manual code exchange
This method handles everything within a single call without having to define custom redirects,
deeplinks or even page reload.
**When creating your OAuth2 app, for a callback/redirect URL you have to use the**
**`https://yourdomain.com/api/oauth2-redirect`**
( _or when testing locally - `http://127.0.0.1:8090/api/oauth2-redirect`_).
JavaScript
Dart
`import PocketBase from 'pocketbase';
const pb = new PocketBase('https://pocketbase.io');
...
// This method initializes a one-off realtime subscription and will
// open a popup window with the OAuth2 vendor page to authenticate.
//
// Once the external OAuth2 sign-in/sign-up flow is completed, the popup
// window will be automatically closed and the OAuth2 data sent back
// to the user through the previously established realtime connection.
//
// If the popup is being blocked on Safari, you can try the suggestion from:
// https://github.com/pocketbase/pocketbase/discussions/2429#discussioncomment-5943061.
const authData = await pb.collection('users').authWithOAuth2({ provider: 'google' });
// after the above you can also access the auth data from the authStore
console.log(pb.authStore.isValid);
console.log(pb.authStore.token);
console.log(pb.authStore.record.id);
// "logout" the last authenticated record
pb.authStore.clear();`
`import 'package:pocketbase/pocketbase.dart';
import 'package:url_launcher/url_launcher.dart';
final pb = PocketBase('https://pocketbase.io');
...
// This method initializes a one-off realtime subscription and will
// call the provided urlCallback with the OAuth2 vendor url to authenticate.
//
// Once the external OAuth2 sign-in/sign-up flow is completed, the browser
// window will be automatically closed and the OAuth2 data sent back
// to the user through the previously established realtime connection.
final authData = await pb.collection('users').authWithOAuth2('google', (url) async {
// or use something like flutter_custom_tabs to make the transitions between native and web content more seamless
await launchUrl(url);
});
// after the above you can also access the auth data from the authStore
print(pb.authStore.isValid);
print(pb.authStore.token);
print(pb.authStore.record.id);
// "logout" the last authenticated record
pb.authStore.clear();`
When authenticating manually with OAuth2 code you'll need 2 endpoints:
- somewhere to show the "Login with ..." links
- somewhere to handle the provider's redirect in order to exchange the auth code for token
Here is a simple web example:
1. **Links page**
(e.g. https://127.0.0.1:8090 serving `pb_public/index.html`):
``
`
When using the "Manual code exchange" flow for sign-in with Apple your redirect
handler must accept `POST` requests in order to receive the name and the
email of the Apple user. If you just need the Apple user id, you can keep the redirect
handler `GET` but you'll need to replace in the Apple authorization url
`response_mode=form_post` with `response_mode=query`.
### [Multi-factor authentication](https://pocketbase.io/docs/authentication/\#multi-factor-authentication)
PocketBase v0.23+ introduced optional Multi-factor authentication (MFA).
If enabled, it requires the user to authenticate with any 2 different auth methods from above (the
order doesn't matter).
The expected flow is:
1. User authenticates with "Auth method A".
2. On success, a 401 response is sent with `{"mfaId": "..."}` as JSON body (the MFA
"session" is stored in the `_mfas` system collection).
3. User authenticates with "Auth method B" as usual
**but adds the `mfaId` from the previous step as body or query parameter**.
4. On success, a regular auth response is returned, aka. token + auth record data.
Below is an example for email/password + OTP authentication:
JavaScript
Dart
`import PocketBase from 'pocketbase';
const pb = new PocketBase('http://127.0.0.1:8090');
...
try {
await pb.collection('users').authWithPassword('test@example.com', '1234567890');
} catch (err) {
const mfaId = err.response?.mfaId;
if (!mfaId) {
throw err; // not mfa -> rethrow
}
// the user needs to authenticate again with another auth method, for example OTP
const result = await pb.collection('users').requestOTP('test@example.com');
// ... show a modal for users to check their email and to enter the received code ...
await pb.collection('users').authWithOTP(result.otpId, 'EMAIL_CODE', { 'mfaId': mfaId });
}`
`import 'package:pocketbase/pocketbase.dart';
final pb = PocketBase('http://127.0.0.1:8090');
...
try {
await pb.collection('users').authWithPassword('test@example.com', '1234567890');
} on ClientException catch (e) {
final mfaId = e.response['mfaId'];
if (mfaId == null) {
throw e; // not mfa -> rethrow
}
// the user needs to authenticate again with another auth method, for example OTP
final result = await pb.collection('users').requestOTP('test@example.com');
// ... show a modal for users to check their email and to enter the received code ...
await pb.collection('users').authWithOTP(result.otpId, 'EMAIL_CODE', query: { 'mfaId': mfaId });
}`
### [Users impersonation](https://pocketbase.io/docs/authentication/\#users-impersonation)
Superusers have the option to generate tokens and authenticate as anyone else via the
[Impersonate endpoint](https://pocketbase.io/docs/api-records#impersonate)
.
The generated impersonate auth tokens can have custom duration but are not refreshable!
For convenience the official SDKs creates and returns a standalone client that keeps the token state
in memory, aka. only for the duration of the impersonate client instance.
JavaScript
Dart
`import PocketBase from 'pocketbase';
const pb = new PocketBase('http://127.0.0.1:8090');
...
// authenticate as superuser
await pb.collection("_superusers").authWithPassword("test@example.com", "1234567890");
// impersonate
// (the custom token duration is in seconds and it is optional)
const impersonateClient = await pb.collection("users").impersonate("USER_RECORD_ID", 3600)
// log the impersonate token and user data
console.log(impersonateClient.authStore.token);
console.log(impersonateClient.authStore.record);
// send requests as the impersonated user
const items = await impersonateClient.collection("example").getFullList();`
`import 'package:pocketbase/pocketbase.dart';
final pb = PocketBase('http://127.0.0.1:8090');
...
// authenticate as superuser
await pb.collection("_superusers").authWithPassword("test@example.com", "1234567890");
// impersonate
// (the custom token duration is in seconds and it is optional)
final impersonateClient = await pb.collection("users").impersonate("USER_RECORD_ID", 3600)
// log the impersonate token and user data
print(impersonateClient.authStore.token);
print(impersonateClient.authStore.record);
// send requests as the impersonated user
final items = await impersonateClient.collection("example").getFullList();`
### [API keys](https://pocketbase.io/docs/authentication/\#api-keys)
While PocketBase doesn't have "API keys" in the traditional sense, as a side effect of the support for
users impersonation, for such cases you can use instead the generated non-refreshable
`_superusers` impersonate auth token.
You can generate such token via the above impersonate API or from the
_Dashboard > Collections > \_superusers > {select superuser} > "Impersonate" dropdown option_:

Because of the security implications (superusers can execute, access and modify anything), use the
generated `_superusers` tokens with extreme care and only for internal
**server-to-server** communication.
To invalidate already issued tokens, you need to change the individual superuser account password
(or if you want to reset the tokens for all superusers - change the shared auth token secret from
the `_superusers` collection options).
### [Auth token verification](https://pocketbase.io/docs/authentication/\#auth-token-verification)
PocketBase doesn't have a dedicated token verification endpoint, but if you want to verify an existing
auth token from a 3rd party app you can send an
[Auth refresh](https://pocketbase.io/docs/api-records/#auth-refresh)
call, aka. `pb.collection("users").authRefresh()`.
On valid token - it returns a new token with refreshed `exp` claim and the latest user data.
Otherwise - returns an error response.
Note that calling `authRefresh` doesn't invalidate previously issued tokens and you can safely disregard
the new one if you don't need it (as mentioned in the beginning - PocketBase doesn't store the tokens on the
server).
Performance wise, the used `HS256` algorithm for generating the JWT has very little to no
impact and it is essentially the same in terms of response time as calling
`getOne("USER_ID")` _(see_
_[benchmarks](https://github.com/pocketbase/benchmarks/blob/master/results/hetzner_cax11.md#user-auth-refresh))_.
* * *
[Prev: API rules and filters](https://pocketbase.io/docs/api-rules-and-filters) [Next: Files upload and handling](https://pocketbase.io/docs/files-handling)
## PocketBase Collections
Collections
- [Overview](https://pocketbase.io/docs/collections/#overview)
- [Base collection](https://pocketbase.io/docs/collections/#base-collection)
- [View collection](https://pocketbase.io/docs/collections/#view-collection)
- [Auth collection](https://pocketbase.io/docs/collections/#auth-collection)
- [Fields](https://pocketbase.io/docs/collections/#fields)
### [Overview](https://pocketbase.io/docs/collections/\#overview)
**Collections** represent your application data. Under the hood they are backed by plain
SQLite tables that are generated automatically with the collection
**name** and **fields** (columns).
Single entry of a collection is called **record** (a single row in the SQL table).
You can manage your **collections** from the Dashboard, with the Web APIs using the
[client-side SDKs](https://pocketbase.io/docs/how-to-use/)
( _superusers only_) or programmatically via the
[Go](https://pocketbase.io/docs/go-migrations/)/ [JavaScript](https://pocketbase.io/docs/js-migrations/)
migrations.
Similarly, you can manage your **records** from the Dashboard, with the Web APIs using the
[client-side SDKs](https://pocketbase.io/docs/how-to-use/)
or programmatically via the
[Go](https://pocketbase.io/docs/go-records/)/ [JavaScript](https://pocketbase.io/docs/js-records/)
Record operations.
Here is what a collection edit panel looks like in the Dashboard:

Currently there are 3 collection types: **Base**, **View** and
**Auth**.
##### [Base collection](https://pocketbase.io/docs/collections/\#base-collection)
**Base collection** is the default collection type and it could be used to store any application
data (articles, products, posts, etc.).
##### [View collection](https://pocketbase.io/docs/collections/\#view-collection)
**View collection** is a read-only collection type where the data is populated from a plain
SQL `SELECT` statement, allowing users to perform aggregations or any other custom queries in
general.
For example, the following query will create a read-only collection with 3 _posts_
fields - _id_, _name_ and _totalComments_:
`SELECT
posts.id,
posts.name,
count(comments.id) as totalComments
FROM posts
LEFT JOIN comments on comments.postId = posts.id
GROUP BY posts.id`
View collections don't receive realtime events because they don't have create/update/delete
operations.
##### [Auth collection](https://pocketbase.io/docs/collections/\#auth-collection)
**Auth collection** has everything from the **Base collection** but with some additional
special fields to help you manage your app users and also providing various authentication options.
Each Auth collection has the following special system fields:
`email`, `emailVisibility`, `verified`,
`password` and `tokenKey`.
They cannot be renamed or deleted but can be configured using their specific field options. For example you
can make the user email required or optional.
You can have as many Auth collections as you want (users, managers, staffs, members, clients, etc.) each
with their own set of fields, separate login and records managing endpoints.
You can build all sort of different access controls:
- **Role (Group)**
_For example, you could attach a "role" `select` field to your Auth collection with the_
_following options: "employee" and "staff". And then in some of your other collections you could_
_define the following rule to allow only "staff":_
_`@request.auth.role = "staff"`_
- **Relation (Ownership)**
_Let's say that you have 2 collections - "posts" base collection and "users" auth collection. In_
_your "posts" collection you can create "author"_
_`relation` field pointing to the "users" collection. To allow access to only the_
_"author" of the record(s), you could use a rule like:_
_`@request.auth.id != "" && author = @request.auth.id`_
_Nested relation fields look ups, including back-relations, are also supported, for example:_
_`someRelField.anotherRelField.author = @request.auth.id`_
- **Managed**
_In addition to the default "List", "View", "Create", "Update", "Delete" API rules, Auth_
_collections have also a special "Manage" API rule that could be used to allow one user (it could_
_be even from a different collection) to be able to fully manage the data of another user (e.g._
_changing their email, password, etc.)._
- **Mixed**
_You can build a mixed approach based on your unique use-case. Multiple rules can be grouped with_
_parenthesis `()` and combined with `&&`_
_(AND) and `||` (OR) operators:_
_`@request.auth.id != "" && (@request.auth.role = "staff" || author = @request.auth.id)`_
### [Fields](https://pocketbase.io/docs/collections/\#fields)
All collection fields _(with exception of the `JSONField`)_ are
**non-nullable and uses a zero-default** for their respective type as fallback value
when missing (empty string for `text`, 0 for `number`, etc.).
All field specific modifiers are supported both in the Web APIs and via the record Get/Set
methods.
**[BoolField](https://pocketbase.io/docs/collections/#boolfield)**
BoolField defines `bool` type field to store a single `false`
(default) or `true` value.
**[NumberField](https://pocketbase.io/docs/collections/#numberfield)**
NumberField defines `number` type field for storing numeric/float64 value:
`0` (default), `2`, `-1`, `1.5`.
The following additional set modifiers are available:
- `fieldName+`
adds number to the already existing record value.
- `fieldName-`
subtracts number from the already existing record value.
**[TextField](https://pocketbase.io/docs/collections/#textfield)**
TextField defines `text` type field for storing string values:
`""` (default), `"example"`.
The following additional set modifiers are available:
- `fieldName:autogenerate`
autogenerate a field value if the `AutogeneratePattern` field option is set.
For example, submitting:
`{"slug:autogenerate":"abc-"}` will result in `"abc-[random]"` `slug` field value.
**[EmailField](https://pocketbase.io/docs/collections/#emailfield)**
EmailField defines `email` type field for storing a single email string address:
`""` (default), `"john@example.com"`.
**[URLField](https://pocketbase.io/docs/collections/#urlfield)**
URLField defines `url` type field for storing a single URL string value:
`""` (default), `"https://example.com"`.
**[EditorField](https://pocketbase.io/docs/collections/#editorfield)**
EditorField defines `editor` type field to store HTML formatted text:
`""` (default), `
example
`.
**[DateField](https://pocketbase.io/docs/collections/#datefield)**
DateField defines `date` type field to store a single datetime string value:
`""` (default), `"2022-01-01 00:00:00.000Z"`.
All PocketBase dates at the moment follows the RFC3399 format `Y-m-d H:i:s.uZ`
(e.g. `2024-11-10 18:45:27.123Z`).
Dates are compared as strings, meaning that when using the filters with a date field you'll
have to specify the full datetime string format. For example to target a single day (e.g.
November 19, 2024) you can use something like:
`created >= '2024-11-19 00:00:00.000Z' && created <= '2024-11-19 23:59:59.999Z'`
**[AutodateField](https://pocketbase.io/docs/collections/#autodatefield)**
AutodateField defines an `autodate` type field and it is similar to the DateField but
its value is auto set on record create/update.
This field is usually used for defining timestamp fields like "created" and "updated".
**[SelectField](https://pocketbase.io/docs/collections/#selectfield)**
SelectField defines `select` type field for storing single or multiple string values
from a predefined list.
It is usually intended for handling enums-like values such as
`pending/public/private`
statuses, simple `client/staff/manager/admin` roles, etc.
For **single** `select` _(the `MaxSelect` option is <= 1)_
the field value is a string:
`""`, `"optionA"`.
For **multiple** `select` _(the `MaxSelect` option is >= 2)_
the field value is an array:
`[]`, `["optionA", "optionB"]`.
The following additional set modifiers are available:
- `fieldName+`
appends one or more values to the existing one.
- `+fieldName`
prepends one or more values to the existing one.
- `fieldName-`
subtracts/removes one or more values from the existing one.
For example: `{"permissions+": "optionA", "roles-": ["staff", "editor"]}`
**[FileField](https://pocketbase.io/docs/collections/#filefield)**
FileField defines `file` type field for managing record file(s).
PocketBase stores in the database only the file name. The file itself is stored either on the
local disk or in S3, depending on your application storage settings.
For **single** `file` _(the `MaxSelect` option is <= 1)_
the stored value is a string:
`""`, `"file1_Ab24ZjL.png"`.
For **multiple** `file` _(the `MaxSelect` option is >= 2)_
the stored value is an array:
`[]`, `["file1_Ab24ZjL.png", "file2_Frq24ZjL.txt"]`.
The following additional set modifiers are available:
- `fieldName+`
appends one or more files to the existing field value.
- `+fieldName`
prepends one or more files to the existing field value.
- `fieldName-`
deletes one or more files from the existing field value.
For example:
`{"documents+": new File(...), "documents-": ["file1_Ab24ZjL.txt", "file2_Frq24ZjL.txt"]}`
You can find more detailed information in the
[Files upload and handling](https://pocketbase.io/docs/files-handling/) guide.
**[RelationField](https://pocketbase.io/docs/collections/#relationfield)**
RelationField defines `relation` type field for storing single or multiple collection
record references.
For **single** `relation` _(the `MaxSelect` option is <= 1)_
the field value is a string:
`""`, `"RECOD_ID"`.
For **multiple** `relation` _(the `MaxSelect` option is >= 2)_
the field value is an array:
`[]`, `["RECORD_ID1", "RECORD_ID2"]`.
The following additional set modifiers are available:
- `fieldName+`
appends one or more ids to the existing one.
- `+fieldName`
prepends one or more ids to the existing one.
- `fieldName-`
subtracts/removes one or more ids from the existing one.
For example: `{"users+": "USER_ID", "categories-": ["CAT_ID1", "CAT_ID2"]}`
**[JSONField](https://pocketbase.io/docs/collections/#jsonfield)**
JSONField defines `json` type field for storing any serialized JSON value,
including `null` (default).
* * *
[Prev: How to use PocketBase](https://pocketbase.io/docs/how-to-use) [Next: API rules and filters](https://pocketbase.io/docs/api-rules-and-filters)
## PocketBase Go Routing
Routing
PocketBase routing is built on top of the standard Go
[`net/http.ServeMux`](https://pkg.go.dev/net/http#ServeMux).
The router can be accessed via the `app.OnServe()` hook allowing you to register custom endpoints
and middlewares.
- [Routes](https://pocketbase.io/docs/go-routing/#routes)
- [Registering new routes](https://pocketbase.io/docs/go-routing/#registering-new-routes)
- [Path parameters and matching rules](https://pocketbase.io/docs/go-routing/#path-parameters-and-matching-rules)
- [Reading path parameters](https://pocketbase.io/docs/go-routing/#reading-path-parameters)
- [Retrieving the current auth state](https://pocketbase.io/docs/go-routing/#retrieving-the-current-auth-state)
- [Reading query parameters](https://pocketbase.io/docs/go-routing/#reading-query-parameters)
- [Reading request headers](https://pocketbase.io/docs/go-routing/#reading-request-headers)
- [Writing response headers](https://pocketbase.io/docs/go-routing/#writing-response-headers)
- [Retrieving uploaded files](https://pocketbase.io/docs/go-routing/#retrieving-uploaded-files)
- [Reading request body](https://pocketbase.io/docs/go-routing/#reading-request-body)
- [Writing response body](https://pocketbase.io/docs/go-routing/#writing-response-body)
- [Reading the client IP](https://pocketbase.io/docs/go-routing/#reading-the-client-ip)
- [Request store](https://pocketbase.io/docs/go-routing/#request-store)
- [Middlewares](https://pocketbase.io/docs/go-routing/#middlewares)
- [Registering middlewares](https://pocketbase.io/docs/go-routing/#registering-middlewares)
- [Removing middlewares](https://pocketbase.io/docs/go-routing/#removing-middlewares)
- [Builtin middlewares](https://pocketbase.io/docs/go-routing/#builtin-middlewares)
- [Default globally registered middlewares](https://pocketbase.io/docs/go-routing/#default-globally-registered-middlewares)
- [Error response](https://pocketbase.io/docs/go-routing/#error-response)
- [Helpers](https://pocketbase.io/docs/go-routing/#helpers)
- [Serving static directory](https://pocketbase.io/docs/go-routing/#serving-static-directory)
- [Auth response](https://pocketbase.io/docs/go-routing/#auth-response)
- [Enrich record(s)](https://pocketbase.io/docs/go-routing/#enrich-records)
- [Go http.Handler wrappers](https://pocketbase.io/docs/go-routing/#go-http-handler-wrappers)
- [Sending request to custom routes using the SDKs](https://pocketbase.io/docs/go-routing/#sending-request-to-custom-routes-using-the-sdks)
### [Routes](https://pocketbase.io/docs/go-routing/\#routes)
##### [Registering new routes](https://pocketbase.io/docs/go-routing/\#registering-new-routes)
Every route have a path, handler function and eventually middlewares attached to it. For example:
`app.OnServe().BindFunc(func(se *core.ServeEvent) error {
// register "GET /hello/{name}" route (allowed for everyone)
se.Router.GET("/hello/{name}", func(e *core.RequestEvent) error {
name := e.Request.PathValue("name")
return e.String(http.StatusOK, "Hello " + name)
})
// register "POST /api/myapp/settings" route (allowed only for authenticated users)
se.Router.POST("/api/myapp/settings", func(e *core.RequestEvent) error {
// do something ...
return e.JSON(http.StatusOK, map[string]bool{"success": true})
}).Bind(apis.RequireAuth())
return se.Next()
})`
There are several routes registration methods available, but the most common ones are:
`se.Router.GET(path, action)
se.Router.POST(path, action)
se.Router.PUT(path, action)
se.Router.PATCH(path, action)
se.Router.DELETE(path, action)
// If you want to handle any HTTP method define only a path (e.g. "/example")
// OR if you want to specify a custom one add it as prefix to the path (e.g. "TRACE /example")
se.Router.Any(pattern, action)`
The router also supports creating groups for routes that share the same base path and middlewares. For
example:
`g := se.Router.Group("/api/myapp")
// group middleware
g.Bind(apis.RequireAuth())
// group routes
g.GET("", action1)
g.GET("/example/{id}", action2)
g.PATCH("/example/{id}", action3).BindFunc(
/* custom route specific middleware func */
)
// nested group
sub := g.Group("/sub")
sub.GET("/sub1", action4)`
The example registers the following endpoints
(all require authenticated user access):
- GET /api/myapp -> action1
- GET /api/myapp/example/{id} -> action2
- PATCH /api/myapp/example/{id} -> action3
- GET /api/myapp/example/sub/sub1 -> action4
Each router group and route could define [middlewares](https://pocketbase.io/docs/go-routing/#middlewares) in a similar manner to the
regular app hooks via the `Bind/BindFunc` methods, allowing you to perform various BEFORE or AFTER
action operations (e.g. inspecting request headers, custom access checks, etc.).
##### [Path parameters and matching rules](https://pocketbase.io/docs/go-routing/\#path-parameters-and-matching-rules)
Because PocketBase routing is based on top of the Go standard router mux, we follow the same pattern
matching rules. Below you could find a short overview but for more details please refer to
[`net/http.ServeMux`](https://pkg.go.dev/net/http#ServeMux).
In general, a route pattern looks like `[METHOD ][HOST]/[PATH]`
( _the METHOD prefix is added automatically when using the designated `GET()`,_
_`POST()`, etc. methods)_).
Route paths can include parameters in the format `{paramName}`.
You can also use `{paramName...}` format to specify a parameter that target more than one path
segment.
A pattern ending with a trailing slash `/` acts as anonymous wildcard and matches any requests
that begins with the defined route. If you want to have a trailing slash but to indicate the end of the
URL then you need to end the path with the special
`{$}` parameter.
If your route path starts with `/api/`
consider combining it with your unique app name like `/api/myapp/...` to avoid collisions
with system routes.
Here are some examples:
`// match "GET example.com/index.html"
se.Router.GET("example.com/index.html")
// match "GET /index.html" (for any host)
se.Router.GET("/index.html")
// match "GET /static/", "GET /static/a/b/c", etc.
se.Router.GET("/static/")
// match "GET /static/", "GET /static/a/b/c", etc.
// (similar to the above but with a named wildcard parameter)
se.Router.GET("/static/{path...}")
// match only "GET /static/" (if no "/static" is registered, it is 301 redirected)
se.Router.GET("/static/{$}")
// match "GET /customers/john", "GET /customer/jane", etc.
se.Router.GET("/customers/{name}")`
* * *
In the following examples `e` is usually
[`*core.RequestEvent`](https://pkg.go.dev/github.com/pocketbase/pocketbase/core#RequestEvent) value.
* * *
##### [Reading path parameters](https://pocketbase.io/docs/go-routing/\#reading-path-parameters)
`id := e.Request.PathValue("id")`
##### [Retrieving the current auth state](https://pocketbase.io/docs/go-routing/\#retrieving-the-current-auth-state)
The request auth state can be accessed (or set) via the `RequestEvent.Auth` field.
`authRecord := e.Auth
isGuest := e.Auth == nil
// the same as "e.Auth != nil && e.Auth.IsSuperuser()"
isSuperuser := e.HasSuperuserAuth()`
Alternatively you could also access the request data from the summarized request info instance
_(usually used in hooks like the `OnRecordEnrich` where there is no direct access to the request)_.
`info, err := e.RequestInfo()
authRecord := info.Auth
isGuest := info.Auth == nil
// the same as "info.Auth != nil && info.Auth.IsSuperuser()"
isSuperuser := info.HasSuperuserAuth()`
##### [Reading query parameters](https://pocketbase.io/docs/go-routing/\#reading-query-parameters)
`search := e.Request.URL.Query().Get("search")
// or via the parsed request info
info, err := e.RequestInfo()
search := info.Query["search"]`
##### [Reading request headers](https://pocketbase.io/docs/go-routing/\#reading-request-headers)
`token := e.Request.Header.Get("Some-Header")
// or via the parsed request info
// (the header value is always normalized per the @request.headers.* API rules format)
info, err := e.RequestInfo()
token := info.Headers["some_header"]`
##### [Writing response headers](https://pocketbase.io/docs/go-routing/\#writing-response-headers)
`e.Response.Header().Set("Some-Header", "123")`
##### [Retrieving uploaded files](https://pocketbase.io/docs/go-routing/\#retrieving-uploaded-files)
`// retrieve the uploaded files and parse the found multipart data into a ready-to-use []*filesystem.File
files, err := e.FindUploadedFiles("document")
// or retrieve the raw single multipart/form-data file and header
mf, mh, err := e.Request.FormFile("document")`
##### [Reading request body](https://pocketbase.io/docs/go-routing/\#reading-request-body)
Body parameters can be read either via
[`e.BindBody`](https://pkg.go.dev/github.com/pocketbase/pocketbase/tools/router#Event.BindBody)
OR through the parsed request info ( _requires manual type assertions_).
The `e.BindBody` argument must be a pointer to a struct or `map[string]any`.
The following struct tags are supported
_(the specific binding rules and which one will be used depend on the request Content-Type)_:
- `json` (json body)- uses the builtin Go JSON package for unmarshaling.
- `xml` (xml body) - uses the builtin Go XML package for unmarshaling.
- `form` (form data) - utilizes the custom
[`router.UnmarshalRequestData`](https://pkg.go.dev/github.com/pocketbase/pocketbase/tools/router#UnmarshalRequestData)
method.
NB! When binding structs make sure that they don't have public fields that shouldn't be bindable and it is
advisable such fields to be unexported or define a separate struct with just the safe bindable fields.
``// read/scan the request body fields into a typed struct
data := struct {
// unexported to prevent binding
somethingPrivate string
Title string `json:"title" form:"title"`
Description string `json:"description" form:"description"`
Active bool `json:"active" form:"active"`
}{}
if err := e.BindBody(&data); err != nil {
return e.BadRequestError("Failed to read request data", err)
}
// alternatively, read the body via the parsed request info
info, err := e.RequestInfo()
title, ok := info.Body["title"].(string)``
##### [Writing response body](https://pocketbase.io/docs/go-routing/\#writing-response-body)
_For all supported methods, you can refer to_
_[`router.Event`](https://pkg.go.dev/github.com/pocketbase/pocketbase/tools/router#Event)_
_._
`// send response with JSON body
// (it also provides a generic response fields picker/filter if the "fields" query parameter is set)
e.JSON(http.StatusOK, map[string]any{"name": "John"})
// send response with string body
e.String(http.StatusOK, "Lorem ipsum...")
// send response with HTML body
// (check also the "Rendering templates" section)
e.HTML(http.StatusOK, "
Hello!
")
// redirect
e.Redirect(http.StatusTemporaryRedirect, "https://example.com")
// send response with no body
e.NoContent(http.StatusNoContent)
// serve a single file
e.FileFS(os.DirFS("..."), "example.txt")
// stream the specified reader
e.Stream(http.StatusOK, "application/octet-stream", reader)
// send response with blob (bytes slice) body
e.Blob(http.StatusOK, "application/octet-stream", []byte{ ... })`
##### [Reading the client IP](https://pocketbase.io/docs/go-routing/\#reading-the-client-ip)
`// The IP of the last client connecting to your server.
// The returned IP is safe and can be always trusted.
// When behind a reverse proxy (e.g. nginx) this method returns the IP of the proxy.
// https://pkg.go.dev/github.com/pocketbase/pocketbase/tools/router#Event.RemoteIP
ip := e.RemoteIP()
// The "real" IP of the client based on the configured Settings.TrustedProxy header(s).
// If such headers are not set, it fallbacks to e.RemoteIP().
// https://pkg.go.dev/github.com/pocketbase/pocketbase/core#RequestEvent.RealIP
ip := e.RealIP()`
##### [Request store](https://pocketbase.io/docs/go-routing/\#request-store)
The `core.RequestEvent` comes with a local store that you can use to share custom data between
[middlewares](https://pocketbase.io/docs/go-routing/#middlewares) and the route action.
`// store for the duration of the request
e.Set("someKey", 123)
// retrieve later
val := e.Get("someKey").(int) // 123`
### [Middlewares](https://pocketbase.io/docs/go-routing/\#middlewares)
##### [Registering middlewares](https://pocketbase.io/docs/go-routing/\#registering-middlewares)
Middlewares allow inspecting, intercepting and filtering route requests.
All middleware functions share the same signature with the route actions (aka.
`func(e *core.RequestEvent) error`) but expect the user to call `e.Next()` if they
want to proceed with the execution chain.
Middlewares can be registered _globally_, on _group_ and on _route_ level using the
`Bind`
and `BindFunc` methods.
Here is a minimal example of a what global middleware looks like:
`app.OnServe().BindFunc(func(se *core.ServeEvent) error {
// register a global middleware
se.Router.BindFunc(func(e *core.RequestEvent) error {
if e.Request.Header.Get("Something") == "" {
return e.BadRequestError("Something header value is missing!", nil)
}
return e.Next()
})
return se.Next()
})`
[`RouterGroup.Bind(middlewares...)`](https://pkg.go.dev/github.com/pocketbase/pocketbase/tools/router#RouterGroup.Bind)
/
[`Route.Bind(middlewares...)`](https://pkg.go.dev/github.com/pocketbase/pocketbase/tools/router#Route.Bind)
registers one or more middleware handlers.
Similar to the other app hooks, a middleware handler has 3 fields:
- `Id` _(optional)_ \- the name of the middleware (could be used as argument for
`Unbind`)
- `Priority` _(optional)_ \- the execution order of the middleware (if empty fallbacks to
the order of registration in the code)
- `Func` _(required)_ \- the middleware handler function
Often you don't need to specify the `Id` or `Priority` of the middleware and for
convenience you can instead use directly
[`RouterGroup.BindFunc(funcs...)`](https://pkg.go.dev/github.com/pocketbase/pocketbase/tools/router#RouterGroup.BindFunc)
/
[`Route.BindFunc(funcs...)`](https://pkg.go.dev/github.com/pocketbase/pocketbase/tools/router#Route.BindFunc) .
Below is a slightly more advanced example showing all options and the execution sequence ( _2,0,1,3,4_):
`app.OnServe().BindFunc(func(se *core.ServeEvent) error {
// attach global middleware
se.Router.BindFunc(func(e *core.RequestEvent) error {
println(0)
return e.Next()
})
g := se.Router.Group("/sub")
// attach group middleware
g.BindFunc(func(e *core.RequestEvent) error {
println(1)
return e.Next()
})
// attach group middleware with an id and custom priority
g.Bind(&hook.Handler[*core.RequestEvent]{
Id: "something",
Func: func(e *core.RequestEvent) error {
println(2)
return e.Next()
},
Priority: -1,
})
// attach middleware to a single route
//
// "GET /sub/hello" should print the sequence: 2,0,1,3,4
g.GET("/hello", func(e *core.RequestEvent) error {
println(4)
return e.String(200, "Hello!")
}).BindFunc(func(e *core.RequestEvent) error {
println(3)
return e.Next()
})
return se.Next()
})`
##### [Removing middlewares](https://pocketbase.io/docs/go-routing/\#removing-middlewares)
To remove a registered middleware from the execution chain for a specific group or route you can make use
of the
`Unbind(id)` method.
Note that only middlewares that have a non-empty `Id` can be removed.
`app.OnServe().BindFunc(func(se *core.ServeEvent) error {
// global middleware
se.Router.Bind(&hook.Handler[*core.RequestEvent]{
Id: "test",
Func: func(e *core.RequestEvent) error {
// ...
return e.Next()
},
)
// "GET /A" invokes the "test" middleware
se.Router.GET("/A", func(e *core.RequestEvent) error {
return e.String(200, "A")
})
// "GET /B" doesn't invoke the "test" middleware
se.Router.GET("/B", func(e *core.RequestEvent) error {
return e.String(200, "B")
}).Unbind("test")
return se.Next()
})`
##### [Builtin middlewares](https://pocketbase.io/docs/go-routing/\#builtin-middlewares)
The
[`apis`](https://pkg.go.dev/github.com/pocketbase/pocketbase/apis)
package exposes several middlewares that you can use as part of your application.
`// Require the request client to be unauthenticated (aka. guest).
// Example: Route.Bind(apis.RequireGuestOnly())
apis.RequireGuestOnly()
// Require the request client to be authenticated
// (optionally specify a list of allowed auth collection names, default to any).
// Example: Route.Bind(apis.RequireAuth())
apis.RequireAuth(optCollectionNames...)
// Require the request client to be authenticated as superuser
// (this is an alias for apis.RequireAuth(core.CollectionNameSuperusers)).
// Example: Route.Bind(apis.RequireSuperuserAuth())
apis.RequireSuperuserAuth()
// Require the request client to be authenticated as superuser OR
// regular auth record with id matching the specified route parameter (default to "id").
// Example: Route.Bind(apis.RequireSuperuserOrOwnerAuth(""))
apis.RequireSuperuserOrOwnerAuth(ownerIdParam)
// Changes the global 32MB default request body size limit (set it to 0 for no limit).
// Note that system record routes have dynamic body size limit based on their collection field types.
// Example: Route.Bind(apis.BodyLimit(10 << 20))
apis.BodyLimit(limitBytes)
// Compresses the HTTP response using Gzip compression scheme.
// Example: Route.Bind(apis.Gzip())
apis.Gzip()
// Instructs the activity logger to log only requests that have failed/returned an error.
// Example: Route.Bind(apis.SkipSuccessActivityLog())
apis.SkipSuccessActivityLog()`
##### [Default globally registered middlewares](https://pocketbase.io/docs/go-routing/\#default-globally-registered-middlewares)
The below list is mostly useful for users that may want to plug their own custom middlewares before/after
the priority of the default global ones, for example: registering a custom auth loader before the rate
limiter with `apis.DefaultRateLimitMiddlewarePriority - 1` so that the rate limit can be applied
properly based on the loaded auth state.
All PocketBase applications have the below internal middlewares registered out of the box ( _sorted by their priority_):
- **WWW redirect** [`apis.DefaultWWWRedirectMiddlewareId`](https://pkg.go.dev/github.com/pocketbase/pocketbase/apis#pkg-constants) [`apis.DefaultWWWRedirectMiddlewarePriority`](https://pkg.go.dev/github.com/pocketbase/pocketbase/apis#pkg-constants)
_Performs www -> non-www redirect(s) if the request host matches with one of the values in_
_certificate host policy._
- **CORS** [`apis.DefaultCorsMiddlewareId`](https://pkg.go.dev/github.com/pocketbase/pocketbase/apis#pkg-constants) [`apis.DefaultCorsMiddlewarePriority`](https://pkg.go.dev/github.com/pocketbase/pocketbase/apis#pkg-constants)
_By default all origins are allowed (PocketBase is stateless and doesn't rely on cookies) and can_
_be configured with the `--origins`_
_flag but for more advanced customization it can be also replaced entirely by binding with_
_`apis.CORS(config)` middleware or registering your own custom one in its place._
- **Activity logger** [`apis.DefaultActivityLoggerMiddlewareId`](https://pkg.go.dev/github.com/pocketbase/pocketbase/apis#pkg-constants) [`apis.DefaultActivityLoggerMiddlewarePriority`](https://pkg.go.dev/github.com/pocketbase/pocketbase/apis#pkg-constants)
_Saves request information into the logs auxiliary database._
- **Auto panic recover** [`apis.DefaultPanicRecoverMiddlewareId`](https://pkg.go.dev/github.com/pocketbase/pocketbase/apis#pkg-constants) [`apis.DefaultPanicRecoverMiddlewarePriority`](https://pkg.go.dev/github.com/pocketbase/pocketbase/apis#pkg-constants)
_Default panic-recover handler._
- **Auth token loader** [`apis.DefaultLoadAuthTokenMiddlewareId`](https://pkg.go.dev/github.com/pocketbase/pocketbase/apis#pkg-constants) [`apis.DefaultLoadAuthTokenMiddlewarePriority`](https://pkg.go.dev/github.com/pocketbase/pocketbase/apis#pkg-constants)
_Loads the auth token from the `Authorization` header and populates the related auth_
_record into the request event (aka. `e.Auth`)._
- **Security response headers** [`apis.DefaultSecurityHeadersMiddlewareId`](https://pkg.go.dev/github.com/pocketbase/pocketbase/apis#pkg-constants) [`apis.DefaultSecurityHeadersMiddlewarePriority`](https://pkg.go.dev/github.com/pocketbase/pocketbase/apis#pkg-constants)
_Adds default common security headers ( `X-XSS-Protection`,_
_`X-Content-Type-Options`,_
_`X-Frame-Options`) to the response (can be overwritten by other middlewares or from_
_inside the route action)._
- **Rate limit** [`apis.DefaultRateLimitMiddlewareId`](https://pkg.go.dev/github.com/pocketbase/pocketbase/apis#pkg-constants) [`apis.DefaultRateLimitMiddlewarePriority`](https://pkg.go.dev/github.com/pocketbase/pocketbase/apis#pkg-constants)
_Rate limits client requests based on the configured app settings (it does nothing if the rate_
_limit option is not enabled)._
- **Body limit** [`apis.DefaultBodyLimitMiddlewareId`](https://pkg.go.dev/github.com/pocketbase/pocketbase/apis#pkg-constants) [`apis.DefaultBodyLimitMiddlewarePriority`](https://pkg.go.dev/github.com/pocketbase/pocketbase/apis#pkg-constants)
_Applies a default max ~32MB request body limit for all custom routes ( system record routes have_
_dynamic body size limit based on their collection field types). Can be overwritten on group/route_
_level by simply rebinding the `apis.BodyLimit(limitBytes)` middleware._
### [Error response](https://pocketbase.io/docs/go-routing/\#error-response)
PocketBase has a global error handler and every returned error from a route or middleware will be safely
converted by default to a generic `ApiError` to avoid accidentally leaking sensitive
information (the original raw error message will be visible only in the _Dashboard > Logs_ or when
in `--dev` mode).
To make it easier returning formatted JSON error responses, the request event provides several
`ApiError` methods.
Note that `ApiError.RawData()` will be returned in the response only if it is a map of
`router.SafeErrorItem`/ `validation.Error` items.
`import validation "github.com/go-ozzo/ozzo-validation/v4"
se.Router.GET("/example", func(e *core.RequestEvent) error {
...
// construct ApiError with custom status code and validation data error
return e.Error(500, "something went wrong", map[string]validation.Error{
"title": validation.NewError("invalid_title", "Invalid or missing title"),
})
// if message is empty string, a default one will be set
return e.BadRequestError(optMessage, optData) // 400 ApiError
return e.UnauthorizedError(optMessage, optData) // 401 ApiError
return e.ForbiddenError(optMessage, optData) // 403 ApiError
return e.NotFoundError(optMessage, optData) // 404 ApiError
return e.TooManyRequestsError(optMessage, optData) // 429 ApiError
return e.InternalServerError(optMessage, optData) // 500 ApiError
})`
This is not very common but if you want to return `ApiError` outside of request related
handlers, you can use the below
`apis.*` factories:
`import (
validation "github.com/go-ozzo/ozzo-validation/v4"
"github.com/pocketbase/pocketbase/apis"
)
app.OnRecordCreate().BindFunc(func(e *core.RecordEvent) error {
...
// construct ApiError with custom status code and validation data error
return apis.NewApiError(500, "something went wrong", map[string]validation.Error{
"title": validation.NewError("invalid_title", "Invalid or missing title"),
})
// if message is empty string, a default one will be set
return apis.NewBadRequestError(optMessage, optData) // 400 ApiError
return apis.NewUnauthorizedError(optMessage, optData) // 401 ApiError
return apis.NewForbiddenError(optMessage, optData) // 403 ApiError
return apis.NewNotFoundError(optMessage, optData) // 404 ApiError
return apis.NewTooManyRequestsError(optMessage, optData) // 429 ApiError
return apis.NewInternalServerError(optMessage, optData) // 500 ApiError
})`
### [Helpers](https://pocketbase.io/docs/go-routing/\#helpers)
##### [Serving static directory](https://pocketbase.io/docs/go-routing/\#serving-static-directory)
[`apis.Static()`](https://pkg.go.dev/github.com/pocketbase/pocketbase/apis#Static)
serves static directory content from `fs.FS` instance.
Expects the route to have a `{path...}` wildcard parameter.
`app.OnServe().BindFunc(func(se *core.ServeEvent) error {
// serves static files from the provided dir (if exists)
se.Router.GET("/{path...}", apis.Static(os.DirFS("/path/to/public"), false))
return se.Next()
})`
##### [Auth response](https://pocketbase.io/docs/go-routing/\#auth-response)
[`apis.RecordAuthResponse()`](https://pkg.go.dev/github.com/pocketbase/pocketbase/apis#RecordAuthResponse)
writes standardized JSON record auth response (aka. token + record data) into the specified request body.
Could be used as a return result from a custom auth route.
``app.OnServe().BindFunc(func(se *core.ServeEvent) error {
se.Router.POST("/phone-login", func(e *core.RequestEvent) error {
data := struct {
Phone string `json:"phone" form:"phone"`
Password string `json:"password" form:"password"`
}{}
if err := e.BindBody(&data); err != nil {
return e.BadRequestError("Failed to read request data", err)
}
record, err := e.App.FindFirstRecordByData("users", "phone", data.Phone)
if err != nil || !record.ValidatePassword(data.Password) {
// return generic 400 error as a basic enumeration protection
return e.BadRequestError("Invalid credentials", err)
}
return apis.RecordAuthResponse(e, record, "phone", nil)
})
return se.Next()
})``
##### [Enrich record(s)](https://pocketbase.io/docs/go-routing/\#enrich-records)
[`apis.EnrichRecord()`](https://pkg.go.dev/github.com/pocketbase/pocketbase/apis#EnrichRecord)
and
[`apis.EnrichRecords()`](https://pkg.go.dev/github.com/pocketbase/pocketbase/apis#EnrichRecords)
helpers parses the request context and enrich the provided record(s) by:
- expands relations (if `defaultExpands` and/or `?expand` query parameter is set)
- ensures that the emails of the auth record and its expanded auth relations are visible only for the
current logged superuser, record owner or record with manage access
`app.OnServe().BindFunc(func(se *core.ServeEvent) error {
se.Router.GET("/custom-article", func(e *core.RequestEvent) error {
records, err := e.App.FindRecordsByFilter("article", "status = 'active'", "-created", 40, 0)
if err != nil {
return e.NotFoundError("No active articles", err)
}
// enrich the records with the "categories" relation as default expand
err = apis.EnrichRecords(e, records, "categories")
if err != nil {
return err
}
return e.JSON(http.StatusOK, records)
})
return se.Next()
})`
##### [Go http.Handler wrappers](https://pocketbase.io/docs/go-routing/\#go-http-handler-wrappers)
If you want to register standard Go `http.Handler` function and middlewares, you can use
[`apis.WrapStdHandler(handler)`](https://pkg.go.dev/github.com/pocketbase/pocketbase/apis#WrapStdHandler)
and
[`apis.WrapStdMiddleware(func)`](https://pkg.go.dev/github.com/pocketbase/pocketbase/apis#WrapStdMiddleware)
functions.
### [Sending request to custom routes using the SDKs](https://pocketbase.io/docs/go-routing/\#sending-request-to-custom-routes-using-the-sdks)
The official PocketBase SDKs expose the internal `send()` method that could be used to send requests
to your custom route(s).
JavaScript
Dart
`import PocketBase from 'pocketbase';
const pb = new PocketBase('http://127.0.0.1:8090');
await pb.send("/hello", {
// for other options check
// https://developer.mozilla.org/en-US/docs/Web/API/fetch#options
query: { "abc": 123 },
});`
`import 'package:pocketbase/pocketbase.dart';
final pb = PocketBase('http://127.0.0.1:8090');
await pb.send("/hello", query: { "abc": 123 })`
* * *
[Prev: Event hooks](https://pocketbase.io/docs/go-event-hooks) [Next: Database](https://pocketbase.io/docs/go-database)
## PocketBase Record Management
Record operations
The most common task when using PocketBase as framework probably would be querying and working with your
collection records.
You could find detailed documentation about all the supported Record model methods in
[`core.Record`](https://pkg.go.dev/github.com/pocketbase/pocketbase/core#Record)
but below are some examples with the most common ones.
- [Set field value](https://pocketbase.io/docs/go-records/#set-field-value)
- [Get field value](https://pocketbase.io/docs/go-records/#get-field-value)
- [Auth accessors](https://pocketbase.io/docs/go-records/#auth-accessors)
- [Copies](https://pocketbase.io/docs/go-records/#copies)
- [Hide/Unhide fields](https://pocketbase.io/docs/go-records/#hideunhide-fields)
- [Fetch records](https://pocketbase.io/docs/go-records/#fetch-records)
- [Fetch single record](https://pocketbase.io/docs/go-records/#fetch-single-record)
- [Fetch multiple records](https://pocketbase.io/docs/go-records/#fetch-multiple-records)
- [Fetch auth records](https://pocketbase.io/docs/go-records/#fetch-auth-records)
- [Custom record query](https://pocketbase.io/docs/go-records/#custom-record-query)
- [Create new record](https://pocketbase.io/docs/go-records/#create-new-record)
- [Create new record programmatically](https://pocketbase.io/docs/go-records/#create-new-record-programmatically)
- [Intercept create request](https://pocketbase.io/docs/go-records/#intercept-create-request)
- [Update existing record](https://pocketbase.io/docs/go-records/#update-existing-record)
- [Update existing record programmatically](https://pocketbase.io/docs/go-records/#update-existing-record-programmatically)
- [Intercept update request](https://pocketbase.io/docs/go-records/#intercept-update-request)
- [Delete record](https://pocketbase.io/docs/go-records/#delete-record)
- [Transaction](https://pocketbase.io/docs/go-records/#transaction)
- [Programmatically expanding relations](https://pocketbase.io/docs/go-records/#programmatically-expanding-relations)
- [Check if record can be accessed](https://pocketbase.io/docs/go-records/#check-if-record-can-be-accessed)
- [Generating and validating tokens](https://pocketbase.io/docs/go-records/#generating-and-validating-tokens)
### [Set field value](https://pocketbase.io/docs/go-records/\#set-field-value)
`// sets the value of a single record field
// (field type specific modifiers are also supported)
record.Set("title", "example")
record.Set("users+", "6jyr1y02438et52") // append to existing value
// populates a record from a data map
// (calls Set for each entry of the map)
record.Load(data)`
### [Get field value](https://pocketbase.io/docs/go-records/\#get-field-value)
`// retrieve a single record field value
// (field specific modifiers are also supported)
record.Get("someField") // -> any (without cast)
record.GetBool("someField") // -> cast to bool
record.GetString("someField") // -> cast to string
record.GetInt("someField") // -> cast to int
record.GetFloat("someField") // -> cast to float64
record.GetDateTime("someField") // -> cast to types.DateTime
record.GetStringSlice("someField") // -> cast to []string
// retrieve the new uploaded files
// (e.g. for inspecting and modifying the file(s) before save)
record.GetUnsavedFiles("someFileField")
// unmarshal a single "json" field value into the provided result
record.UnmarshalJSONField("someJSONField", &result)
// retrieve a single or multiple expanded data
record.ExpandedOne("author") // -> nil|*core.Record
record.ExpandedAll("categories") // -> []*core.Record
// export all the public safe record fields as map[string]any
// (note: "json" type field values are exported as types.JSONRaw bytes slice)
record.PublicExport()`
### [Auth accessors](https://pocketbase.io/docs/go-records/\#auth-accessors)
`record.IsSuperuser() // alias for record.Collection().Name == "_superusers"
record.Email() // alias for record.Get("email")
record.SetEmail(email) // alias for record.Set("email", email)
record.Verified() // alias for record.Get("verified")
record.SetVerified(false) // alias for record.Set("verified", false)
record.TokenKey() // alias for record.Get("tokenKey")
record.SetTokenKey(key) // alias for record.Set("tokenKey", key)
record.RefreshTokenKey() // alias for record.Set("tokenKey:autogenerate", "")
record.ValidatePassword(pass)
record.SetPassword(pass) // alias for record.Set("password", pass)
record.SetRandomPassword() // sets cryptographically random 30 characters string as password`
### [Copies](https://pocketbase.io/docs/go-records/\#copies)
`// returns a shallow copy of the current record model populated
// with its ORIGINAL db data state and everything else reset to the defaults
// (usually used for comparing old and new field values)
record.Original()
// returns a shallow copy of the current record model populated
// with its LATEST data state and everything else reset to the defaults
// (aka. no expand, no custom fields and with default visibility flags)
record.Fresh()
// returns a shallow copy of the current record model populated
// with its ALL collection and custom fields data, expand and visibility flags
record.Clone()`
### [Hide/Unhide fields](https://pocketbase.io/docs/go-records/\#hideunhide-fields)
Collection fields can be marked as "Hidden" from the Dashboard to prevent regular user access to the field
values.
Record models provide an option to further control the fields serialization visibility in addition to the
"Hidden" fields option using the
[`record.Hide(fieldNames...)`](https://pkg.go.dev/github.com/pocketbase/pocketbase/core#Record.Hide)
and
[`record.Unhide(fieldNames...)`](https://pkg.go.dev/github.com/pocketbase/pocketbase/core#Record.Unhide)
methods.
Often the `Hide/Unhide` methods are used in combination with the `OnRecordEnrich` hook
invoked on every record enriching (list, view, create, update, realtime change, etc.). For example:
`app.OnRecordEnrich("articles").BindFunc(func(e *core.RecordEnrichEvent) error {
// dynamically show/hide a record field depending on whether the current
// authenticated user has a certain "role" (or any other field constraint)
if e.RequestInfo.Auth == nil ||
(!e.RequestInfo.Auth.IsSuperuser() && e.RequestInfo.Auth.GetString("role") != "staff") {
e.Record.Hide("someStaffOnlyField")
}
return e.Next()
})`
For custom fields, not part of the record collection schema, it is required to call explicitly
`record.WithCustomData(true)` to allow them in the public serialization.
### [Fetch records](https://pocketbase.io/docs/go-records/\#fetch-records)
##### [Fetch single record](https://pocketbase.io/docs/go-records/\#fetch-single-record)
All single record retrieval methods return `nil` and `sql.ErrNoRows` error if no record
is found.
`// retrieve a single "articles" record by its id
record, err := app.FindRecordById("articles", "RECORD_ID")
// retrieve a single "articles" record by a single key-value pair
record, err := app.FindFirstRecordByData("articles", "slug", "test")
// retrieve a single "articles" record by a string filter expression
// (NB! use "{:placeholder}" to safely bind untrusted user input parameters)
record, err := app.FindFirstRecordByFilter(
"articles",
"status = 'public' && category = {:category}",
dbx.Params{ "category": "news" },
)`
##### [Fetch multiple records](https://pocketbase.io/docs/go-records/\#fetch-multiple-records)
All multiple records retrieval methods return empty slice and `nil` error if no records are found.
`// retrieve multiple "articles" records by their ids
records, err := app.FindRecordsByIds("articles", []string{"RECORD_ID1", "RECORD_ID2"})
// retrieve the total number of "articles" records in a collection with optional dbx expressions
totalPending, err := app.CountRecords("articles", dbx.HashExp{"status": "pending"})
// retrieve multiple "articles" records with optional dbx expressions
records, err := app.FindAllRecords("articles",
dbx.NewExp("LOWER(username) = {:username}", dbx.Params{"username": "John.Doe"}),
dbx.HashExp{"status": "pending"},
)
// retrieve multiple paginated "articles" records by a string filter expression
// (NB! use "{:placeholder}" to safely bind untrusted user input parameters)
records, err := app.FindRecordsByFilter(
"articles", // collection
"status = 'public' && category = {:category}", // filter
"-published", // sort
10, // limit
0, // offset
dbx.Params{ "category": "news" }, // optional filter params
)`
##### [Fetch auth records](https://pocketbase.io/docs/go-records/\#fetch-auth-records)
`// retrieve a single auth record by its email
user, err := app.FindAuthRecordByEmail("users", "test@example.com")
// retrieve a single auth record by JWT
// (you could also specify an optional list of accepted token types)
user, err := app.FindAuthRecordByToken("YOUR_TOKEN", core.TokenTypeAuth)`
##### [Custom record query](https://pocketbase.io/docs/go-records/\#custom-record-query)
In addition to the above query helpers, you can also create custom Record queries using
[`RecordQuery(collection)`](https://pkg.go.dev/github.com/pocketbase/pocketbase/core#RecordQuery)
method. It returns a SELECT DB builder that can be used with the same methods described in the
[Database guide](https://pocketbase.io/docs/go-database).
`import (
"github.com/pocketbase/dbx"
"github.com/pocketbase/pocketbase/core"
)
...
func FindActiveArticles(app core.App) ([]*core.Record, error) {
records := []*core.Record{}
err := app.RecordQuery("articles").
AndWhere(dbx.HashExp{"status": "active"}).
OrderBy("published DESC").
Limit(10).
All(&records)
if err != nil {
return nil, err
}
return records, nil
}`
### [Create new record](https://pocketbase.io/docs/go-records/\#create-new-record)
##### [Create new record programmatically](https://pocketbase.io/docs/go-records/\#create-new-record-programmatically)
`import (
"github.com/pocketbase/pocketbase/core"
"github.com/pocketbase/pocketbase/tools/filesystem"
)
...
collection, err := app.FindCollectionByNameOrId("articles")
if err != nil {
return err
}
record := core.NewRecord(collection)
record.Set("title", "Lorem ipsum")
record.Set("active", true)
// field type specific modifiers can also be used
record.Set("slug:autogenerate", "post-")
// new files must be one or a slice of *filesystem.File values
//
// note1: see all factories in https://pkg.go.dev/github.com/pocketbase/pocketbase/tools/filesystem#File
// note2: for reading files from a request event you can also use e.FindUploadedFiles("fileKey")
f1, _ := filesystem.NewFileFromPath("/local/path/to/file1.txt")
f2, _ := filesystem.NewFileFromBytes([]byte{"test content"}, "file2.txt")
f3, _ := filesystem.NewFileFromURL(context.Background(), "https://example.com/file3.pdf")
record.Set("documents", []*filesystem.File{f1, f2, f3})
// validate and persist
// (use SaveNoValidate to skip fields validation)
err = app.Save(record);
if err != nil {
return err
}`
##### [Intercept create request](https://pocketbase.io/docs/go-records/\#intercept-create-request)
`import (
"github.com/pocketbase/pocketbase/core"
)
...
app.OnRecordCreateRequest("articles").BindFunc(func(e *core.RecordRequestEvent) error {
// ignore for superusers
if e.HasSuperuserAuth() {
return e.Next()
}
// overwrite the submitted "status" field value
e.Record.Set("status", "pending")
// or you can also prevent the create event by returning an error
status := e.Record.GetString("status")
if (status != "pending" &&
// guest or not an editor
(e.Auth == nil || e.Auth.GetString("role") != "editor")) {
return e.BadRequestError("Only editors can set a status different from pending", nil)
}
return e.Next()
})`
### [Update existing record](https://pocketbase.io/docs/go-records/\#update-existing-record)
##### [Update existing record programmatically](https://pocketbase.io/docs/go-records/\#update-existing-record-programmatically)
`record, err := app.FindRecordById("articles", "RECORD_ID")
if err != nil {
return err
}
record.Set("title", "Lorem ipsum")
// delete existing record files by specifying their file names
record.Set("documents-", []string{"file1_abc123.txt", "file3_abc123.txt"})
// append one or more new files to the already uploaded list
//
// note1: see all factories in https://pkg.go.dev/github.com/pocketbase/pocketbase/tools/filesystem#File
// note2: for reading files from a request event you can also use e.FindUploadedFiles("fileKey")
f1, _ := filesystem.NewFileFromPath("/local/path/to/file1.txt")
f2, _ := filesystem.NewFileFromBytes([]byte{"test content"}, "file2.txt")
f3, _ := filesystem.NewFileFromURL(context.Background(), "https://example.com/file3.pdf")
record.Set("documents+", []*filesystem.File{f1, f2, f3})
// validate and persist
// (use SaveNoValidate to skip fields validation)
err = app.Save(record);
if err != nil {
return err
}`
##### [Intercept update request](https://pocketbase.io/docs/go-records/\#intercept-update-request)
`import (
"github.com/pocketbase/pocketbase/core"
)
...
app.OnRecordUpdateRequest("articles").Add(func(e *core.RecordRequestEvent) error {
// ignore for superusers
if e.HasSuperuserAuth() {
return e.Next()
}
// overwrite the submitted "status" field value
e.Record.Set("status", "pending")
// or you can also prevent the create event by returning an error
status := e.Record.GetString("status")
if (status != "pending" &&
// guest or not an editor
(e.Auth == nil || e.Auth.GetString("role") != "editor")) {
return e.BadRequestError("Only editors can set a status different from pending", nil)
}
return e.Next()
})`
### [Delete record](https://pocketbase.io/docs/go-records/\#delete-record)
`record, err := app.FindRecordById("articles", "RECORD_ID")
if err != nil {
return err
}
err = app.Delete(record)
if err != nil {
return err
}`
### [Transaction](https://pocketbase.io/docs/go-records/\#transaction)
To execute multiple queries in a transaction you can use
[`app.RunInTransaction(fn)`](https://pkg.go.dev/github.com/pocketbase/pocketbase/core#BaseApp.RunInTransaction)
.
The DB operations are persisted only if the transaction returns `nil`.
It is safe to nest `RunInTransaction` calls as long as you use the callback's
`txApp` argument.
Inside the transaction function always use its `txApp` argument and not the original
`app` instance because we allow only a single writer/transaction at a time and it could
result in a deadlock.
To avoid performance issues, try to minimize slow/long running tasks such as sending emails,
connecting to external services, etc. as part of the transaction.
`import (
"github.com/pocketbase/pocketbase/core"
)
...
titles := []string{"title1", "title2", "title3"}
collection, err := app.FindCollectionByNameOrId("articles")
if err != nil {
return err
}
// create new record for each title
app.RunInTransaction(func(txApp core.App) error {
for _, title := range titles {
record := core.NewRecord(collection)
record.Set("title", title)
if err := txApp.Save(record); err != nil {
return err
}
}
return nil
})`
### [Programmatically expanding relations](https://pocketbase.io/docs/go-records/\#programmatically-expanding-relations)
To expand record relations programmatically you can use
[`app.ExpandRecord(record, expands, optFetchFunc)`](https://pkg.go.dev/github.com/pocketbase/pocketbase/core#BaseApp.ExpandRecord)
for single or
[`app.ExpandRecords(records, expands, optFetchFunc)`](https://pkg.go.dev/github.com/pocketbase/pocketbase/core#BaseApp.ExpandRecords)
for multiple records.
Once loaded, you can access the expanded relations via
[`record.ExpandedOne(relName)`](https://pkg.go.dev/github.com/pocketbase/pocketbase/core#Record.ExpandedOne)
or
[`record.ExpandedAll(relName)`](https://pkg.go.dev/github.com/pocketbase/pocketbase/core#Record.ExpandedAll) .
For example:
`record, err := app.FindFirstRecordByData("articles", "slug", "lorem-ipsum")
if err != nil {
return err
}
// expand the "author" and "categories" relations
errs := app.ExpandRecord(record, []string{"author", "categories"}, nil)
if len(errs) > 0 {
return fmt.Errorf("failed to expand: %v", errs)
}
// print the expanded records
log.Println(record.ExpandedOne("author"))
log.Println(record.ExpandedAll("categories"))`
### [Check if record can be accessed](https://pocketbase.io/docs/go-records/\#check-if-record-can-be-accessed)
To check whether a custom client request or user can access a single record, you can use the
[`app.CanAccessRecord(record, requestInfo, rule)`](https://pkg.go.dev/github.com/pocketbase/pocketbase/core#BaseApp.CanAccessRecord)
method.
Below is an example of creating a custom route to retrieve a single article and checking the request
satisfy the View API rule of the record collection:
`package main
import (
"log"
"net/http"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/core"
)
func main() {
app := pocketbase.New()
app.OnServe().BindFunc(func(se *core.ServeEvent) error {
se.Router.GET("/articles/{slug}", func(e *core.RequestEvent) error {
slug := e.Request.PathValue("slug")
record, err := e.App.FindFirstRecordByData("articles", "slug", slug)
if err != nil {
return e.NotFoundError("Missing or invalid slug", err)
}
info, err := e.RequestInfo()
if err != nil {
return e.BadRequestError("Failed to retrieve request info", err)
}
canAccess, err := e.App.CanAccessRecord(record, info, record.Collection().ViewRule)
if !canAccess {
return e.ForbiddenError("", err)
}
return e.JSON(http.StatusOK, record)
})
return se.Next()
})
if err := app.Start(); err != nil {
log.Fatal(err)
}
}`
### [Generating and validating tokens](https://pocketbase.io/docs/go-records/\#generating-and-validating-tokens)
PocketBase Web APIs are fully stateless (aka. there are no sessions in the traditional sense) and an auth
record is considered authenticated if the submitted request contains a valid
`Authorization: TOKEN`
header
_(see also [Builtin auth middlewares](https://pocketbase.io/docs/go-routing/#builtin-middlewares) and_
_[Retrieving the current auth state from a route](https://pocketbase.io/docs/go-routing/#retrieving-the-current-auth-state)_
_)_
.
If you want to issue and verify manually a record JWT (auth, verification, password reset, etc.), you
could do that using the record token type specific methods:
`token, err := record.NewAuthToken()
token, err := record.NewVerificationToken()
token, err := record.NewPasswordResetToken()
token, err := record.NewEmailChangeToken(newEmail)
token, err := record.NewFileToken() // for protected files
token, err := record.NewStaticAuthToken(optCustomDuration) // non-refreshable auth token`
Each token type has its own secret and the token duration is managed via its type related collection auth
option ( _the only exception is `NewStaticAuthToken`_).
To validate a record token you can use the
[`app.FindAuthRecordByToken`](https://pkg.go.dev/github.com/pocketbase/pocketbase/core#BaseApp.FindAuthRecordByToken)
method. The token related auth record is returned only if the token is not expired and its signature is valid.
Here is an example how to validate an auth token:
`record, err := app.FindAuthRecordByToken("YOUR_TOKEN", core.TokenTypeAuth)`
* * *
[Prev: Database](https://pocketbase.io/docs/go-database) [Next: Collection operations](https://pocketbase.io/docs/go-collections)
## PocketBase API Records
API Records
### [CRUD actions](https://pocketbase.io/docs/api-records/\#crud-actions)
**[List/Search records](https://pocketbase.io/docs/api-records/#listsearch-records)**
Returns a paginated records list, supporting sorting and filtering.
Depending on the collection's `listRule` value, the access to this action may or may not
have been restricted.
_You could find individual generated records API documentation in the "Dashboard > Collections_
_\> API Preview"._
JavaScript
Dart
`import PocketBase from 'pocketbase';
const pb = new PocketBase('http://127.0.0.1:8090');
...
// fetch a paginated records list
const resultList = await pb.collection('posts').getList(1, 50, {
filter: 'created >= "2022-01-01 00:00:00" && someField1 != someField2',
});
// you can also fetch all records at once via getFullList
const records = await pb.collection('posts').getFullList({
sort: '-created',
});
// or fetch only the first record that matches the specified filter
const record = await pb.collection('posts').getFirstListItem('someField="test"', {
expand: 'relField1,relField2.subRelField',
});`
`import 'package:pocketbase/pocketbase.dart';
final pb = PocketBase('http://127.0.0.1:8090');
...
// fetch a paginated records list
final resultList = await pb.collection('posts').getList(
page: 1,
perPage: 50,
filter: 'created >= "2022-01-01 00:00:00" && someField1 != someField2',
);
// you can also fetch all records at once via getFullList
final records = await pb.collection('posts').getFullList(sort: '-created');
// or fetch only the first record that matches the specified filter
final record = await pb.collection('posts').getFirstListItem(
'someField="test"',
expand: 'relField1,relField2.subRelField',
);`
###### API details
**GET**
/api/collections/ `collectionIdOrName`/records
Path parameters
| Param | Type | Description |
| --- | --- | --- |
| collectionIdOrName | String | ID or name of the records' collection. |
Query parameters
| Param | Type | Description |
| --- | --- | --- |
| page | Number | The page (aka. offset) of the paginated list ( _default to 1_). |
| perPage | Number | The max returned records per page ( _default to 30_). |
| sort | String | Specify the _ORDER BY_ fields. Add `-` / `+` (default) in front of the attribute for DESC / ASC order, eg.: `// DESC by created and ASC by id ?sort=-created,id` **Supported record sort fields:** `@random`, `@rowid`, `id`, **and any other collection field**. |
| filter | String | Filter expression to filter/search the returned records list (in addition to the collection's `listRule`), e.g.: `?filter=(title~'abc' && created>'2022-01-01')` **Supported record filter fields:** `id`, **\+ any field from the collection schema**. The syntax basically follows the format `OPERAND OPERATOR OPERAND`, where: - `OPERAND` \- could be any field literal, string (single or double quoted), number, null, true, false - `OPERATOR` \- is one of:
- `=` Equal - `!=` NOT equal - `>` Greater than - `>=` Greater than or equal - `<` Less than - `<=` Less than or equal - `~` Like/Contains (if not specified auto wraps the right string OPERAND in a "%" for wildcard match) - `!~` NOT Like/Contains (if not specified auto wraps the right string OPERAND in a "%" for wildcard match) - `?=` _Any/At least one of_ Equal - `?!=` _Any/At least one of_ NOT equal - `?>` _Any/At least one of_ Greater than - `?>=` _Any/At least one of_ Greater than or equal - `?<` _Any/At least one of_ Less than - `?<=` _Any/At least one of_ Less than or equal - `?~` _Any/At least one of_ Like/Contains (if not specified auto wraps the right string OPERAND in a "%" for wildcard match) - `?!~` _Any/At least one of_ NOT Like/Contains (if not specified auto wraps the right string OPERAND in a "%" for wildcard match) To group and combine several expressions you can use parenthesis `(...)`, `&&` (AND) and `||` (OR) tokens. Single line comments are also supported: `// Example comment`. |
| expand | String | Auto expand record relations. Ex.:
`?expand=relField1,relField2.subRelField` Supports up to 6-levels depth nested relations expansion. The expanded relations will be appended to the record under the `expand` property (e.g. `"expand": {"relField1": {...}, ...}`).
Only the relations to which the request user has permissions to **view** will be expanded. |
| fields | String | Comma separated string of the fields to return in the JSON response _(by default returns all fields)_. Ex.:
`?fields=*,expand.relField.name` `*` targets all keys from the specific depth level. In addition, the following field modifiers are also supported: - `:excerpt(maxLength, withEllipsis?)`
Returns a short plain text version of the field string value.
Ex.: `?fields=*,description:excerpt(200,true)` |
| skipTotal | Boolean | If it is set the total counts query will be skipped and the response fields `totalItems` and `totalPages` will have `-1` value.
This could drastically speed up the search queries when the total counters are not needed or cursor based pagination is used.
For optimization purposes, it is set by default for the `getFirstListItem()` and `getFullList()` SDKs methods. |
Responses
200 400 403
`{
"page": 1,
"perPage": 100,
"totalItems": 2,
"totalPages": 1,
"items": [\
{\
"id": "ae40239d2bc4477",\
"collectionId": "a98f514eb05f454",\
"collectionName": "posts",\
"updated": "2022-06-25 11:03:50.052",\
"created": "2022-06-25 11:03:35.163",\
"title": "test1"\
},\
{\
"id": "d08dfc4f4d84419",\
"collectionId": "a98f514eb05f454",\
"collectionName": "posts",\
"updated": "2022-06-25 11:03:45.876",\
"created": "2022-06-25 11:03:45.876",\
"title": "test2"\
}\
]
}`
`{
"status": 400,
"message": "Something went wrong while processing your request. Invalid filter.",
"data": {}
}`
`{
"status": 403,
"message": "Only superusers can filter by '@collection.*'",
"data": {}
}`
**[View record](https://pocketbase.io/docs/api-records/#view-record)**
Returns a single collection record by its ID.
Depending on the collection's `viewRule` value, the access to this action may or may not
have been restricted.
_You could find individual generated records API documentation in the "Dashboard > Collections_
_\> API Preview"._
JavaScript
Dart
`import PocketBase from 'pocketbase';
const pb = new PocketBase('http://127.0.0.1:8090');
...
const record1 = await pb.collection('posts').getOne('RECORD_ID', {
expand: 'relField1,relField2.subRelField',
});`
`import 'package:pocketbase/pocketbase.dart';
final pb = PocketBase('http://127.0.0.1:8090');
...
final record1 = await pb.collection('posts').getOne('RECORD_ID',
expand: 'relField1,relField2.subRelField',
);`
###### API details
**GET**
/api/collections/ `collectionIdOrName`/records/ `recordId`
Path parameters
| Param | Type | Description |
| --- | --- | --- |
| collectionIdOrName | String | ID or name of the record's collection. |
| recordId | String | ID of the record to view. |
Query parameters
| Param | Type | Description |
| --- | --- | --- |
| expand | String | Auto expand record relations. Ex.:
`?expand=relField1,relField2.subRelField` Supports up to 6-levels depth nested relations expansion. The expanded relations will be appended to the record under the `expand` property (e.g. `"expand": {"relField1": {...}, ...}`).
Only the relations to which the request user has permissions to **view** will be expanded. |
| fields | String | Comma separated string of the fields to return in the JSON response _(by default returns all fields)_. Ex.:
`?fields=*,expand.relField.name` `*` targets all keys from the specific depth level. In addition, the following field modifiers are also supported: - `:excerpt(maxLength, withEllipsis?)`
Returns a short plain text version of the field string value.
Ex.: `?fields=*,description:excerpt(200,true)` |
Responses
200 403 404
`{
"id": "ae40239d2bc4477",
"collectionId": "a98f514eb05f454",
"collectionName": "posts",
"updated": "2022-06-25 11:03:50.052",
"created": "2022-06-25 11:03:35.163",
"title": "test1"
}`
`{
"status": 403,
"message": "Only superusers can perform this action.",
"data": {}
}`
`{
"status": 404,
"message": "The requested resource wasn't found.",
"data": {}
}`
**[Create record](https://pocketbase.io/docs/api-records/#create-record)**
Creates a new collection _Record_.
Depending on the collection's `createRule` value, the access to this action may or may not
have been restricted.
_You could find individual generated records API documentation from the Dashboard._
JavaScript
Dart
`import PocketBase from 'pocketbase';
const pb = new PocketBase('http://127.0.0.1:8090');
...
const record = await pb.collection('demo').create({
title: 'Lorem ipsum',
});`
`import 'package:pocketbase/pocketbase.dart';
final pb = PocketBase('http://127.0.0.1:8090');
...
final record = await pb.collection('demo').create(body: {
'title': 'Lorem ipsum',
});`
###### API details
**POST**
/api/collections/ `collectionIdOrName`/records
Path parameters
| Param | Type | Description |
| --- | --- | --- |
| collectionIdOrName | String | ID or name of the record's collection. |
Body Parameters
| Param | Type | Description |
| --- | --- | --- |
| Optionalid | String | **15 characters string** to store as record ID.
If not set, it will be auto generated. |
| Schema fields |
| **Any field from the collection's schema.** |
| Additional auth record fields |
| Requiredpassword | String | Auth record password. |
| RequiredpasswordConfirm | String | Auth record password confirmation. |
Body parameters could be sent as _JSON_ or
_multipart/form-data_.
File upload is supported only through _multipart/form-data_.
Query parameters
| Param | Type | Description |
| --- | --- | --- |
| expand | String | Auto expand record relations. Ex.:
`?expand=relField1,relField2.subRelField` Supports up to 6-levels depth nested relations expansion. The expanded relations will be appended to the record under the `expand` property (e.g. `"expand": {"relField1": {...}, ...}`).
Only the relations to which the request user has permissions to **view** will be expanded. |
| fields | String | Comma separated string of the fields to return in the JSON response _(by default returns all fields)_. Ex.:
`?fields=*,expand.relField.name` `*` targets all keys from the specific depth level. In addition, the following field modifiers are also supported: - `:excerpt(maxLength, withEllipsis?)`
Returns a short plain text version of the field string value.
Ex.: `?fields=*,description:excerpt(200,true)` |
Responses
200 400 403 404
`{
"collectionId": "a98f514eb05f454",
"collectionName": "demo",
"id": "ae40239d2bc4477",
"updated": "2022-06-25 11:03:50.052",
"created": "2022-06-25 11:03:35.163",
"title": "Lorem ipsum"
}`
`{
"status": 400,
"message": "Failed to create record.",
"data": {
"title": {
"code": "validation_required",
"message": "Missing required value."
}
}
}`
`{
"status": 403,
"message": "Only superusers can perform this action.",
"data": {}
}`
`{
"status": 404,
"message": "The requested resource wasn't found. Missing collection context.",
"data": {}
}`
**[Update record](https://pocketbase.io/docs/api-records/#update-record)**
Updates an existing collection _Record_.
Depending on the collection's `updateRule` value, the access to this action may or may not
have been restricted.
_You could find individual generated records API documentation from the Dashboard._
JavaScript
Dart
`import PocketBase from 'pocketbase';
const pb = new PocketBase('http://127.0.0.1:8090');
...
const record = await pb.collection('demo').update('YOUR_RECORD_ID', {
title: 'Lorem ipsum',
});`
`import 'package:pocketbase/pocketbase.dart';
final pb = PocketBase('http://127.0.0.1:8090');
...
final record = await pb.collection('demo').update('YOUR_RECORD_ID', body: {
'title': 'Lorem ipsum',
});`
###### API details
**PATCH**
/api/collections/ `collectionIdOrName`/records/ `recordId`
Path parameters
| Param | Type | Description |
| --- | --- | --- |
| collectionIdOrName | String | ID or name of the record's collection. |
| recordId | String | ID of the record to update. |
Body Parameters
| Param | Type | Description |
| --- | --- | --- |
| Schema fields |
| **Any field from the collection's schema.** |
| Additional auth record fields |
| OptionaloldPassword | String | Old auth record password.
This field is required only when changing the record password. Superusers and auth records with "Manage" access can skip this field. |
| Optionalpassword | String | New auth record password. |
| OptionalpasswordConfirm | String | New auth record password confirmation. |
Body parameters could be sent as _JSON_ or
_multipart/form-data_.
File upload is supported only through _multipart/form-data_.
Query parameters
| Param | Type | Description |
| --- | --- | --- |
| expand | String | Auto expand record relations. Ex.:
`?expand=relField1,relField2.subRelField` Supports up to 6-levels depth nested relations expansion. The expanded relations will be appended to the record under the `expand` property (e.g. `"expand": {"relField1": {...}, ...}`).
Only the relations to which the request user has permissions to **view** will be expanded. |
| fields | String | Comma separated string of the fields to return in the JSON response _(by default returns all fields)_. Ex.:
`?fields=*,expand.relField.name` `*` targets all keys from the specific depth level. In addition, the following field modifiers are also supported: - `:excerpt(maxLength, withEllipsis?)`
Returns a short plain text version of the field string value.
Ex.: `?fields=*,description:excerpt(200,true)` |
Responses
200 400 403 404
`{
"collectionId": "a98f514eb05f454",
"collectionName": "demo",
"id": "ae40239d2bc4477",
"updated": "2022-06-25 11:03:50.052",
"created": "2022-06-25 11:03:35.163",
"title": "Lorem ipsum"
}`
`{
"status": 400,
"message": "Failed to create record.",
"data": {
"title": {
"code": "validation_required",
"message": "Missing required value."
}
}
}`
`{
"status": 403,
"message": "Only superusers can perform this action.",
"data": {}
}`
`{
"status": 404,
"message": "The requested resource wasn't found. Missing collection context.",
"data": {}
}`
**[Delete record](https://pocketbase.io/docs/api-records/#delete-record)**
Deletes a single collection _Record_ by its ID.
Depending on the collection's `deleteRule` value, the access to this action may or may not
have been restricted.
_You could find individual generated records API documentation from the Dashboard._
JavaScript
Dart
`import PocketBase from 'pocketbase';
const pb = new PocketBase('http://127.0.0.1:8090');
...
await pb.collection('demo').delete('YOUR_RECORD_ID');`
`import 'package:pocketbase/pocketbase.dart';
final pb = PocketBase('http://127.0.0.1:8090');
...
await pb.collection('demo').delete('YOUR_RECORD_ID');`
###### API details
**DELETE**
/api/collections/ `collectionIdOrName`/records/ `recordId`
Path parameters
| Param | Type | Description |
| --- | --- | --- |
| collectionIdOrName | String | ID or name of the record's collection. |
| recordId | String | ID of the record to delete. |
Responses
204 400 403 404
`null`
`{
"status": 400,
"message": "Failed to delete record. Make sure that the record is not part of a required relation reference.",
"data": {}
}`
`{
"status": 403,
"message": "Only superusers can perform this action.",
"data": {}
}`
`{
"status": 404,
"message": "The requested resource wasn't found.",
"data": {}
}`
**[Batch create/update/upsert/delete records](https://pocketbase.io/docs/api-records/#batch-createupdateupsertdelete-records)**
Batch and transactional create/update/upsert/delete of multiple records in a single request.
The batch Web API need to be explicitly enabled and configured from the
_Dashboard > Settings > Application_.
Because this endpoint process the requests in a single read&write transaction, other queries
could queue up and it could degrade the performance of your application if not used with
proper care and configuration (couple recommendations: prefer using the smallest possible max
processing time limit, avoid large file uploads over slow S3 networks, restrict the body size
limit to something smaller, etc.).
JavaScript
Dart
`import PocketBase from 'pocketbase';
const pb = new PocketBase('http://127.0.0.1:8090');
...
const batch = pb.createBatch();
batch.collection('example1').create({ ... });
batch.collection('example2').update('RECORD_ID', { ... });
batch.collection('example3').delete('RECORD_ID');
batch.collection('example4').upsert({ ... });
const result = await batch.send();`
`import 'package:pocketbase/pocketbase.dart';
final pb = PocketBase('http://127.0.0.1:8090');
...
final batch = pb.createBatch();
batch.collection('example1').create(body: { ... });
batch.collection('example2').update('RECORD_ID', body: { ... });
batch.collection('example3').delete('RECORD_ID');
batch.collection('example4').upsert(body: { ... });
final result = await batch.send();`
###### API details
**POST**
/api/batch
Body Parameters
Body parameters could be sent as _application/json_ or _multipart/form-data_.
File upload is supported only via _multipart/form-data_ (see below for more details).
| Param | Description |
| --- | --- |
| Requiredrequests | Array \- List of the requests to process.
The supported batch request actions are: - record create - `POST /api/collections/{collection}/records` - record update - `PATCH /api/collections/{collection}/records/{id}` - record upsert - `PUT /api/collections/{collection}/records`
(the body must have `id` field) - record delete - `DELETE /api/collections/{collection}/records/{id}` Each batch Request element have the following properties: - `url path` _(could include query parameters)_ - `method` _(GET, POST, PUT, PATCH, DELETE)_ - `headers`
_(custom per-request `Authorization` header is not supported at the moment,_ _aka. all batch requests have the same auth state)_ - `body` **NB!** When the batch request is send as `multipart/form-data`, the regular batch action fields are expected to be submitted as serialized json under the `@jsonPayload` field and file keys need to follow the pattern `requests.N.fileField` or `requests[N].fileField` _(this is usually handled transparently by the SDKs when their specific object_ _notation is used)_.
If you don't use the SDKs or prefer manually to construct the `FormData` body, then it could look something like:
`const formData = new FormData(); formData.append("@jsonPayload", JSON.stringify({ requests: [ { method: "POST", url: "/api/collections/example/records?expand=user", body: { title: "test1" }, }, { method: "PATCH", url: "/api/collections/example/records/RECORD_ID", body: { title: "test2" }, }, { method: "DELETE", url: "/api/collections/example/records/RECORD_ID", }, ] })) // file for the first request formData.append("requests.0.document", new File(...)) // file for the second request formData.append("requests.1.document", new File(...))` |
Responses
200 400 403
`[\
{\
"status": 200,\
"body": {\
"collectionId": "a98f514eb05f454",\
"collectionName": "demo",\
"id": "ae40239d2bc4477",\
"updated": "2022-06-25 11:03:50.052",\
"created": "2022-06-25 11:03:35.163",\
"title": "test1",\
"document": "file_a98f51.txt"\
}\
},\
{\
"status": 200,\
"body": {\
"collectionId": "a98f514eb05f454",\
"collectionName": "demo",\
"id": "31y1gc447bc9602",\
"updated": "2022-06-25 11:03:50.052",\
"created": "2022-06-25 11:03:35.163",\
"title": "test2",\
"document": "file_f514eb0.txt"\
}\
},\
]`
`{
"status": 400,
"message": "Batch transaction failed.",
"data": {
"requests": {
"1": {
"code": "batch_request_failed",
"message": "Batch request failed.",
"response": {
"status": 400,
"message": "Failed to create record.",
"data": {
"title": {
"code": "validation_min_text_constraint",
"message": "Must be at least 3 character(s).",
"params": { "min": 3 }
}
}
}
}
}
}
}`
`{
"status": 403,
"message": "Batch requests are not allowed.",
"data": {}
}`
### [Auth record actions](https://pocketbase.io/docs/api-records/\#auth-record-actions)
**[List auth methods](https://pocketbase.io/docs/api-records/#list-auth-methods)**
Returns a public list with the allowed collection authentication methods.
JavaScript
Dart
`import PocketBase from 'pocketbase';
const pb = new PocketBase('http://127.0.0.1:8090');
...
const result = await pb.collection('users').listAuthMethods();`
`import 'package:pocketbase/pocketbase.dart';
final pb = PocketBase('http://127.0.0.1:8090');
...
final result = await pb.collection('users').listAuthMethods();`
###### API details
**GET**
/api/collections/ `collectionIdOrName`/auth-methods
Path parameters
| Param | Type | Description |
| --- | --- | --- |
| collectionIdOrName | String | ID or name of the auth collection. |
Query parameters
| Param | Type | Description |
| --- | --- | --- |
| fields | String | Comma separated string of the fields to return in the JSON response _(by default returns all fields)_. Ex.:
`?fields=*,expand.relField.name` `*` targets all keys from the specific depth level. In addition, the following field modifiers are also supported: - `:excerpt(maxLength, withEllipsis?)`
Returns a short plain text version of the field string value.
Ex.: `?fields=*,description:excerpt(200,true)` |
Responses
200
`{
"password": {
"enabled": true,
"identityFields": ["email"]
},
"oauth2": {
"enabled": true,
"providers": [\
{\
"name": "github",\
"displayName": "GitHub",\
"state": "nT7SLxzXKAVMeRQJtxSYj9kvnJAvGk",\
"authURL": "https://github.com/login/oauth/authorize?client_id=test&code_challenge=fcf8WAhNI6uCLJYgJubLyWXHvfs8xghoLe3zksBvxjE&code_challenge_method=S256&response_type=code&scope=read%3Auser+user%3Aemail&state=nT7SLxzXKAVMeRQJtxSYj9kvnJAvGk&redirect_uri=",\
"codeVerifier": "PwBG5OKR2IyQ7siLrrcgWHFwLLLAeUrz7PS1nY4AneG",\
"codeChallenge": "fcf8WAhNI6uCLJYgJubLyWXHvfs8xghoLe3zksBvxjE",\
"codeChallengeMethod": "S256"\
}\
]
},
"mfa": {
"enabled": false,
"duration": 0
},
"otp": {
"enabled": false,
"duration": 0
}
}`
**[Auth with password](https://pocketbase.io/docs/api-records/#auth-with-password)**
Authenticate a single auth record by combination of a password and a unique identity field (e.g.
email).
JavaScript
Dart
`import PocketBase from 'pocketbase';
const pb = new PocketBase('http://127.0.0.1:8090');
...
const authData = await pb.collection('users').authWithPassword(
'YOUR_USERNAME_OR_EMAIL',
'YOUR_PASSWORD',
);
// after the above you can also access the auth data from the authStore
console.log(pb.authStore.isValid);
console.log(pb.authStore.token);
console.log(pb.authStore.record.id);
// "logout" the last authenticated record
pb.authStore.clear();`
`import 'package:pocketbase/pocketbase.dart';
final pb = PocketBase('http://127.0.0.1:8090');
...
final authData = await pb.collection('users').authWithPassword(
'YOUR_USERNAME_OR_EMAIL',
'YOUR_PASSWORD',
);
// after the above you can also access the auth data from the authStore
print(pb.authStore.isValid);
print(pb.authStore.token);
print(pb.authStore.record.id);
// "logout" the last authenticated record
pb.authStore.clear();`
###### API details
**POST**
/api/collections/ `collectionIdOrName`/auth-with-password
Path parameters
| Param | Type | Description |
| --- | --- | --- |
| collectionIdOrName | String | ID or name of the auth collection. |
Body Parameters
| Param | Type | Description |
| --- | --- | --- |
| Requiredidentity | String | Auth record username or email address. |
| Requiredpassword | String | Auth record password. |
| OptionalidentityField | String | A specific identity field to use (by default fallbacks to the first matching one). |
Body parameters could be sent as _JSON_ or
_multipart/form-data_.
Query parameters
| Param | Type | Description |
| --- | --- | --- |
| expand | String | Auto expand record relations. Ex.:
`?expand=relField1,relField2.subRelField` Supports up to 6-levels depth nested relations expansion. The expanded relations will be appended to the record under the `expand` property (e.g. `"expand": {"relField1": {...}, ...}`).
Only the relations to which the request user has permissions to **view** will be expanded. |
| fields | String | Comma separated string of the fields to return in the JSON response _(by default returns all fields)_. Ex.:
`?fields=*,record.expand.relField.name` `*` targets all keys from the specific depth level. In addition, the following field modifiers are also supported: - `:excerpt(maxLength, withEllipsis?)`
Returns a short plain text version of the field string value.
Ex.: `?fields=*,record.description:excerpt(200,true)` |
Responses
200 400
`{
"token": "eyJhbGciOiJIUzI1NiJ9.eyJpZCI6IjRxMXhsY2xtZmxva3UzMyIsInR5cGUiOiJhdXRoUmVjb3JkIiwiY29sbGVjdGlvbklkIjoiX3BiX3VzZXJzX2F1dGhfIiwiZXhwIjoyMjA4OTg1MjYxfQ.UwD8JvkbQtXpymT09d7J6fdA0aP9g4FJ1GPh_ggEkzc",
"record": {
"id": "8171022dc95a4ed",
"collectionId": "d2972397d45614e",
"collectionName": "users",
"created": "2022-06-24 06:24:18.434Z",
"updated": "2022-06-24 06:24:18.889Z",
"username": "test@example.com",
"email": "test@example.com",
"verified": false,
"emailVisibility": true,
"someCustomField": "example 123"
}
}`
`{
"status": 400,
"message": "An error occurred while submitting the form.",
"data": {
"password": {
"code": "validation_required",
"message": "Missing required value."
}
}
}`
**[Auth with OAuth2](https://pocketbase.io/docs/api-records/#auth-with-oauth2)**
Authenticate with an OAuth2 provider and returns a new auth token and record data.
This action usually should be called right after the provider login page redirect.
You could also check the
[OAuth2 web integration example](https://pocketbase.io/docs/authentication#web-oauth2-integration).
JavaScript
Dart
`import PocketBase from 'pocketbase';
const pb = new PocketBase('http://127.0.0.1:8090');
...
const authData = await pb.collection('users').authWithOAuth2Code(
'google',
'CODE',
'VERIFIER',
'REDIRECT_URL',
// optional data that will be used for the new account on OAuth2 sign-up
{
'name': 'test',
},
);
// after the above you can also access the auth data from the authStore
console.log(pb.authStore.isValid);
console.log(pb.authStore.token);
console.log(pb.authStore.record.id);
// "logout" the last authenticated record
pb.authStore.clear();`
`import 'package:pocketbase/pocketbase.dart';
final pb = PocketBase('http://127.0.0.1:8090');
...
final authData = await pb.collection('users').authWithOAuth2Code(
'google',
'CODE',
'VERIFIER',
'REDIRECT_URL',
// optional data that will be used for the new account on OAuth2 sign-up
createData: {
'name': 'test',
},
);
// after the above you can also access the auth data from the authStore
print(pb.authStore.isValid);
print(pb.authStore.token);
print(pb.authStore.record.id);
// "logout" the last authenticated record
pb.authStore.clear();`
###### API details
**POST**
/api/collections/ `collectionIdOrName`/auth-with-oauth2
Path parameters
| Param | Type | Description |
| --- | --- | --- |
| collectionIdOrName | String | ID or name of the auth collection. |
Body Parameters
| Param | Type | Description |
| --- | --- | --- |
| Requiredprovider | String | The name of the OAuth2 client provider (e.g. "google"). |
| Requiredcode | String | The authorization code returned from the initial request. |
| RequiredcodeVerifier | String | The code verifier sent with the initial request as part of the code\_challenge. |
| RequiredredirectUrl | String | The redirect url sent with the initial request. |
| OptionalcreateData | Object | Optional data that will be used when creating the auth record on OAuth2 sign-up. The created auth record must comply with the same requirements and validations in the regular **create** action.
_The data can only be in `json`, aka. `multipart/form-data` and_ _files upload currently are not supported during OAuth2 sign-ups._ |
Body parameters could be sent as _JSON_ or
_multipart/form-data_.
Query parameters
| Param | Type | Description |
| --- | --- | --- |
| expand | String | Auto expand record relations. Ex.:
`?expand=relField1,relField2.subRelField` Supports up to 6-levels depth nested relations expansion. The expanded relations will be appended to the record under the `expand` property (e.g. `"expand": {"relField1": {...}, ...}`).
Only the relations to which the request user has permissions to **view** will be expanded. |
| fields | String | Comma separated string of the fields to return in the JSON response _(by default returns all fields)_. Ex.:
`?fields=*,record.expand.relField.name` `*` targets all keys from the specific depth level. In addition, the following field modifiers are also supported: - `:excerpt(maxLength, withEllipsis?)`
Returns a short plain text version of the field string value.
Ex.: `?fields=*,record.description:excerpt(200,true)` |
Responses
200 400
`{
"token": "eyJhbGciOiJIUzI1NiJ9.eyJpZCI6IjRxMXhsY2xtZmxva3UzMyIsInR5cGUiOiJhdXRoUmVjb3JkIiwiY29sbGVjdGlvbklkIjoiX3BiX3VzZXJzX2F1dGhfIiwiZXhwIjoyMjA4OTg1MjYxfQ.UwD8JvkbQtXpymT09d7J6fdA0aP9g4FJ1GPh_ggEkzc",
"record": {
"id": "8171022dc95a4ed",
"collectionId": "d2972397d45614e",
"collectionName": "users",
"created": "2022-06-24 06:24:18.434Z",
"updated": "2022-06-24 06:24:18.889Z",
"username": "test@example.com",
"email": "test@example.com",
"verified": true,
"emailVisibility": false,
"someCustomField": "example 123"
},
"meta": {
"id": "abc123",
"name": "John Doe",
"username": "john.doe",
"email": "test@example.com",
"isNew": false,
"avatarURL": "https://example.com/avatar.png",
"rawUser": {...},
"accessToken": "...",
"refreshToken": "...",
"expiry": "..."
}
}`
`{
"status": 400,
"message": "An error occurred while submitting the form.",
"data": {
"provider": {
"code": "validation_required",
"message": "Missing required value."
}
}
}`
**[Auth with OTP](https://pocketbase.io/docs/api-records/#auth-with-otp)**
Authenticate a single auth record with an one-time password (OTP).
Note that when requesting an OTP we return an `otpId` even if a user with the provided email
doesn't exist as a very basic enumeration protection.
JavaScript
Dart
`import PocketBase from 'pocketbase';
const pb = new PocketBase('http://127.0.0.1:8090');
...
// send OTP email to the provided auth record
const req = await pb.collection('users').requestOTP('test@example.com');
// ... show a screen/popup to enter the password from the email ...
// authenticate with the requested OTP id and the email password
const authData = await pb.collection('users').authWithOTP(req.otpId, "YOUR_OTP");
// after the above you can also access the auth data from the authStore
console.log(pb.authStore.isValid);
console.log(pb.authStore.token);
console.log(pb.authStore.record.id);
// "logout"
pb.authStore.clear();`
`import 'package:pocketbase/pocketbase.dart';
final pb = PocketBase('http://127.0.0.1:8090');
...
// send OTP email to the provided auth record
final req = await pb.collection('users').requestOTP('test@example.com');
// ... show a screen/popup to enter the password from the email ...
// authenticate with the requested OTP id and the email password
final authData = await pb.collection('users').authWithOTP(req.otpId, "YOUR_OTP");
// after the above you can also access the auth data from the authStore
print(pb.authStore.isValid);
print(pb.authStore.token);
print(pb.authStore.record.id);
// "logout"
pb.authStore.clear();`
###### API details
OTP Request
OTP Auth
**POST**
/api/collections/ `collectionIdOrName`/request-otp
Path parameters
| Param | Type | Description |
| --- | --- | --- |
| collectionIdOrName | String | ID or name of the auth collection. |
Body Parameters
| Param | Type | Description |
| --- | --- | --- |
| Requiredemail | String | The auth record email address to send the OTP request (if exists). |
Responses
200 400 429
`{
"otpId": "ZLid2YhLoy7h0Z8"
}`
`{
"status": 400,
"message": "An error occurred while validating the submitted data.",
"data": {
"email": {
"code": "validation_is_email",
"message": "Must be a valid email address."
}
}
}`
`{
"status": 429,
"message": "You've send too many OTP requests, please try again later.",
"data": {}
}`
**POST**
/api/collections/ `collectionIdOrName`/auth-with-otp
Path parameters
| Param | Type | Description |
| --- | --- | --- |
| collectionIdOrName | String | ID or name of the auth collection. |
Body Parameters
| Param | Type | Description |
| --- | --- | --- |
| RequiredotpId | String | The id of the OTP request. |
| Requiredpassword | String | The one-time password. |
Query parameters
| Param | Type | Description |
| --- | --- | --- |
| expand | String | Auto expand record relations. Ex.:
`?expand=relField1,relField2.subRelField` Supports up to 6-levels depth nested relations expansion. The expanded relations will be appended to the record under the `expand` property (e.g. `"expand": {"relField1": {...}, ...}`).
Only the relations to which the request user has permissions to **view** will be expanded. |
| fields | String | Comma separated string of the fields to return in the JSON response _(by default returns all fields)_. Ex.:
`?fields=*,record.expand.relField.name` `*` targets all keys from the specific depth level. In addition, the following field modifiers are also supported: - `:excerpt(maxLength, withEllipsis?)`
Returns a short plain text version of the field string value.
Ex.: `?fields=*,record.description:excerpt(200,true)` |
Responses
200 400
`{
"token": "eyJhbGciOiJIUzI1NiJ9.eyJpZCI6IjRxMXhsY2xtZmxva3UzMyIsInR5cGUiOiJhdXRoUmVjb3JkIiwiY29sbGVjdGlvbklkIjoiX3BiX3VzZXJzX2F1dGhfIiwiZXhwIjoyMjA4OTg1MjYxfQ.UwD8JvkbQtXpymT09d7J6fdA0aP9g4FJ1GPh_ggEkzc",
"record": {
"id": "8171022dc95a4ed",
"collectionId": "d2972397d45614e",
"collectionName": "users",
"created": "2022-06-24 06:24:18.434Z",
"updated": "2022-06-24 06:24:18.889Z",
"username": "test@example.com",
"email": "test@example.com",
"verified": false,
"emailVisibility": true,
"someCustomField": "example 123"
}
}`
`{
"status": 400,
"message": "Failed to authenticate.",
"data": {
"otpId": {
"code": "validation_required",
"message": "Missing required value."
}
}
}`
**[Auth refresh](https://pocketbase.io/docs/api-records/#auth-refresh)**
Returns a new auth response (token and user data) for already authenticated auth record.
_This method is usually called by users on page/screen reload to ensure that the previously_
_stored data in `pb.authStore` is still valid and up-to-date._
JavaScript
Dart
`import PocketBase from 'pocketbase';
const pb = new PocketBase('http://127.0.0.1:8090');
...
const authData = await pb.collection('users').authRefresh();
// after the above you can also access the refreshed auth data from the authStore
console.log(pb.authStore.isValid);
console.log(pb.authStore.token);
console.log(pb.authStore.record.id);`
`import 'package:pocketbase/pocketbase.dart';
final pb = PocketBase('http://127.0.0.1:8090');
...
final authData = await pb.collection('users').authRefresh();
// after the above you can also access the refreshed auth data from the authStore
print(pb.authStore.isValid);
print(pb.authStore.token);
print(pb.authStore.record.id);`
###### API details
**POST**
/api/collections/ `collectionIdOrName`/auth-refresh
Requires `Authorization:TOKEN`
Path parameters
| Param | Type | Description |
| --- | --- | --- |
| collectionIdOrName | String | ID or name of the auth collection. |
Query parameters
| Param | Type | Description |
| --- | --- | --- |
| expand | String | Auto expand record relations. Ex.:
`?expand=relField1,relField2.subRelField` Supports up to 6-levels depth nested relations expansion. The expanded relations will be appended to the record under the `expand` property (e.g. `"expand": {"relField1": {...}, ...}`).
Only the relations to which the request user has permissions to **view** will be expanded. |
| fields | String | Comma separated string of the fields to return in the JSON response _(by default returns all fields)_. Ex.:
`?fields=*,record.expand.relField.name` `*` targets all keys from the specific depth level. In addition, the following field modifiers are also supported: - `:excerpt(maxLength, withEllipsis?)`
Returns a short plain text version of the field string value.
Ex.: `?fields=*,record.description:excerpt(200,true)` |
Responses
200 401 403 404
`{
"token": "eyJhbGciOiJIUzI1NiJ9.eyJpZCI6IjRxMXhsY2xtZmxva3UzMyIsInR5cGUiOiJhdXRoUmVjb3JkIiwiY29sbGVjdGlvbklkIjoiX3BiX3VzZXJzX2F1dGhfIiwiZXhwIjoyMjA4OTg1MjYxfQ.UwD8JvkbQtXpymT09d7J6fdA0aP9g4FJ1GPh_ggEkzc",
"record": {
"id": "8171022dc95a4ed",
"collectionId": "d2972397d45614e",
"collectionName": "users",
"created": "2022-06-24 06:24:18.434Z",
"updated": "2022-06-24 06:24:18.889Z",
"username": "test@example.com",
"email": "test@example.com",
"verified": false,
"emailVisibility": true,
"someCustomField": "example 123"
}
}`
`{
"status": 401,
"message": "The request requires valid record authorization token to be set.",
"data": {}
}`
`{
"status": 403,
"message": "The authorized record model is not allowed to perform this action.",
"data": {}
}`
`{
"status": 404,
"message": "Missing auth record context.",
"data": {}
}`
**[Verification](https://pocketbase.io/docs/api-records/#verification)**
Sends auth record email verification request.
JavaScript
Dart
`import PocketBase from 'pocketbase';
const pb = new PocketBase('http://127.0.0.1:8090');
...
await pb.collection('users').requestVerification('test@example.com');
// ---
// (optional) in your custom confirmation page:
// ---
await pb.collection('users').confirmVerification('VERIFICATION_TOKEN');`
`import 'package:pocketbase/pocketbase.dart';
final pb = PocketBase('http://127.0.0.1:8090');
...
await pb.collection('users').requestVerification('test@example.com');
// ---
// (optional) in your custom confirmation page:
// ---
await pb.collection('users').confirmVerification('VERIFICATION_TOKEN');`
###### API details
Request verification
Confirm verification
**POST**
/api/collections/ `collectionIdOrName`/request-verification
Body Parameters
| Param | Type | Description |
| --- | --- | --- |
| Requiredemail | String | The auth record email address to send the verification request (if exists). |
Responses
204 400
`null`
`{
"status": 400,
"message": "An error occurred while validating the submitted data.",
"data": {
"email": {
"code": "validation_required",
"message": "Missing required value."
}
}
}`
**POST**
/api/collections/ `collectionIdOrName`/confirm-verification
Body Parameters
| Param | Type | Description |
| --- | --- | --- |
| Requiredtoken | String | The token from the verification request email. |
Responses
204 400
`null`
`{
"status": 400,
"message": "An error occurred while validating the submitted data.",
"data": {
"token": {
"code": "validation_required",
"message": "Missing required value."
}
}
}`
**[Password reset](https://pocketbase.io/docs/api-records/#password-reset)**
Sends auth record password reset email request.
On successful password reset all previously issued auth tokens for the specific record will be
automatically invalidated.
JavaScript
Dart
`import PocketBase from 'pocketbase';
const pb = new PocketBase('http://127.0.0.1:8090');
...
await pb.collection('users').requestPasswordReset('test@example.com');
// ---
// (optional) in your custom confirmation page:
// ---
// note: after this call all previously issued auth tokens are invalidated
await pb.collection('users').confirmPasswordReset(
'RESET_TOKEN',
'NEW_PASSWORD',
'NEW_PASSWORD_CONFIRM',
);`
`import 'package:pocketbase/pocketbase.dart';
final pb = PocketBase('http://127.0.0.1:8090');
...
await pb.collection('users').requestPasswordReset('test@example.com');
// ---
// (optional) in your custom confirmation page:
// ---
// note: after this call all previously issued auth tokens are invalidated
await pb.collection('users').confirmPasswordReset(
'RESET_TOKEN',
'NEW_PASSWORD',
'NEW_PASSWORD_CONFIRM',
);`
###### API details
Request password reset
Confirm password reset
**POST**
/api/collections/ `collectionIdOrName`/request-password-reset
Body Parameters
| Param | Type | Description |
| --- | --- | --- |
| Requiredemail | String | The auth record email address to send the password reset request (if exists). |
Responses
204 400
`null`
`{
"status": 400,
"message": "An error occurred while validating the submitted data.",
"data": {
"email": {
"code": "validation_required",
"message": "Missing required value."
}
}
}`
**POST**
/api/collections/ `collectionIdOrName`/confirm-password-reset
Body Parameters
| Param | Type | Description |
| --- | --- | --- |
| Requiredtoken | String | The token from the password reset request email. |
| Requiredpassword | String | The new password to set. |
| RequiredpasswordConfirm | String | The new password confirmation. |
Responses
204 400
`null`
`{
"status": 400,
"message": "An error occurred while validating the submitted data.",
"data": {
"token": {
"code": "validation_required",
"message": "Missing required value."
}
}
}`
**[Email change](https://pocketbase.io/docs/api-records/#email-change)**
Sends auth record email change request.
On successful email change all previously issued auth tokens for the specific record will be
automatically invalidated.
JavaScript
Dart
`import PocketBase from 'pocketbase';
const pb = new PocketBase('http://127.0.0.1:8090');
...
await pb.collection('users').authWithPassword('test@example.com', '1234567890');
await pb.collection('users').requestEmailChange('new@example.com');
// ---
// (optional) in your custom confirmation page:
// ---
// note: after this call all previously issued auth tokens are invalidated
await pb.collection('users').confirmEmailChange('EMAIL_CHANGE_TOKEN', 'YOUR_PASSWORD');`
`import 'package:pocketbase/pocketbase.dart';
final pb = PocketBase('http://127.0.0.1:8090');
...
await pb.collection('users').authWithPassword('test@example.com', '1234567890');
await pb.collection('users').requestEmailChange('new@example.com');
...
// ---
// (optional) in your custom confirmation page:
// ---
// note: after this call all previously issued auth tokens are invalidated
await pb.collection('users').confirmEmailChange('EMAIL_CHANGE_TOKEN', 'YOUR_PASSWORD');`
###### API details
Request email change
Confirm email change
**POST**
/api/collections/ `collectionIdOrName`/request-email-change
Requires `Authorization:TOKEN`
Body Parameters
| Param | Type | Description |
| --- | --- | --- |
| RequirednewEmail | String | The new email address to send the change email request. |
Responses
204 400 401 403
`null`
`{
"status": 400,
"message": "An error occurred while validating the submitted data.",
"data": {
"newEmail": {
"code": "validation_required",
"message": "Missing required value."
}
}
}`
`{
"status": 401,
"message": "The request requires valid record authorization token to be set.",
"data": {}
}`
`{
"status": 403,
"message": "The authorized record model is not allowed to perform this action.",
"data": {}
}`
**POST**
/api/collections/ `collectionIdOrName`/confirm-email-change
Body Parameters
| Param | Type | Description |
| --- | --- | --- |
| Requiredtoken | String | The token from the change email request email. |
| Requiredpassword | String | The account password to confirm the email change. |
Responses
204 400
`null`
`{
"status": 400,
"message": "An error occurred while validating the submitted data.",
"data": {
"token": {
"code": "validation_required",
"message": "Missing required value."
}
}
}`
**[Impersonate](https://pocketbase.io/docs/api-records/#impersonate)**
Impersonate allows you to authenticate as a different user by generating a
**nonrefreshable** auth token.
Only superusers can perform this action.
JavaScript
Dart
`import PocketBase from 'pocketbase';
const pb = new PocketBase('http://127.0.0.1:8090');
...
// authenticate as superuser
await pb.collection("_superusers").authWithPassword("test@example.com", "1234567890");
// impersonate
// (the custom token duration is optional and must be in seconds)
const impersonateClient = pb.collection("users").impersonate("USER_RECORD_ID", 3600)
// log the impersonate token and user data
console.log(impersonateClient.authStore.token);
console.log(impersonateClient.authStore.record);
// send requests as the impersonated user
impersonateClient.collection("example").getFullList();`
`import 'package:pocketbase/pocketbase.dart';
final pb = PocketBase('http://127.0.0.1:8090');
...
// authenticate as superuser
await pb.collection("_superusers").authWithPassword("test@example.com", "1234567890");
// impersonate
// (the custom token duration is optional and must be in seconds)
final impersonateClient = pb.collection("users").impersonate("USER_RECORD_ID", 3600)
// log the impersonate token and user data
print(impersonateClient.authStore.token);
print(impersonateClient.authStore.record);
// send requests as the impersonated user
impersonateClient.collection("example").getFullList();`
###### API details
**POST**
/api/collections/ `collectionIdOrName`/impersonate/ `id`
Requires `Authorization:TOKEN`
Path parameters
| Param | Type | Description |
| --- | --- | --- |
| collectionIdOrName | String | ID or name of the auth collection. |
| id | String | ID of the auth record to impersonate. |
Body Parameters
| Param | Type | Description |
| --- | --- | --- |
| Optionalduration | Number | Optional custom JWT duration for the `exp` claim (in seconds).
If not set or 0, it fallbacks to the default collection auth token duration option. |
Body parameters could be sent as _JSON_ or
_multipart/form-data_.
Query parameters
| Param | Type | Description |
| --- | --- | --- |
| expand | String | Auto expand record relations. Ex.:
`?expand=relField1,relField2.subRelField` Supports up to 6-levels depth nested relations expansion. The expanded relations will be appended to the record under the `expand` property (e.g. `"expand": {"relField1": {...}, ...}`).
Only the relations to which the request user has permissions to **view** will be expanded. |
| fields | String | Comma separated string of the fields to return in the JSON response _(by default returns all fields)_. Ex.:
`?fields=*,record.expand.relField.name` `*` targets all keys from the specific depth level. In addition, the following field modifiers are also supported: - `:excerpt(maxLength, withEllipsis?)`
Returns a short plain text version of the field string value.
Ex.: `?fields=*,record.description:excerpt(200,true)` |
Responses
200 400 401 403 404
`{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjb2xsZWN0aW9uSWQiOiJfcGJjX2MwcHdrZXNjcXMiLCJleHAiOjE3MzAzNjgxMTUsImlkIjoicXkwMmMxdDBueDBvanFuIiwicmVmcmVzaGFibGUiOmZhbHNlLCJ0eXBlIjoiYXV0aCJ9.1JOaE54TyPdDLf0mb0T6roIYeh8Y1HfJvDlYZADMN4U",
"record": {
"id": "8171022dc95a4ed",
"collectionId": "d2972397d45614e",
"collectionName": "users",
"created": "2022-06-24 06:24:18.434Z",
"updated": "2022-06-24 06:24:18.889Z",
"username": "test@example.com",
"email": "test@example.com",
"verified": false,
"emailVisibility": true,
"someCustomField": "example 123"
}
}`
`{
"status": 400,
"message": "The request requires valid record authorization token to be set.",
"data": {
"duration": {
"code": "validation_min_greater_equal_than_required",
"message": "Must be no less than 0."
}
}
}`
`{
"status": 401,
"message": "An error occurred while validating the submitted data.",
"data": {}
}`
`{
"status": 403,
"message": "The authorized record model is not allowed to perform this action.",
"data": {}
}`
`{
"status": 404,
"message": "The requested resource wasn't found.",
"data": {}
}`
* * *
[Next: API Realtime](https://pocketbase.io/docs/api-realtime)
## PocketBase Collection Operations
Collection operations
Collections are usually managed via the Dashboard interface, but there are some situations where you may
want to create or edit a collection programmatically (usually as part of a
[DB migration](https://pocketbase.io/docs/go-migrations)). You can find all available Collection related operations
and methods in
[`core.App`](https://pkg.go.dev/github.com/pocketbase/pocketbase/core#App)
and
[`core.Collection`](https://pkg.go.dev/github.com/pocketbase/pocketbase/core#Collection)
, but below are listed some of the most common ones:
- [Fetch collections](https://pocketbase.io/docs/go-collections/#fetch-collections)
- [Fetch single collection](https://pocketbase.io/docs/go-collections/#fetch-single-collection)
- [Fetch multiple collections](https://pocketbase.io/docs/go-collections/#fetch-multiple-collections)
- [Custom collection query](https://pocketbase.io/docs/go-collections/#custom-collection-query)
- [Collection properties](https://pocketbase.io/docs/go-collections/#collection-properties)
- [Field definitions](https://pocketbase.io/docs/go-collections/#field-definitions)
- [Create new collection](https://pocketbase.io/docs/go-collections/#create-new-collection)
- [Update existing collection](https://pocketbase.io/docs/go-collections/#update-existing-collection)
- [Delete collection](https://pocketbase.io/docs/go-collections/#delete-collection)
### [Fetch collections](https://pocketbase.io/docs/go-collections/\#fetch-collections)
##### [Fetch single collection](https://pocketbase.io/docs/go-collections/\#fetch-single-collection)
All single collection retrieval methods return `nil` and `sql.ErrNoRows` error if no
collection is found.
`collection, err := app.FindCollectionByNameOrId("example")`
##### [Fetch multiple collections](https://pocketbase.io/docs/go-collections/\#fetch-multiple-collections)
All multiple collections retrieval methods return empty slice and `nil` error if no collections
are found.
`allCollections, err := app.FindAllCollections()
authAndViewCollections, err := app.FindAllCollections(core.CollectionTypeAuth, core.CollectionTypeView)`
##### [Custom collection query](https://pocketbase.io/docs/go-collections/\#custom-collection-query)
In addition to the above query helpers, you can also create custom Collection queries using
[`CollectionQuery()`](https://pkg.go.dev/github.com/pocketbase/pocketbase/core#CollectionQuery)
method. It returns a SELECT DB builder that can be used with the same methods described in the
[Database guide](https://pocketbase.io/docs/go-database).
`import (
"github.com/pocketbase/dbx"
"github.com/pocketbase/pocketbase/core"
)
...
func FindSystemCollections(app core.App) ([]*core.Collection, error) {
collections := []*core.Collection{}
err := app.CollectionQuery().
AndWhere(dbx.HashExp{"system": true}).
OrderBy("created DESC").
All(&collections)
if err != nil {
return nil, err
}
return collections, nil
}`
### [Collection properties](https://pocketbase.io/docs/go-collections/\#collection-properties)
`Id string
Name string
Type string // "base", "view", "auth"
System bool // !prevent collection rename, deletion and rules change of internal collections like _superusers
Fields core.FieldsList
Indexes types.JSONArray[string]
Created types.DateTime
Updated types.DateTime
// CRUD rules
ListRule *string
ViewRule *string
CreateRule *string
UpdateRule *string
DeleteRule *string
// "view" type specific options
// (see https://github.com/pocketbase/pocketbase/blob/master/core/collection_model_view_options.go)
ViewQuery string
// "auth" type specific options
// (see https://github.com/pocketbase/pocketbase/blob/master/core/collection_model_auth_options.go)
AuthRule *string
ManageRule *string
AuthAlert core.AuthAlertConfig
OAuth2 core.OAuth2Config
PasswordAuth core.PasswordAuthConfig
MFA core.MFAConfig
OTP core.OTPConfig
AuthToken core.TokenConfig
PasswordResetToken core.TokenConfig
EmailChangeToken core.TokenConfig
VerificationToken core.TokenConfig
FileToken core.TokenConfig
VerificationTemplate core.EmailTemplate
ResetPasswordTemplate core.EmailTemplate
ConfirmEmailChangeTemplate core.EmailTemplate`
### [Field definitions](https://pocketbase.io/docs/go-collections/\#field-definitions)
- [`core.BoolField`](https://pkg.go.dev/github.com/pocketbase/pocketbase/core#BoolField)
- [`core.NumberField`](https://pkg.go.dev/github.com/pocketbase/pocketbase/core#NumberField)
- [`core.TextField`](https://pkg.go.dev/github.com/pocketbase/pocketbase/core#TextField)
- [`core.EmailField`](https://pkg.go.dev/github.com/pocketbase/pocketbase/core#EmailField)
- [`core.URLField`](https://pkg.go.dev/github.com/pocketbase/pocketbase/core#URLField)
- [`core.EditorField`](https://pkg.go.dev/github.com/pocketbase/pocketbase/core#EditorField)
- [`core.DateField`](https://pkg.go.dev/github.com/pocketbase/pocketbase/core#DateField)
- [`core.AutodateField`](https://pkg.go.dev/github.com/pocketbase/pocketbase/core#AutodateField)
- [`core.SelectField`](https://pkg.go.dev/github.com/pocketbase/pocketbase/core#SelectField)
- [`core.FileField`](https://pkg.go.dev/github.com/pocketbase/pocketbase/core#FileField)
- [`core.RelationField`](https://pkg.go.dev/github.com/pocketbase/pocketbase/core#RelationField)
- [`core.JSONField`](https://pkg.go.dev/github.com/pocketbase/pocketbase/core#JSONField)
### [Create new collection](https://pocketbase.io/docs/go-collections/\#create-new-collection)
``import (
"github.com/pocketbase/pocketbase/core"
"github.com/pocketbase/pocketbase/tools/types"
)
...
// core.NewAuthCollection("example")
// core.NewViewCollection("example")
collection := core.NewBaseCollection("example")
// set rules
collection.ViewRule = types.Pointer("@request.auth.id != ''")
collection.CreateRule = types.Pointer("@request.auth.id != '' && @request.body.user = @request.auth.id")
collection.UpdateRule = types.Pointer(`
@request.auth.id != '' &&
user = @request.auth.id &&
(@request.body.user:isset = false || @request.body.user = @request.auth.id)
`)
// add text field
collection.Fields.Add(&core.TextField{
Name: "title",
Required: true,
Max: 100,
})
// add relation field
usersCollection, err := app.FindCollectionByNameOrId("users")
if err != nil {
return err
}
collection.Fields.Add(&core.RelationField{
Name: "user",
Required: true,
Max: 100,
CascadeDelete: true,
CollectionId: usersCollection.Id,
})
// add autodate/timestamp fields (created/updated)
collection.Fields.Add(&core.AutodateField{
Name: "created",
OnCreate: true,
})
collection.Fields.Add(&core.AutodateField{
Name: "updated",
OnCreate: true,
OnUpdate: true,
})
// or: collection.Indexes = []string{"CREATE UNIQUE INDEX idx_example_user ON example (user)"}
collection.AddIndex("idx_example_user", true, "user", "")
// validate and persist
// (use SaveNoValidate to skip fields validation)
err = app.Save(collection)
if err != nil {
return err
}``
### [Update existing collection](https://pocketbase.io/docs/go-collections/\#update-existing-collection)
`import (
"github.com/pocketbase/pocketbase/core"
"github.com/pocketbase/pocketbase/tools/types"
)
...
collection, err := app.FindCollectionByNameOrId("example")
if err != nil {
return err
}
// change rule
collection.DeleteRule = types.Pointer("@request.auth.id != ''")
// add new editor field
collection.Fields.Add(&core.EditorField{
Name: "description",
Required: true,
})
// change existing field
// (returns a pointer and direct modifications are allowed without the need of reinsert)
titleField := collection.Fields.GetByName("title")
titleField.Min = 10
// or: collection.Indexes = append(collection.Indexes, "CREATE INDEX idx_example_title ON example (title)")
collection.AddIndex("idx_example_title", false, "title", "")
// validate and persist
// (use SaveNoValidate to skip fields validation)
err = app.Save(collection)
if err != nil {
return err
}`
### [Delete collection](https://pocketbase.io/docs/go-collections/\#delete-collection)
`collection, err := app.FindCollectionByNameOrId("example")
if err != nil {
return err
}
err = app.Delete(collection)
if err != nil {
return err
}`
* * *
[Prev: Record operations](https://pocketbase.io/docs/go-records) [Next: Migrations](https://pocketbase.io/docs/go-migrations)
## PocketBase Migrations Guide
Migrations
PocketBase comes with a builtin DB and data migration utility, allowing you to version your DB structure,
create collections programmatically, initialize default settings and/or run anything that needs to be
executed only once.
The user defined migrations are located in `pb_migrations` directory (it can be changed using
the
`--migrationsDir` flag) and each unapplied migration inside it will be executed automatically
in a transaction on `serve` (or on `migrate up`).
The generated migrations are safe to be committed to version control and can be shared with your other
team members.
- [Automigrate](https://pocketbase.io/docs/js-migrations/#automigrate)
- [Creating migrations](https://pocketbase.io/docs/js-migrations/#creating-migrations)
- [Migration file](https://pocketbase.io/docs/js-migrations/#migration-file)
- [Collections snapshot](https://pocketbase.io/docs/js-migrations/#collections-snapshot)
- [Migrations history](https://pocketbase.io/docs/js-migrations/#migrations-history)
- [Examples](https://pocketbase.io/docs/js-migrations/#examples)
- [Executing raw SQL statements](https://pocketbase.io/docs/js-migrations/#executing-raw-sql-statements)
- [Initialize default application settings](https://pocketbase.io/docs/js-migrations/#initialize-default-application-settings)
- [Creating initial superuser](https://pocketbase.io/docs/js-migrations/#creating-initial-superuser)
- [Creating collection programmatically](https://pocketbase.io/docs/js-migrations/#creating-collection-programmatically)
### [Automigrate](https://pocketbase.io/docs/js-migrations/\#automigrate)
The prebuilt executable has the `--automigrate` flag enabled by default, meaning that every collection
configuration change from the Dashboard (or Web API) will generate the related migration file automatically
for you.
### [Creating migrations](https://pocketbase.io/docs/js-migrations/\#creating-migrations)
To create a new blank migration you can run `migrate create`.
`[root@dev app]$ ./pocketbase migrate create "your_new_migration"`
`// pb_migrations/1687801097_your_new_migration.js
migrate((app) => {
// add up queries...
}, (app) => {
// add down queries...
})`
New migrations are applied automatically on `serve`.
Optionally, you could apply new migrations manually by running `migrate up`.
To revert the last applied migration(s), you could run `migrate down [number]`.
When manually applying or reverting migrations, the `serve` process needs to be restarted so
that it can refresh its cached collections state.
##### [Migration file](https://pocketbase.io/docs/js-migrations/\#migration-file)
Each migration file should have a single `migrate(upFunc, downFunc)` call.
In the migration file, you are expected to write your "upgrade" code in the `upFunc` callback.
The `downFunc` is optional and it should contain the "downgrade" operations to revert the
changes made by the `upFunc`.
Both callbacks accept a transactional `app` instance.
### [Collections snapshot](https://pocketbase.io/docs/js-migrations/\#collections-snapshot)
The `migrate collections` command generates a full snapshot of your current collections
configuration without having to type it manually. Similar to the `migrate create` command, this
will generate a new migration file in the
`pb_migrations` directory.
`[root@dev app]$ ./pocketbase migrate collections`
By default the collections snapshot is imported in _extend_ mode, meaning that collections and
fields that don't exist in the snapshot are preserved. If you want the snapshot to _delete_
missing collections and fields, you can edit the generated file and change the last argument of
`ImportCollectionsByMarshaledJSON`
to `true`.
### [Migrations history](https://pocketbase.io/docs/js-migrations/\#migrations-history)
All applied migration filenames are stored in the internal `_migrations` table.
During local development often you might end up making various collection changes to test different approaches.
When `--automigrate` is enabled ( _which is the default_) this could lead in a migration
history with unnecessary intermediate steps that may not be wanted in the final migration history.
To avoid the clutter and to prevent applying the intermediate steps in production, you can remove (or
squash) the unnecessary migration files manually and then update the local migrations history by running:
`[root@dev app]$ ./pocketbase migrate history-sync`
The above command will remove any entry from the `_migrations` table that doesn't have a related
migration file associated with it.
### [Examples](https://pocketbase.io/docs/js-migrations/\#examples)
##### [Executing raw SQL statements](https://pocketbase.io/docs/js-migrations/\#executing-raw-sql-statements)
`// pb_migrations/1687801090_set_pending_status.js
migrate((app) => {
app.db().newQuery("UPDATE articles SET status = 'pending' WHERE status = ''").execute()
})`
##### [Initialize default application settings](https://pocketbase.io/docs/js-migrations/\#initialize-default-application-settings)
`// pb_migrations/1687801090_initial_settings.js
migrate((app) => {
let settings = app.settings()
// for all available settings fields you could check
// /jsvm/interfaces/core.Settings.html
settings.meta.appName = "test"
settings.meta.appURL = "https://example.com"
settings.logs.maxDays = 2
settings.logs.logAuthId = true
settings.logs.logIP = false
app.save(settings)
})`
##### [Creating initial superuser](https://pocketbase.io/docs/js-migrations/\#creating-initial-superuser)
_For all supported record methods, you can refer to_
_[Record operations](https://pocketbase.io/docs/js-records)_
.
`// pb_migrations/1687801090_initial_superuser.js
migrate((app) => {
let superusers = app.findCollectionByNameOrId("_superusers")
let record = new Record(superusers)
// note: the values can be eventually loaded via $os.getenv(key)
// or from a special local config file
record.set("email", "test@example.com")
record.set("password", "1234567890")
app.save(record)
}, (app) => { // optional revert operation
try {
let record = app.findAuthRecordByEmail("_superusers", "test@example.com")
app.delete(record)
} catch {
// silent errors (probably already deleted)
}
})`
##### [Creating collection programmatically](https://pocketbase.io/docs/js-migrations/\#creating-collection-programmatically)
_For all supported collection methods, you can refer to_
_[Collection operations](https://pocketbase.io/docs/js-collections)_
.
`// migrations/1687801090_create_clients_collection.js
migrate((app) => {
// missing default options, system fields like id, email, etc. are initialized automatically
// and will be merged with the provided configuration
let collection = new Collection({
type: "auth",
name: "clients",
listRule: "id = @request.auth.id",
viewRule: "id = @request.auth.id",
fields: [\
{\
type: "text",\
name: "company",\
required: true,\
max: 100,\
},\
{\
name: "url",\
type: "url",\
presentable: true,\
},\
],
passwordAuth: {
enabled: false,
},
otp: {
enabled: true,
},
indexes: [\
"CREATE INDEX idx_clients_company ON clients (company)"\
],
})
app.save(collection)
}, (app) => {
let collection = app.findCollectionByNameOrId("clients")
app.delete(collection)
})`
* * *
[Prev: Collection operations](https://pocketbase.io/docs/js-collections) [Next: Jobs scheduling](https://pocketbase.io/docs/js-jobs-scheduling)
## Realtime API Overview
API Realtime
The Realtime API is implemented via Server-Sent Events (SSE). Generally, it consists of 2 operations:
1. establish SSE connection
2. submit client subscriptions
SSE events are sent for **create**, **update**
and **delete** record operations.
**You could subscribe to a single record or to an entire collection.**
When you subscribe to a **single record**, the collection's
**ViewRule** will be used to determine whether the subscriber has access to receive the
event message.
When you subscribe to an **entire collection**, the collection's
**ListRule** will be used to determine whether the subscriber has access to receive the
event message.
**[Connect](https://pocketbase.io/docs/api-realtime/#connect)**
**GET**
/api/realtime
Establishes a new SSE connection and immediately sends a `PB_CONNECT` SSE event with the
created client ID.
**NB!** The user/superuser authorization happens during the first
[Set subscriptions](https://pocketbase.io/docs/api-realtime#set-subscriptions)
call.
If the connected client doesn't receive any new messages for 5 minutes, the server will send a
disconnect signal (this is to prevent forgotten/leaked connections). The connection will be
automatically reestablished if the client is still active (e.g. the browser tab is still open).
**[Set subscriptions](https://pocketbase.io/docs/api-realtime/#set-subscriptions)**
**POST**
/api/realtime
Sets new active client's subscriptions (and auto unsubscribes from the previous ones).
If `Authorization` header is set, will authorize the client SSE connection with the
associated user or superuser.
Body Parameters
| Param | Type | Description |
| --- | --- | --- |
| RequiredclientId | String | ID of the SSE client connection. |
| Optionalsubscriptions | Array | The new client subscriptions to set in the format:
`COLLECTION_ID_OR_NAME` or `COLLECTION_ID_OR_NAME/RECORD_ID`. You can also attach optional query and header parameters as serialized json to a single topic using the `options` query parameter, e.g.:
`COLLECTION_ID_OR_NAME/RECORD_ID?options={"query": {"abc": "123"}, "headers": {"x-token": "..."}}` Leave empty to unsubscribe from everything. |
Body parameters could be sent as _JSON_ or
_multipart/form-data_.
Responses
204 400 403 404
`null`
`{
"status": 400,
"message": "Something went wrong while processing your request.",
"data": {
"clientId": {
"code": "validation_required",
"message": "Missing required value."
}
}
}`
`{
"status": 403,
"message": "The current and the previous request authorization don't match.",
"data": {}
}`
`{
"status": 404,
"message": "Missing or invalid client id.",
"data": {}
}`
All of this is seamlessly handled by the SDKs using just the `subscribe` and
`unsubscribe` methods:
JavaScript
Dart
`import PocketBase from 'pocketbase';
const pb = new PocketBase('http://127.0.0.1:8090');
...
// (Optionally) authenticate
await pb.collection('users').authWithPassword('test@example.com', '1234567890');
// Subscribe to changes in any record in the collection
pb.collection('example').subscribe('*', function (e) {
console.log(e.action);
console.log(e.record);
}, { /* other options like expand, custom headers, etc. */ });
// Subscribe to changes only in the specified record
pb.collection('example').subscribe('RECORD_ID', function (e) {
console.log(e.action);
console.log(e.record);
}, { /* other options like expand, custom headers, etc. */ });
// Unsubscribe
pb.collection('example').unsubscribe('RECORD_ID'); // remove all 'RECORD_ID' subscriptions
pb.collection('example').unsubscribe('*'); // remove all '*' topic subscriptions
pb.collection('example').unsubscribe(); // remove all subscriptions in the collection`
`import 'package:pocketbase/pocketbase.dart';
final pb = PocketBase('http://127.0.0.1:8090');
...
// (Optionally) authenticate
await pb.collection('users').authWithPassword('test@example.com', '1234567890');
// Subscribe to changes in any record in the collection
pb.collection('example').subscribe('*', (e) {
print(e.action);
print(e.record);
}, /* other options like expand, custom headers, etc. */);
// Subscribe to changes only in the specified record
pb.collection('example').subscribe('RECORD_ID', (e) {
print(e.action);
print(e.record);
}, /* other options like expand, custom headers, etc. */);
// Unsubscribe
pb.collection('example').unsubscribe('RECORD_ID'); // remove all 'RECORD_ID' subscriptions
pb.collection('example').unsubscribe('*'); // remove all '*' topic subscriptions
pb.collection('example').unsubscribe(); // remove all subscriptions in the collection`
* * *
[Prev: API Records](https://pocketbase.io/docs/api-records) [Next: API Files](https://pocketbase.io/docs/api-files)
## PocketBase Migrations Guide
Migrations
PocketBase comes with a builtin DB and data migration utility, allowing you to version your DB structure,
create collections programmatically, initialize default settings, etc.
Because the migrations are regular Go functions, besides applying schema changes, they could be used also
to adjust existing data to fit the new schema or any other app specific logic that you want to run only
once.
And as a bonus, being `.go` files also ensure that the migrations will be embedded seamlessly in
your final executable.
- [Quick setup](https://pocketbase.io/docs/go-migrations/#quick-setup)
- [0\. Register the migrate command](https://pocketbase.io/docs/go-migrations/#0-register-the-migrate-command)
- [1\. Create new migration](https://pocketbase.io/docs/go-migrations/#1-create-new-migration)
- [2\. Load migrations](https://pocketbase.io/docs/go-migrations/#2-load-migrations)
- [3\. Run migrations](https://pocketbase.io/docs/go-migrations/#3-run-migrations)
- [Collections snapshot](https://pocketbase.io/docs/go-migrations/#collections-snapshot)
- [Migrations history](https://pocketbase.io/docs/go-migrations/#migrations-history)
- [Examples](https://pocketbase.io/docs/go-migrations/#examples)
- [Executing raw SQL statements](https://pocketbase.io/docs/go-migrations/#executing-raw-sql-statements)
- [Initialize default application settings](https://pocketbase.io/docs/go-migrations/#initialize-default-application-settings)
- [Creating initial superuser](https://pocketbase.io/docs/go-migrations/#creating-initial-superuser)
- [Creating collection programmatically](https://pocketbase.io/docs/go-migrations/#creating-collection-programmatically)
### [Quick setup](https://pocketbase.io/docs/go-migrations/\#quick-setup)
##### [0\. Register the migrate command](https://pocketbase.io/docs/go-migrations/\#0-register-the-migrate-command)
_You can find all available config options in the_
_[`migratecmd`](https://pkg.go.dev/github.com/pocketbase/pocketbase/plugins/migratecmd)_
_subpackage._
`// main.go
package main
import (
"log"
"strings"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/plugins/migratecmd"
// enable once you have at least one migration
// _ "yourpackage/migrations"
)
func main() {
app := pocketbase.New()
// loosely check if it was executed using "go run"
isGoRun := strings.HasPrefix(os.Args[0], os.TempDir())
migratecmd.MustRegister(app, app.RootCmd, migratecmd.Config{
// enable auto creation of migration files when making collection changes in the Dashboard
// (the isGoRun check is to enable it only during development)
Automigrate: isGoRun,
})
if err := app.Start(); err != nil {
log.Fatal(err)
}
}`
##### [1\. Create new migration](https://pocketbase.io/docs/go-migrations/\#1-create-new-migration)
To create a new blank migration you can run `migrate create`.
`// Since the "create" command makes sense only during development,
// it is expected the user to be in the app working directory
// and to be using "go run"
[root@dev app]$ go run . migrate create "your_new_migration"`
`// migrations/1655834400_your_new_migration.go
package migrations
import (
"github.com/pocketbase/pocketbase/core"
m "github.com/pocketbase/pocketbase/migrations"
)
func init() {
m.Register(func(app core.App) error {
// add up queries...
return nil
}, func(app core.App) error {
// add down queries...
return nil
})
}`
The above will create a new blank migration file inside the default command `migrations` directory.
Each migration file should have a single `m.Register(upFunc, downFunc)` call.
In the migration file, you are expected to write your "upgrade" code in the `upFunc` callback.
The `downFunc` is optional and it should contain the "downgrade" operations to revert the
changes made by the `upFunc`.
Both callbacks accept a transactional `core.App` instance.
You can explore the
[Database guide](https://pocketbase.io/docs/go-database),
[Collection operations](https://pocketbase.io/docs/go-collections) and
[Record operations](https://pocketbase.io/docs/go-records)
for more details how to interact with the database. You can also find
[some examples](https://pocketbase.io/docs/go-migrations/#examples) further below in ths guide.
##### [2\. Load migrations](https://pocketbase.io/docs/go-migrations/\#2-load-migrations)
To make your application aware of the registered migrations, you have to import the above
`migrations` package in one of your `main` package files:
`package main
import _ "yourpackage/migrations"
// ...`
##### [3\. Run migrations](https://pocketbase.io/docs/go-migrations/\#3-run-migrations)
New unapplied migrations are automatically executed when the application server starts, aka. on
`serve`.
Alternatively, you can also apply new migrations manually by running `migrate up`.
To revert the last applied migration(s), you can run `migrate down [number]`.
When manually applying or reverting migrations, the `serve` process needs to be restarted so
that it can refresh its cached collections state.
### [Collections snapshot](https://pocketbase.io/docs/go-migrations/\#collections-snapshot)
The `migrate collections` command generates a full snapshot of your current collections
configuration without having to type it manually. Similar to the `migrate create` command, this
will generate a new migration file in the
`migrations` directory.
`// Since the "collections" command makes sense only during development,
// it is expected the user to be in the app working directory
// and to be using "go run"
[root@dev app]$ go run . migrate collections`
By default the collections snapshot is imported in _extend_ mode, meaning that collections and
fields that don't exist in the snapshot are preserved. If you want the snapshot to _delete_
missing collections and fields, you can edit the generated file and change the last argument of
`ImportCollectionsByMarshaledJSON` method to `true`.
### [Migrations history](https://pocketbase.io/docs/go-migrations/\#migrations-history)
All applied migration filenames are stored in the internal `_migrations` table.
During local development often you might end up making various collection changes to test different approaches.
When `Automigrate` is enabled this could lead in a migration history with unnecessary intermediate
steps that may not be wanted in the final migration history.
To avoid the clutter and to prevent applying the intermediate steps in production, you can remove (or
squash) the unnecessary migration files manually and then update the local migrations history by running:
`[root@dev app]$ go run . migrate history-sync`
The above command will remove any entry from the `_migrations` table that doesn't have a related
migration file associated with it.
### [Examples](https://pocketbase.io/docs/go-migrations/\#examples)
##### [Executing raw SQL statements](https://pocketbase.io/docs/go-migrations/\#executing-raw-sql-statements)
`// migrations/1687801090_set_pending_status.go
package migrations
import (
"github.com/pocketbase/pocketbase/core"
m "github.com/pocketbase/pocketbase/migrations"
)
// set a default "pending" status to all empty status articles
func init() {
m.Register(func(app core.App) error {
_, err := app.DB().NewQuery("UPDATE articles SET status = 'pending' WHERE status = ''").Execute()
return err
}, nil)
}`
##### [Initialize default application settings](https://pocketbase.io/docs/go-migrations/\#initialize-default-application-settings)
`// migrations/1687801090_initial_settings.go
package migrations
import (
"github.com/pocketbase/pocketbase/core"
m "github.com/pocketbase/pocketbase/migrations"
)
func init() {
m.Register(func(app core.App) error {
settings := app.Settings()
// for all available settings fields you could check
// https://github.com/pocketbase/pocketbase/blob/develop/core/settings_model.go#L121-L130
settings.Meta.AppName = "test"
settings.Meta.AppURL = "https://example.com"
settings.Logs.MaxDays = 2
settings.Logs.LogAuthId = true
settings.Logs.LogIP = false
return app.Save(settings)
}, nil)
}`
##### [Creating initial superuser](https://pocketbase.io/docs/go-migrations/\#creating-initial-superuser)
_For all supported record methods, you can refer to_
_[Record operations](https://pocketbase.io/docs/go-records)_
.
_You can also create the initial super user using the_
_`./pocketbase superuser create EMAIL PASS`_
_command._
`// migrations/1687801090_initial_superuser.go
package migrations
import (
"github.com/pocketbase/pocketbase/core"
m "github.com/pocketbase/pocketbase/migrations"
)
func init() {
m.Register(func(app core.App) error {
superusers, err := app.FindCollectionByNameOrId(core.CollectionNameSuperusers)
if err != nil {
return err
}
record := core.NewRecord(superusers)
// note: the values can be eventually loaded via os.Getenv(key)
// or from a special local config file
record.Set("email", "test@example.com")
record.Set("password", "1234567890")
return app.Save(record)
}, func(app core.App) error { // optional revert operation
record, _ := app.FindAuthRecordByEmail(core.CollectionNameSuperusers, "test@example.com")
if record == nil {
return nil // probably already deleted
}
return app.Delete(record)
})
}`
##### [Creating collection programmatically](https://pocketbase.io/docs/go-migrations/\#creating-collection-programmatically)
_For all supported collection methods, you can refer to_
_[Collection operations](https://pocketbase.io/docs/go-collections)_
.
`// migrations/1687801090_create_clients_collection.go
package migrations
import (
"github.com/pocketbase/pocketbase/core"
"github.com/pocketbase/pocketbase/tools/types"
m "github.com/pocketbase/pocketbase/migrations"
)
func init() {
m.Register(func(app core.App) error {
// init a new auth collection with the default system fields and auth options
collection := core.NewAuthCollection("clients")
// restrict the list and view rules for record owners
collection.ListRule = types.Pointer("id = @request.auth.id")
collection.ViewRule = types.Pointer("id = @request.auth.id")
// add extra fields in addition to the default ones
collection.Fields.Add(
&core.TextField{
Name: "company",
Required: true,
Max: 100,
},
&core.URLField{
Name: "website",
Presentable: true,
},
)
// disable password auth and enable OTP only
collection.PasswordAuth.Enabled = false
collection.OTP.Enabled = true
collection.AddIndex("idx_clients_company", false, "company", "")
return app.Save(collection)
}, func(app core.App) error { // optional revert operation
collection, err := app.FindCollectionByNameOrId("clients")
if err != nil {
return err
}
return app.Delete(collection)
})
}`
* * *
[Prev: Collection operations](https://pocketbase.io/docs/go-collections) [Next: Jobs scheduling](https://pocketbase.io/docs/go-jobs-scheduling)
## PocketBase API Collections
API Collections
**[List collections](https://pocketbase.io/docs/api-collections/#list-collections)**
Returns a paginated Collections list.
Only superusers can perform this action.
JavaScript
Dart
`import PocketBase from 'pocketbase';
const pb = new PocketBase('http://127.0.0.1:8090');
...
await pb.collection("_superusers").authWithPassword('test@example.com', '1234567890');
// fetch a paginated collections list
const pageResult = await pb.collections.getList(1, 100, {
filter: 'created >= "2022-01-01 00:00:00"',
});
// you can also fetch all collections at once via getFullList
const collections = await pb.collections.getFullList({ sort: '-created' });
// or fetch only the first collection that matches the specified filter
const collection = await pb.collections.getFirstListItem('type="auth"');`
`import 'package:pocketbase/pocketbase.dart';
final pb = PocketBase('http://127.0.0.1:8090');
...
await pb.collection("_superusers").authWithPassword('test@example.com', '1234567890');
// fetch a paginated collections list
final pageResult = await pb.collections.getList(
page: 1,
perPage: 100,
filter: 'created >= "2022-01-01 00:00:00"',
);
// you can also fetch all collections at once via getFullList
final collections = await pb.collections.getFullList(sort: '-created');
// or fetch only the first collection that matches the specified filter
final collection = await pb.collections.getFirstListItem('type="auth"');`
###### API details
**GET**
/api/collections
Requires `Authorization:TOKEN`
Query parameters
| Param | Type | Description |
| --- | --- | --- |
| page | Number | The page (aka. offset) of the paginated list ( _default to 1_). |
| perPage | Number | The max returned collections per page ( _default to 30_). |
| sort | String | Specify the _ORDER BY_ fields. Add `-` / `+` (default) in front of the attribute for DESC / ASC order, e.g.: `// DESC by created and ASC by id ?sort=-created,id` **Supported collection sort fields:** `@random`, `id`, `created`, `updated`, `name`, `type`, `system` |
| filter | String | Filter expression to filter/search the returned collections list, e.g.: `?filter=(name~'abc' && created>'2022-01-01')` **Supported collection filter fields:** `id`, `created`, `updated`, `name`, `type`, `system` The syntax basically follows the format `OPERAND OPERATOR OPERAND`, where: - `OPERAND` \- could be any field literal, string (single or double quoted), number, null, true, false - `OPERATOR` \- is one of:
- `=` Equal - `!=` NOT equal - `>` Greater than - `>=` Greater than or equal - `<` Less than - `<=` Less than or equal - `~` Like/Contains (if not specified auto wraps the right string OPERAND in a "%" for wildcard match) - `!~` NOT Like/Contains (if not specified auto wraps the right string OPERAND in a "%" for wildcard match) - `?=` _Any/At least one of_ Equal - `?!=` _Any/At least one of_ NOT equal - `?>` _Any/At least one of_ Greater than - `?>=` _Any/At least one of_ Greater than or equal - `?<` _Any/At least one of_ Less than - `?<=` _Any/At least one of_ Less than or equal - `?~` _Any/At least one of_ Like/Contains (if not specified auto wraps the right string OPERAND in a "%" for wildcard match) - `?!~` _Any/At least one of_ NOT Like/Contains (if not specified auto wraps the right string OPERAND in a "%" for wildcard match) To group and combine several expressions you can use parenthesis `(...)`, `&&` (AND) and `||` (OR) tokens. Single line comments are also supported: `// Example comment`. |
| fields | String | Comma separated string of the fields to return in the JSON response _(by default returns all fields)_. Ex.:
`?fields=*,expand.relField.name` `*` targets all keys from the specific depth level. In addition, the following field modifiers are also supported: - `:excerpt(maxLength, withEllipsis?)`
Returns a short plain text version of the field string value.
Ex.: `?fields=*,description:excerpt(200,true)` |
| skipTotal | Boolean | If it is set the total counts query will be skipped and the response fields `totalItems` and `totalPages` will have `-1` value.
This could drastically speed up the search queries when the total counters are not needed or cursor based pagination is used.
`?fields=*,expand.relField.name` `*` targets all keys from the specific depth level. In addition, the following field modifiers are also supported: - `:excerpt(maxLength, withEllipsis?)`
Returns a short plain text version of the field string value.
Ex.: `?fields=*,description:excerpt(200,true)` |
Responses
200 401 403 404
`{
"id": "_pbc_2287844090",
"listRule": null,
"viewRule": null,
"createRule": null,
"updateRule": null,
"deleteRule": null,
"name": "posts",
"type": "base",
"fields": [\
{\
"autogeneratePattern": "[a-z0-9]{15}",\
"hidden": false,\
"id": "text3208210256",\
"max": 15,\
"min": 15,\
"name": "id",\
"pattern": "^[a-z0-9]+$",\
"presentable": false,\
"primaryKey": true,\
"required": true,\
"system": true,\
"type": "text"\
},\
{\
"autogeneratePattern": "",\
"hidden": false,\
"id": "text724990059",\
"max": 0,\
"min": 0,\
"name": "title",\
"pattern": "",\
"presentable": false,\
"primaryKey": false,\
"required": false,\
"system": false,\
"type": "text"\
},\
{\
"hidden": false,\
"id": "autodate2990389176",\
"name": "created",\
"onCreate": true,\
"onUpdate": false,\
"presentable": false,\
"system": false,\
"type": "autodate"\
},\
{\
"hidden": false,\
"id": "autodate3332085495",\
"name": "updated",\
"onCreate": true,\
"onUpdate": true,\
"presentable": false,\
"system": false,\
"type": "autodate"\
}\
],
"indexes": [],
"system": false
}`
`{
"status": 401,
"message": "The request requires valid record authorization token.",
"data": {}
}`
`{
"status": 403,
"message": "The authorized record is not allowed to perform this action.",
"data": {}
}`
`{
"status": 404,
"message": "The requested resource wasn't found.",
"data": {}
}`
**[Create collection](https://pocketbase.io/docs/api-collections/#create-collection)**
Creates a new Collection.
Only superusers can perform this action.
JavaScript
Dart
`import PocketBase from 'pocketbase';
const pb = new PocketBase('http://127.0.0.1:8090');
...
await pb.collection("_superusers").authWithPassword('test@example.com', '1234567890');
// create base collection
const base = await pb.collections.create({
name: 'exampleBase',
type: 'base',
fields: [\
{\
name: 'title',\
type: 'text',\
required: true,\
min: 10,\
},\
{\
name: 'status',\
type: 'bool',\
},\
],
});
// create auth collection
const auth = await pb.collections.create({
name: 'exampleAuth',
type: 'auth',
createRule: 'id = @request.auth.id',
updateRule: 'id = @request.auth.id',
deleteRule: 'id = @request.auth.id',
fields: [\
{\
name: 'name',\
type: 'text',\
}\
],
passwordAuth: {
enabled: true,
identityFields: ['email']
},
});
// create view collection
const view = await pb.collections.create({
name: 'exampleView',
type: 'view',
listRule: '@request.auth.id != ""',
viewRule: null,
// the schema will be autogenerated from the below query
viewQuery: 'SELECT id, name from posts',
});`
`import 'package:pocketbase/pocketbase.dart';
final pb = PocketBase('http://127.0.0.1:8090');
...
await pb.collection("_superusers").authWithPassword('test@example.com', '1234567890');
// create base collection
final base = await pb.collections.create(body: {
'name': 'exampleBase',
'type': 'base',
'fields': [\
{\
'name': 'title',\
'type': 'text',\
'required': true,\
'min': 10,\
},\
{\
'name': 'status',\
'type': 'bool',\
},\
],
});
// create auth collection
final auth = await pb.collections.create(body: {
'name': 'exampleAuth',
'type': 'auth',
'createRule': 'id = @request.auth.id',
'updateRule': 'id = @request.auth.id',
'deleteRule': 'id = @request.auth.id',
'fields': [\
{\
'name': 'name',\
'type': 'text',\
}\
],
'passwordAuth': {
'enabled': true,
'identityFields': ['email']
},
});
// create view collection
final view = await pb.collections.create(body: {
'name': 'exampleView',
'type': 'view',
'listRule': '@request.auth.id != ""',
'viewRule': null,
// the schema will be autogenerated from the below query
'viewQuery': 'SELECT id, name from posts',
});`
###### API details
**POST**
/api/collections
Requires `Authorization:TOKEN`
Body Parameters
Body parameters could be sent as _JSON_ or _multipart/form-data_.
`{
// 15 characters string to store as collection ID.
// If not set, it will be auto generated.
id (optional): string
// Unique collection name (used as a table name for the records table).
name (required): string
// Type of the collection.
// If not set, the collection type will be "base" by default.
type (optional): "base" | "view" | "auth"
// List with the collection fields.
// This field is optional and autopopulated for "view" collections based on the viewQuery.
fields (required|optional): Array
// The collection indexes and unique constraints.
// Note that "view" collections don't support indexes.
indexes (optional): Array
// Marks the collection as "system" to prevent being renamed, deleted or modify its API rules.
system (optional): boolean
// CRUD API rules
listRule (optional): null|string
viewRule (optional): null|string
createRule (optional): null|string
updateRule (optional): null|string
deleteRule (optional): null|string
// -------------------------------------------------------
// view options
// -------------------------------------------------------
viewQuery (required): string
// -------------------------------------------------------
// auth options
// -------------------------------------------------------
// API rule that gives admin-like permissions to allow fully managing the auth record(s),
// e.g. changing the password without requiring to enter the old one, directly updating the
// verified state or email, etc. This rule is executed in addition to the createRule and updateRule.
manageRule (optional): null|string
// API rule that could be used to specify additional record constraints applied after record
// authentication and right before returning the auth token response to the client.
//
// For example, to allow only verified users you could set it to "verified = true".
//
// Set it to empty string to allow any Auth collection record to authenticate.
//
// Set it to null to disallow authentication altogether for the collection.
authRule (optional): null|string
// AuthAlert defines options related to the auth alerts on new device login.
authAlert (optional): {
enabled (optional): boolean
emailTemplate (optional): {
subject (required): string
body (required): string
}
}
// OAuth2 specifies whether OAuth2 auth is enabled for the collection
// and which OAuth2 providers are allowed.
oauth2 (optional): {
enabled (optional): boolean
mappedFields (optional): {
id (optional): string
name (optional): string
username (optional): string
avatarURL (optional): string
}:
providers (optional): [\
{\
name (required): string\
clientId (required): string\
clientSecret (required): string\
authUrl (optional): string\
tokenUrl (optional): string\
userApiUrl (optional): string\
displayName (optional): string\
pkce (optional): null|boolean\
}\
]
}
// PasswordAuth defines options related to the collection password authentication.
passwordAuth (optional): {
enabled (optional): boolean
identityFields (required): Array
}
// MFA defines options related to the Multi-factor authentication (MFA).
mfa (optional):{
enabled (optional): boolean
duration (required): number
rule (optional): string
}
// OTP defines options related to the One-time password authentication (OTP).
otp (optional): {
enabled (optional): boolean
duration (required): number
length (required): number
emailTemplate (optional): {
subject (required): string
body (required): string
}
}
// Token configurations.
authToken (optional): {
duration (required): number
secret (required): string
}
passwordResetToken (optional): {
duration (required): number
secret (required): string
}
emailChangeToken (optional): {
duration (required): number
secret (required): string
}
verificationToken (optional): {
duration (required): number
secret (required): string
}
fileToken (optional): {
duration (required): number
secret (required): string
}
// Default email templates.
verificationTemplate (optional): {
subject (required): string
body (required): string
}
resetPasswordTemplate (optional): {
subject (required): string
body (required): string
}
confirmEmailChangeTemplate (optional): {
subject (required): string
body (required): string
}
}`
Query parameters
| Param | Type | Description |
| --- | --- | --- |
| fields | String | Comma separated string of the fields to return in the JSON response _(by default returns all fields)_. Ex.:
`?fields=*,expand.relField.name` `*` targets all keys from the specific depth level. In addition, the following field modifiers are also supported: - `:excerpt(maxLength, withEllipsis?)`
Returns a short plain text version of the field string value.
Ex.: `?fields=*,description:excerpt(200,true)` |
Responses
200 400 401 403
`{
"id": "_pbc_2287844090",
"listRule": null,
"viewRule": null,
"createRule": null,
"updateRule": null,
"deleteRule": null,
"name": "posts",
"type": "base",
"fields": [\
{\
"autogeneratePattern": "[a-z0-9]{15}",\
"hidden": false,\
"id": "text3208210256",\
"max": 15,\
"min": 15,\
"name": "id",\
"pattern": "^[a-z0-9]+$",\
"presentable": false,\
"primaryKey": true,\
"required": true,\
"system": true,\
"type": "text"\
},\
{\
"autogeneratePattern": "",\
"hidden": false,\
"id": "text724990059",\
"max": 0,\
"min": 0,\
"name": "title",\
"pattern": "",\
"presentable": false,\
"primaryKey": false,\
"required": false,\
"system": false,\
"type": "text"\
},\
{\
"hidden": false,\
"id": "autodate2990389176",\
"name": "created",\
"onCreate": true,\
"onUpdate": false,\
"presentable": false,\
"system": false,\
"type": "autodate"\
},\
{\
"hidden": false,\
"id": "autodate3332085495",\
"name": "updated",\
"onCreate": true,\
"onUpdate": true,\
"presentable": false,\
"system": false,\
"type": "autodate"\
}\
],
"indexes": [],
"system": false
}`
`{
"status": 400,
"message": "An error occurred while submitting the form.",
"data": {
"title": {
"code": "validation_required",
"message": "Missing required value."
}
}
}`
`{
"status": 401,
"message": "The request requires valid record authorization token.",
"data": {}
}`
`{
"status": 403,
"message": "The authorized record is not allowed to perform this action.",
"data": {}
}`
**[Update collection](https://pocketbase.io/docs/api-collections/#update-collection)**
Updates a single Collection by its ID or name.
Only superusers can perform this action.
JavaScript
Dart
`import PocketBase from 'pocketbase';
const pb = new PocketBase('http://127.0.0.1:8090');
...
await pb.collection("_superusers").authWithPassword('test@example.com', '123456');
const collection = await pb.collections.update('demo', {
name: 'new_demo',
listRule: 'created > "2022-01-01 00:00:00"',
});`
`import 'package:pocketbase/pocketbase.dart';
final pb = PocketBase('http://127.0.0.1:8090');
...
await pb.collection("_superusers").authWithPassword('test@example.com', '123456');
final collection = await pb.collections.update('demo', body: {
'name': 'new_demo',
'listRule': 'created > "2022-01-01 00:00:00"',
});`
###### API details
**PATCH**
/api/collections/ `collectionIdOrName`
Requires `Authorization:TOKEN`
Path parameters
| Param | Type | Description |
| --- | --- | --- |
| collectionIdOrName | String | ID or name of the collection to view. |
Body Parameters
Body parameters could be sent as _JSON_ or _multipart/form-data_.
`{
// Unique collection name (used as a table name for the records table).
name (required): string
// List with the collection fields.
// This field is optional and autopopulated for "view" collections based on the viewQuery.
fields (required|optional): Array
// The collection indexes and unique constriants.
// Note that "view" collections don't support indexes.
indexes (optional): Array
// Marks the collection as "system" to prevent being renamed, deleted or modify its API rules.
system (optional): boolean
// CRUD API rules
listRule (optional): null|string
viewRule (optional): null|string
createRule (optional): null|string
updateRule (optional): null|string
deleteRule (optional): null|string
// -------------------------------------------------------
// view options
// -------------------------------------------------------
viewQuery (required): string
// -------------------------------------------------------
// auth options
// -------------------------------------------------------
// API rule that gives admin-like permissions to allow fully managing the auth record(s),
// e.g. changing the password without requiring to enter the old one, directly updating the
// verified state or email, etc. This rule is executed in addition to the createRule and updateRule.
manageRule (optional): null|string
// API rule that could be used to specify additional record constraints applied after record
// authentication and right before returning the auth token response to the client.
//
// For example, to allow only verified users you could set it to "verified = true".
//
// Set it to empty string to allow any Auth collection record to authenticate.
//
// Set it to null to disallow authentication altogether for the collection.
authRule (optional): null|string
// AuthAlert defines options related to the auth alerts on new device login.
authAlert (optional): {
enabled (optional): boolean
emailTemplate (optional): {
subject (required): string
body (required): string
}
}
// OAuth2 specifies whether OAuth2 auth is enabled for the collection
// and which OAuth2 providers are allowed.
oauth2 (optional): {
enabled (optional): boolean
mappedFields (optional): {
id (optional): string
name (optional): string
username (optional): string
avatarURL (optional): string
}:
providers (optional): [\
{\
name (required): string\
clientId (required): string\
clientSecret (required): string\
authUrl (optional): string\
tokenUrl (optional): string\
userApiUrl (optional): string\
displayName (optional): string\
pkce (optional): null|boolean\
}\
]
}
// PasswordAuth defines options related to the collection password authentication.
passwordAuth (optional): {
enabled (optional): boolean
identityFields (required): Array
}
// MFA defines options related to the Multi-factor authentication (MFA).
mfa (optional):{
enabled (optional): boolean
duration (required): number
rule (optional): string
}
// OTP defines options related to the One-time password authentication (OTP).
otp (optional): {
enabled (optional): boolean
duration (required): number
length (required): number
emailTemplate (optional): {
subject (required): string
body (required): string
}
}
// Token configurations.
authToken (optional): {
duration (required): number
secret (required): string
}
passwordResetToken (optional): {
duration (required): number
secret (required): string
}
emailChangeToken (optional): {
duration (required): number
secret (required): string
}
verificationToken (optional): {
duration (required): number
secret (required): string
}
fileToken (optional): {
duration (required): number
secret (required): string
}
// Default email templates.
verificationTemplate (optional): {
subject (required): string
body (required): string
}
resetPasswordTemplate (optional): {
subject (required): string
body (required): string
}
confirmEmailChangeTemplate (optional): {
subject (required): string
body (required): string
}
}`
Query parameters
| Param | Type | Description |
| --- | --- | --- |
| fields | String | Comma separated string of the fields to return in the JSON response _(by default returns all fields)_. Ex.:
`?fields=*,expand.relField.name` `*` targets all keys from the specific depth level. In addition, the following field modifiers are also supported: - `:excerpt(maxLength, withEllipsis?)`
Returns a short plain text version of the field string value.
Ex.: `?fields=*,description:excerpt(200,true)` |
Responses
200 400 401 403
`{
"id": "_pbc_2287844090",
"listRule": null,
"viewRule": null,
"createRule": null,
"updateRule": null,
"deleteRule": null,
"name": "posts",
"type": "base",
"fields": [\
{\
"autogeneratePattern": "[a-z0-9]{15}",\
"hidden": false,\
"id": "text3208210256",\
"max": 15,\
"min": 15,\
"name": "id",\
"pattern": "^[a-z0-9]+$",\
"presentable": false,\
"primaryKey": true,\
"required": true,\
"system": true,\
"type": "text"\
},\
{\
"autogeneratePattern": "",\
"hidden": false,\
"id": "text724990059",\
"max": 0,\
"min": 0,\
"name": "title",\
"pattern": "",\
"presentable": false,\
"primaryKey": false,\
"required": false,\
"system": false,\
"type": "text"\
},\
{\
"hidden": false,\
"id": "autodate2990389176",\
"name": "created",\
"onCreate": true,\
"onUpdate": false,\
"presentable": false,\
"system": false,\
"type": "autodate"\
},\
{\
"hidden": false,\
"id": "autodate3332085495",\
"name": "updated",\
"onCreate": true,\
"onUpdate": true,\
"presentable": false,\
"system": false,\
"type": "autodate"\
}\
],
"indexes": [],
"system": false
}`
`{
"status": 400,
"message": "An error occurred while submitting the form.",
"data": {
"email": {
"code": "validation_required",
"message": "Missing required value."
}
}
}`
`{
"status": 401,
"message": "The request requires valid record authorization token.",
"data": {}
}`
`{
"status": 403,
"message": "The authorized record is not allowed to perform this action.",
"data": {}
}`
**[Delete collection](https://pocketbase.io/docs/api-collections/#delete-collection)**
Deletes a single Collection by its ID or name.
Only superusers can perform this action.
JavaScript
Dart
`import PocketBase from 'pocketbase';
const pb = new PocketBase('http://127.0.0.1:8090');
...
await pb.collection("_superusers").authWithPassword('test@example.com', '1234567890');
await pb.collections.delete('demo');`
`import 'package:pocketbase/pocketbase.dart';
final pb = PocketBase('http://127.0.0.1:8090');
...
await pb.collection("_superusers").authWithPassword('test@example.com', '1234567890');
await pb.collections.delete('demo');`
###### API details
**DELETE**
/api/collections/ `collectionIdOrName`
Requires `Authorization:TOKEN`
Path parameters
| Param | Type | Description |
| --- | --- | --- |
| collectionIdOrName | String | ID or name of the collection to view. |
Responses
204 400 401 403 404
`null`
`{
"status": 400,
"message": "Failed to delete collection. Make sure that the collection is not referenced by other collections.",
"data": {}
}`
`{
"status": 401,
"message": "The request requires valid record authorization token.",
"data": {}
}`
`{
"status": 403,
"message": "The authorized record is not allowed to perform this action.",
"data": {}
}`
`{
"status": 404,
"message": "The requested resource wasn't found.",
"data": {}
}`
**[Truncate collection](https://pocketbase.io/docs/api-collections/#truncate-collection)**
Deletes all the records of a single collection (including their related files and cascade delete
enabled relations).
Only superusers can perform this action.
JavaScript
Dart
`import PocketBase from 'pocketbase';
const pb = new PocketBase('http://127.0.0.1:8090');
...
await pb.collection("_superusers").authWithPassword('test@example.com', '1234567890');
await pb.collections.truncate('demo');`
`import 'package:pocketbase/pocketbase.dart';
final pb = PocketBase('http://127.0.0.1:8090');
...
await pb.collection("_superusers").authWithPassword('test@example.com', '1234567890');
await pb.collections.truncate('demo');`
###### API details
**DELETE**
/api/collections/ `collectionIdOrName`/truncate
Requires `Authorization:TOKEN`
Path parameters
| Param | Type | Description |
| --- | --- | --- |
| collectionIdOrName | String | ID or name of the collection to truncate. |
Responses
204 400 401 403 404
`null`
`{
"status": 400,
"message": "Failed to truncate collection (most likely due to required cascade delete record references).",
"data": {}
}`
`{
"status": 401,
"message": "The request requires valid record authorization token.",
"data": {}
}`
`{
"status": 403,
"message": "The authorized record is not allowed to perform this action.",
"data": {}
}`
`{
"status": 404,
"message": "The requested resource wasn't found.",
"data": {}
}`
**[Import collections](https://pocketbase.io/docs/api-collections/#import-collections)**
Bulk imports the provided _Collections_ configuration.
Only superusers can perform this action.
JavaScript
Dart
`import PocketBase from 'pocketbase';
const pb = new PocketBase('http://127.0.0.1:8090');
...
await pb.collection("_superusers").authWithPassword('test@example.com', '1234567890');
const importData = [\
{\
name: 'collection1',\
schema: [\
{\
name: 'status',\
type: 'bool',\
},\
],\
},\
{\
name: 'collection2',\
schema: [\
{\
name: 'title',\
type: 'text',\
},\
],\
},\
];
await pb.collections.import(importData, false);`
`import 'package:pocketbase/pocketbase.dart';
final pb = PocketBase('http://127.0.0.1:8090');
...
await pb.collection("_superusers").authWithPassword('test@example.com', '1234567890');
final importData = [\
CollectionModel(\
name: "collection1",\
schema: [\
SchemaField(name: "status", type: "bool"),\
],\
),\
CollectionModel(\
name: "collection2",\
schema: [\
SchemaField(name: "title", type: "text"),\
],\
),\
];
await pb.collections.import(importData, deleteMissing: false);`
###### API details
**PUT**
/api/collections/import
Requires `Authorization:TOKEN`
Body Parameters
| Param | Type | Description |
| --- | --- | --- |
| Requiredcollections | Array | List of collections to import (replace and create). |
| OptionaldeleteMissing | Boolean | If _true_ all existing collections and schema fields that are not present in the imported configuration **will be deleted**, including their related records data (default to _false_). |
Body parameters could be sent as _JSON_ or
_multipart/form-data_.
Responses
204 400 401 403
`null`
`{
"status": 400,
"message": "An error occurred while submitting the form.",
"data": {
"collections": {
"code": "collections_import_failure",
"message": "Failed to import the collections configuration."
}
}
}`
`{
"status": 401,
"message": "The request requires valid record authorization token.",
"data": {}
}`
`{
"status": 403,
"message": "The authorized record is not allowed to perform this action.",
"data": {}
}`
**[Scaffolds](https://pocketbase.io/docs/api-collections/#scaffolds)**
Returns an object will all of the collection types and their default fields
_(used primarily in the Dashboard UI)_.
Only superusers can perform this action.
JavaScript
Dart
`import PocketBase from 'pocketbase';
const pb = new PocketBase('http://127.0.0.1:8090');
...
await pb.collection("_superusers").authWithPassword('test@example.com', '1234567890');
const scaffolds = await pb.collections.getScaffolds();`
`import 'package:pocketbase/pocketbase.dart';
final pb = PocketBase('http://127.0.0.1:8090');
...
await pb.collection("_superusers").authWithPassword('test@example.com', '1234567890');
final scaffolds = await pb.collections.getScaffolds();`
###### API details
**GET**
/api/collections/meta/scaffolds
Requires `Authorization:TOKEN`
Responses
200 401 403 404
``{
"auth": {
"id": "",
"listRule": null,
"viewRule": null,
"createRule": null,
"updateRule": null,
"deleteRule": null,
"name": "",
"type": "auth",
"fields": [\
{\
"autogeneratePattern": "[a-z0-9]{15}",\
"hidden": false,\
"id": "text3208210256",\
"max": 15,\
"min": 15,\
"name": "id",\
"pattern": "^[a-z0-9]+$",\
"presentable": false,\
"primaryKey": true,\
"required": true,\
"system": true,\
"type": "text"\
},\
{\
"cost": 0,\
"hidden": true,\
"id": "password901924565",\
"max": 0,\
"min": 8,\
"name": "password",\
"pattern": "",\
"presentable": false,\
"required": true,\
"system": true,\
"type": "password"\
},\
{\
"autogeneratePattern": "[a-zA-Z0-9]{50}",\
"hidden": true,\
"id": "text2504183744",\
"max": 60,\
"min": 30,\
"name": "tokenKey",\
"pattern": "",\
"presentable": false,\
"primaryKey": false,\
"required": true,\
"system": true,\
"type": "text"\
},\
{\
"exceptDomains": null,\
"hidden": false,\
"id": "email3885137012",\
"name": "email",\
"onlyDomains": null,\
"presentable": false,\
"required": true,\
"system": true,\
"type": "email"\
},\
{\
"hidden": false,\
"id": "bool1547992806",\
"name": "emailVisibility",\
"presentable": false,\
"required": false,\
"system": true,\
"type": "bool"\
},\
{\
"hidden": false,\
"id": "bool256245529",\
"name": "verified",\
"presentable": false,\
"required": false,\
"system": true,\
"type": "bool"\
}\
],
"indexes": [\
"CREATE UNIQUE INDEX `idx_tokenKey_hclGvwhtqG` ON `test` (`tokenKey`)",\
"CREATE UNIQUE INDEX `idx_email_eyxYyd3gp1` ON `test` (`email`) WHERE `email` != ''"\
],
"created": "",
"updated": "",
"system": false,
"authRule": "",
"manageRule": null,
"authAlert": {
"enabled": true,
"emailTemplate": {
"subject": "Login from a new location",
"body": "..."
}
},
"oauth2": {
"providers": [],
"mappedFields": {
"id": "",
"name": "",
"username": "",
"avatarURL": ""
},
"enabled": false
},
"passwordAuth": {
"enabled": true,
"identityFields": [\
"email"\
]
},
"mfa": {
"enabled": false,
"duration": 1800,
"rule": ""
},
"otp": {
"enabled": false,
"duration": 180,
"length": 8,
"emailTemplate": {
"subject": "OTP for {APP_NAME}",
"body": "..."
}
},
"authToken": {
"duration": 604800
},
"passwordResetToken": {
"duration": 1800
},
"emailChangeToken": {
"duration": 1800
},
"verificationToken": {
"duration": 259200
},
"fileToken": {
"duration": 180
},
"verificationTemplate": {
"subject": "Verify your {APP_NAME} email",
"body": "..."
},
"resetPasswordTemplate": {
"subject": "Reset your {APP_NAME} password",
"body": "..."
},
"confirmEmailChangeTemplate": {
"subject": "Confirm your {APP_NAME} new email address",
"body": "..."
}
},
"base": {
"id": "",
"listRule": null,
"viewRule": null,
"createRule": null,
"updateRule": null,
"deleteRule": null,
"name": "",
"type": "base",
"fields": [\
{\
"autogeneratePattern": "[a-z0-9]{15}",\
"hidden": false,\
"id": "text3208210256",\
"max": 15,\
"min": 15,\
"name": "id",\
"pattern": "^[a-z0-9]+$",\
"presentable": false,\
"primaryKey": true,\
"required": true,\
"system": true,\
"type": "text"\
}\
],
"indexes": [],
"created": "",
"updated": "",
"system": false
},
"view": {
"id": "",
"listRule": null,
"viewRule": null,
"createRule": null,
"updateRule": null,
"deleteRule": null,
"name": "",
"type": "view",
"fields": [],
"indexes": [],
"created": "",
"updated": "",
"system": false,
"viewQuery": ""
}
}``
`{
"status": 401,
"message": "The request requires valid record authorization token.",
"data": {}
}`
`{
"status": 403,
"message": "The authorized record is not allowed to perform this action.",
"data": {}
}`
`{
"status": 404,
"message": "The requested resource wasn't found.",
"data": {}
}`
* * *
[Prev: API Files](https://pocketbase.io/docs/api-files) [Next: API Settings](https://pocketbase.io/docs/api-settings)
## PocketBase API Settings
API Settings
**[List settings](https://pocketbase.io/docs/api-settings/#list-settings)**
Returns a list with all available application settings.
Secret/password fields are automatically redacted with _\\*\\*\\*\*\*\*_ characters.
Only superusers can perform this action.
JavaScript
Dart
`import PocketBase from 'pocketbase';
const pb = new PocketBase('http://127.0.0.1:8090');
...
await pb.collection("_superusers").authWithPassword('test@example.com', '1234567890');
const settings = await pb.settings.getAll();`
`import 'package:pocketbase/pocketbase.dart';
final pb = PocketBase('http://127.0.0.1:8090');
...
await pb.collection("_superusers").authWithPassword('test@example.com', '1234567890');
final settings = await pb.settings.getAll();`
###### API details
**GET**
/api/settings
Requires `Authorization:TOKEN`
Query parameters
| Param | Type | Description |
| --- | --- | --- |
| fields | String | Comma separated string of the fields to return in the JSON response _(by default returns all fields)_. Ex.:
`?fields=*,expand.relField.name` `*` targets all keys from the specific depth level. In addition, the following field modifiers are also supported: - `:excerpt(maxLength, withEllipsis?)`
Returns a short plain text version of the field string value.
Ex.: `?fields=*,description:excerpt(200,true)` |
Responses
200 401 403
`{
"smtp": {
"enabled": false,
"port": 587,
"host": "smtp.example.com",
"username": "",
"authMethod": "",
"tls": true,
"localName": ""
},
"backups": {
"cron": "0 0 * * *",
"cronMaxKeep": 3,
"s3": {
"enabled": false,
"bucket": "",
"region": "",
"endpoint": "",
"accessKey": "",
"forcePathStyle": false
}
},
"s3": {
"enabled": false,
"bucket": "",
"region": "",
"endpoint": "",
"accessKey": "",
"forcePathStyle": false
},
"meta": {
"appName": "Acme",
"appURL": "https://example.com",
"senderName": "Support",
"senderAddress": "support@example.com",
"hideControls": false
},
"rateLimits": {
"rules": [\
{\
"label": "*:auth",\
"audience": "",\
"duration": 3,\
"maxRequests": 2\
},\
{\
"label": "*:create",\
"audience": "",\
"duration": 5,\
"maxRequests": 20\
},\
{\
"label": "/api/batch",\
"audience": "",\
"duration": 1,\
"maxRequests": 3\
},\
{\
"label": "/api/",\
"audience": "",\
"duration": 10,\
"maxRequests": 300\
}\
],
"enabled": false
},
"trustedProxy": {
"headers": [],
"useLeftmostIP": false
},
"batch": {
"enabled": true,
"maxRequests": 50,
"timeout": 3,
"maxBodySize": 0
},
"logs": {
"maxDays": 7,
"minLevel": 0,
"logIP": true,
"logAuthId": false
}
}`
`{
"status": 401,
"message": "The request requires valid record authorization token.",
"data": {}
}`
`{
"status": 403,
"message": "The authorized record is not allowed to perform this action.",
"data": {}
}`
**[Update settings](https://pocketbase.io/docs/api-settings/#update-settings)**
Bulk updates application settings and returns the updated settings list.
Only superusers can perform this action.
JavaScript
Dart
`import PocketBase from 'pocketbase';
const pb = new PocketBase('http://127.0.0.1:8090');
...
await pb.collection("_superusers").authWithPassword('test@example.com', '123456');
const settings = await pb.settings.update({
meta: {
appName: 'YOUR_APP',
appUrl: 'http://127.0.0.1:8090',
},
});`
`import 'package:pocketbase/pocketbase.dart';
final pb = PocketBase('http://127.0.0.1:8090');
...
await pb.collection("_superusers").authWithPassword('test@example.com', '123456');
final settings = await pb.settings.update(body: {
'meta': {
'appName': 'YOUR_APP',
'appUrl': 'http://127.0.0.1:8090',
},
});`
###### API details
**PATCH**
/api/settings
Requires `Authorization:TOKEN`
Body Parameters
| Param | Type | Description |
| --- | --- | --- |
| **meta** Application meta data (name, url, support email, etc.). |
| ├─Required _appName_ | String | The app name. |
| ├─Required _appUrl_ | String | The app public absolute url. |
| ├─Optional _hideControls_ | Boolean | Hides the collection create and update controls from the Dashboard. Useful to prevent making accidental schema changes when in production environment. |
| ├─Required _senderName_ | String | Transactional mails sender name. |
| ├─Required _senderAddress_ | String | Transactional mails sender address. |
| **logs** App logger settings. |
| └─Optional _maxDays_ | Number | Max retention period. Set to _0_ for no logs. |
| └─Optional _minLevel_ | Number | Specifies the minimum log persistent level.
The default log levels are:
- -4: DEBUG - 0: INFO - 4: WARN - 8: ERROR |
| └─Optional _logIP_ | Boolean | If enabled includes the client IP in the activity request logs. |
| └─Optional _logAuthId_ | Boolean | If enabled includes the authenticated record id in the activity request logs. |
| **backups** App data backups settings. |
| ├─Optional _cron_ | String | Cron expression to schedule auto backups, e.g. `0 0 * * *`. |
| ├─Optional _cronMaxKeep_ | Number | The max number of cron generated backups to keep before removing older entries. |
| └─Optional _s3_ | Object | S3 configuration (the same fields as for the S3 file storage settings). |
| **smtp** SMTP mail server settings. |
| ├─Optional _enabled_ | Boolean | Enable the use of the SMTP mail server for sending emails. |
| ├─Required _host_ | String | Mail server host (required if SMTP is enabled). |
| ├─Required _port_ | Number | Mail server port (required if SMTP is enabled). |
| ├─Optional _username_ | String | Mail server username. |
| ├─Optional _password_ | String | Mail server password. |
| ├─Optional _tls_ | Boolean | Whether to enforce TLS connection encryption.
When _false_ _StartTLS_ command is send, leaving the server to decide whether to upgrade the connection or not). |
| ├─Optional _authMethod_ | String | The SMTP AUTH method to use - _PLAIN_ or _LOGIN_ (used mainly by Microsoft).
Default to _PLAIN_ if empty. |
| └─Optional _localName_ | String | Optional domain name or (IP address) to use for the initial EHLO/HELO exchange.
If not explicitly set, `localhost` will be used.
Note that some SMTP providers, such as Gmail SMTP-relay, requires a proper domain name and and will reject attempts to use localhost. |
| **s3** S3 compatible file storage settings. |
| ├─Optional _enabled_ | Boolean | Enable the use of a S3 compatible storage. |
| ├─Required _bucket_ | String | S3 storage bucket (required if enabled). |
| ├─Required _region_ | String | S3 storage region (required if enabled). |
| ├─Required _endpoint_ | String | S3 storage public endpoint (required if enabled). |
| ├─Required _accessKey_ | String | S3 storage access key (required if enabled). |
| ├─Required _secret_ | String | S3 storage secret (required if enabled). |
| └─Optional _forcePathStyle_ | Boolean | Forces the S3 request to use path-style addressing, e.g. "https://s3.amazonaws.com/BUCKET/KEY" instead of the default "https://BUCKET.s3.amazonaws.com/KEY". |
| **batch** Batch logs settings. |
| ├─Optional _enabled_ | Boolean | Enable the batch Web APIs. |
| ├─Required _maxRequests_ | Number | The maximum allowed batch request to execute. |
| ├─Required _timeout_ | Number | The max duration in seconds to wait before cancelling the batch transaction. |
| └─Optional _maxBodySize_ | Number | The maximum allowed batch request body size in bytes.
If not set, fallbacks to max ~128MB. |
| **rateLimits** Rate limiter settings. |
| ├─Optional _enabled_ | Boolean | Enable the builtin rate limiter. |
| └─Optional _rules_ | Array | List of rate limit rules. Each rule have:
- `label` \- the identifier of the rule.
It could be a tag, complete path or path prerefix (when ends with \`/\`). - `maxRequests` \- the max allowed number of requests per duration. - `duration` \- specifies the interval (in seconds) per which to reset the counted/accumulated rate limiter tokens.. |
| **trustedProxy** Trusted proxy headers settings. |
| ├─Optional _headers_ | Array | List of explicit trusted header(s) to check. |
| └─Optional _useLeftmostIP_ | Boolean | Specifies to use the left-mostish IP from the trusted headers. |
Body parameters could be sent as _JSON_ or
_multipart/form-data_.
Query parameters
| Param | Type | Description |
| --- | --- | --- |
| fields | String | Comma separated string of the fields to return in the JSON response _(by default returns all fields)_. Ex.:
`?fields=*,expand.relField.name` `*` targets all keys from the specific depth level. In addition, the following field modifiers are also supported: - `:excerpt(maxLength, withEllipsis?)`
Returns a short plain text version of the field string value.
Ex.: `?fields=*,description:excerpt(200,true)` |
Responses
200 400 401 403
`{
"smtp": {
"enabled": false,
"port": 587,
"host": "smtp.example.com",
"username": "",
"authMethod": "",
"tls": true,
"localName": ""
},
"backups": {
"cron": "0 0 * * *",
"cronMaxKeep": 3,
"s3": {
"enabled": false,
"bucket": "",
"region": "",
"endpoint": "",
"accessKey": "",
"forcePathStyle": false
}
},
"s3": {
"enabled": false,
"bucket": "",
"region": "",
"endpoint": "",
"accessKey": "",
"forcePathStyle": false
},
"meta": {
"appName": "Acme",
"appURL": "https://example.com",
"senderName": "Support",
"senderAddress": "support@example.com",
"hideControls": false
},
"rateLimits": {
"rules": [\
{\
"label": "*:auth",\
"audience": "",\
"duration": 3,\
"maxRequests": 2\
},\
{\
"label": "*:create",\
"audience": "",\
"duration": 5,\
"maxRequests": 20\
},\
{\
"label": "/api/batch",\
"audience": "",\
"duration": 1,\
"maxRequests": 3\
},\
{\
"label": "/api/",\
"audience": "",\
"duration": 10,\
"maxRequests": 300\
}\
],
"enabled": false
},
"trustedProxy": {
"headers": [],
"useLeftmostIP": false
},
"batch": {
"enabled": true,
"maxRequests": 50,
"timeout": 3,
"maxBodySize": 0
},
"logs": {
"maxDays": 7,
"minLevel": 0,
"logIP": true,
"logAuthId": false
}
}`
`{
"status": 400,
"message": "An error occurred while submitting the form.",
"data": {
"meta": {
"appName": {
"code": "validation_required",
"message": "Missing required value."
}
}
}
}`
`{
"status": 401,
"message": "The request requires valid record authorization token.",
"data": {}
}`
`{
"status": 403,
"message": "The authorized record is not allowed to perform this action.",
"data": {}
}`
**[Test S3 storage connection](https://pocketbase.io/docs/api-settings/#test-s3-storage-connection)**
Performs a S3 storage connection test.
Only superusers can perform this action.
JavaScript
Dart
`import PocketBase from 'pocketbase';
const pb = new PocketBase('http://127.0.0.1:8090');
...
await pb.collection("_superusers").authWithPassword('test@example.com', '1234567890');
await pb.settings.testS3("backups");`
`import 'package:pocketbase/pocketbase.dart';
final pb = PocketBase('http://127.0.0.1:8090');
...
await pb.collection("_superusers").authWithPassword('test@example.com', '1234567890');
await pb.settings.testS3("backups");`
###### API details
**POST**
/api/settings/test/s3
Requires `Authorization:TOKEN`
Body Parameters
| Param | Type | Description |
| --- | --- | --- |
| Requiredfilesystem | String | The storage filesystem to test ( `storage` or `backups`). |
Body parameters could be sent as _JSON_ or
_multipart/form-data_.
Responses
204 400 401
`null`
`{
"status": 400,
"message": "Failed to initialize the S3 storage. Raw error:...",
"data": {}
}`
`{
"status": 401,
"message": "The request requires valid record authorization token.",
"data": {}
}`
**[Send test email](https://pocketbase.io/docs/api-settings/#send-test-email)**
Sends a test user email.
Only superusers can perform this action.
JavaScript
Dart
`import PocketBase from 'pocketbase';
const pb = new PocketBase('http://127.0.0.1:8090');
...
await pb.collection("_superusers").authWithPassword('test@example.com', '1234567890');
await pb.settings.testEmail("test@example.com", "verification");`
`import 'package:pocketbase/pocketbase.dart';
final pb = PocketBase('http://127.0.0.1:8090');
...
await pb.collection("_superusers").authWithPassword('test@example.com', '1234567890');
await pb.settings.testEmail("test@example.com", "verification");`
###### API details
**POST**
/api/settings/test/email
Requires `Authorization:TOKEN`
Body Parameters
| Param | Type | Description |
| --- | --- | --- |
| Optionalcollection | String | The name or id of the auth collection. Fallbacks to _\_superusers_ if not set. |
| Requiredemail | String | The receiver of the test email. |
| Requiredtemplate | String | The test email template to send: `verification`, `password-reset` or `email-change`. |
Body parameters could be sent as _JSON_ or
_multipart/form-data_.
Responses
204 400 401
`null`
`{
"status": 400,
"message": "Failed to send the test email.",
"data": {
"email": {
"code": "validation_required",
"message": "Missing required value."
}
}
}`
`{
"status": 401,
"message": "The request requires valid record authorization token.",
"data": {}
}`
**[Generate Apple client secret](https://pocketbase.io/docs/api-settings/#generate-apple-client-secret)**
Generates a new Apple OAuth2 client secret key.
Only superusers can perform this action.
JavaScript
Dart
`import PocketBase from 'pocketbase';
const pb = new PocketBase('http://127.0.0.1:8090');
...
await pb.collection("_superusers").authWithPassword('test@example.com', '1234567890');
await pb.settings.generateAppleClientSecret(clientId, teamId, keyId, privateKey, duration)`
`import 'package:pocketbase/pocketbase.dart';
final pb = PocketBase('http://127.0.0.1:8090');
...
await pb.collection("_superusers").authWithPassword('test@example.com', '1234567890');
await pb.settings.generateAppleClientSecret(clientId, teamId, keyId, privateKey, duration)`
###### API details
**POST**
/api/settings/apple/generate-client-secret
Requires `Authorization:TOKEN`
Body Parameters
| Param | Type | Description |
| --- | --- | --- |
| RequiredclientId | String | The identifier of your app (aka. Service ID). |
| RequiredteamId | String | 10-character string associated with your developer account (usually could be found next to your name in the Apple Developer site). |
| RequiredkeyId | String | 10-character key identifier generated for the "Sign in with Apple" private key associated with your developer account. |
| RequiredprivateKey | String | PrivateKey is the private key associated to your app. |
| Requiredduration | Number | Duration specifies how long the generated JWT token should be considered valid.
The specified value must be in seconds and max 15777000 (~6months). |
Body parameters could be sent as _JSON_ or
_multipart/form-data_.
Responses
200 400 401
`{
"secret": "..."
}`
`{
"status": 400,
"message": "Failed to generate client secret. Raw error:...",
"data": {}
}`
`{
"status": 401,
"message": "The request requires valid record authorization token.",
"data": {}
}`
* * *
[Prev: API Collections](https://pocketbase.io/docs/api-collections) [Next: API Logs](https://pocketbase.io/docs/api-logs)
## File API Overview
API Files
Files are uploaded, updated or deleted via the
[Records API](https://pocketbase.io/docs/api-records).
The File API is usually used to fetch/download a file resource (with support for basic image
manipulations, like generating thumbs).
**[Download / Fetch file](https://pocketbase.io/docs/api-files/#download-fetch-file)**
Downloads a single file resource (aka. the URL address to the file). Example:
``
###### API details
**GET**
/api/files/ `collectionIdOrName`/ `recordId`/ `filename`
Path parameters
| Param | Type | Description |
| --- | --- | --- |
| collectionIdOrName | String | ID or name of the collection whose record model contains the file resource. |
| recordId | String | ID of the record model that contains the file resource. |
| filename | String | Name of the file resource. |
Query parameters
| Param | Type | Description |
| --- | --- | --- |
| thumb | String | Get the thumb of the requested file.
The following thumb formats are currently supported: - **WxH** (e.g. 100x300) - crop to WxH viewbox (from center) - **WxHt** (e.g. 100x300t) - crop to WxH viewbox (from top) - **WxHb** (e.g. 100x300b) - crop to WxH viewbox (from bottom) - **WxHf** (e.g. 100x300f) - fit inside a WxH viewbox (without cropping) - **0xH** (e.g. 0x300) - resize to H height preserving the aspect ratio - **Wx0** (e.g. 100x0) - resize to W width preserving the aspect ratio If the thumb size is not defined in the file schema field options or the file resource is not an image (jpg, png, gif), then the original file resource is returned unmodified. |
| token | String | Optional **file token** for granting access to **protected file(s)**.
For an example, you can check ["Files upload and handling"](https://pocketbase.io/docs/files-handling/#protected-files). |
| download | Boolean | If it is set to a truthy value ( _1_, _t_, _true_) the file will be served with `Content-Disposition: attachment` header instructing the browser to ignore the file preview for pdf, images, videos, etc. and to directly download the file. |
Responses
200 400 404
`[file resource]`
`{
"status": 400,
"message": "Filesystem initialization failure.",
"data": {}
}`
`{
"status": 404,
"message": "The requested resource wasn't found.",
"data": {}
}`
**[Generate protected file token](https://pocketbase.io/docs/api-files/#generate-protected-file-token)**
Generates a **short-lived file token** for accessing
**protected file(s)**.
The client must be superuser or auth record authenticated (aka. have regular authorization token
sent with the request).
###### API details
**POST**
/api/files/token
Requires `Authorization:TOKEN`
Responses
200 400
`{
"token": "..."
}`
`{
"status": 400,
"message": "Failed to generate file token.",
"data": {}
}`
* * *
[Prev: API Realtime](https://pocketbase.io/docs/api-realtime) [Next: API Collections](https://pocketbase.io/docs/api-collections)
## Go Database Operations
Database
[`core.App`](https://pkg.go.dev/github.com/pocketbase/pocketbase/core#App)
is the main interface to interact with the database.
`App.DB()` returns a `dbx.Builder` that could run all kind of SQL statements, including
raw queries.
Most of the common DB operations are listed below, but you can find further information in the
[dbx package godoc](https://pkg.go.dev/github.com/pocketbase/dbx).
For more details and examples how to interact with Record and Collection models programmatically
you could also check [Collection operations](https://pocketbase.io/docs/go-collections)
and
[Record operations](https://pocketbase.io/docs/go-records) sections.
- [Executing queries](https://pocketbase.io/docs/go-database/#executing-queries)
- [Binding parameters](https://pocketbase.io/docs/go-database/#binding-parameters)
- [Query builder](https://pocketbase.io/docs/go-database/#query-builder)
- [Select(), AndSelect(), Distinct()](https://pocketbase.io/docs/go-database/#select-andselect-distinct)
- [From()](https://pocketbase.io/docs/go-database/#from)
- [Join()](https://pocketbase.io/docs/go-database/#join)
- [Where(), AndWhere(), OrWhere()](https://pocketbase.io/docs/go-database/#where-andwhere-orwhere)
- [OrderBy(), AndOrderBy()](https://pocketbase.io/docs/go-database/#orderby-andorderby)
- [GroupBy(), AndGroupBy()](https://pocketbase.io/docs/go-database/#groupby-andgroupby)
- [Having(), AndHaving(), OrHaving()](https://pocketbase.io/docs/go-database/#having-andhaving-orhaving)
- [Limit()](https://pocketbase.io/docs/go-database/#limit)
- [Offset()](https://pocketbase.io/docs/go-database/#offset)
- [Transaction](https://pocketbase.io/docs/go-database/#transaction)
### [Executing queries](https://pocketbase.io/docs/go-database/\#executing-queries)
To execute DB queries you can start with the `NewQuery("...")` statement and then call one of:
- ` Execute() `
\- for any query statement that is not meant to retrieve data:
`res, err := app.DB().
NewQuery("DELETE FROM articles WHERE status = 'archived'").
Execute()`
- ` One() `
\- to populate a single row into a struct:
``type User struct {
Id string `db:"id" json:"id"`
Status bool `db:"status" json:"status"`
Age int `db:"age" json:"age"`
Roles types.JSONArray[string] `db:"roles" json:"roles"`
}
user := User{}
err := app.DB().
NewQuery("SELECT id, status, age, roles FROM users WHERE id=1").
One(&user)``
- ` All() `
\- to populate multiple rows into a slice of structs:
``type User struct {
Id string `db:"id" json:"id"`
Status bool `db:"status" json:"status"`
Age int `db:"age" json:"age"`
Roles types.JSONArray[string] `db:"roles" json:"roles"`
}
users := []User{}
err := app.DB().
NewQuery("SELECT id, status, age, roles FROM users LIMIT 100").
All(&users)``
### [Binding parameters](https://pocketbase.io/docs/go-database/\#binding-parameters)
To prevent SQL injection attacks, you should use named parameters for any expression value that comes from
user input. This could be done using the named `{:paramName}`
placeholders in your SQL statement and then define the parameter values for the query with
`Bind(params)`. For example:
``type Post struct {
Name string `db:"name" json:"name"`
Created types.DateTime `db:"created" json:"created"`
}
posts := []Post{}
err := app.DB().
NewQuery("SELECT name, created FROM posts WHERE created >= {:from} and created <= {:to}").
Bind(dbx.Params{
"from": "2023-06-25 00:00:00.000Z",
"to": "2023-06-28 23:59:59.999Z",
}).
All(&posts)``
### [Query builder](https://pocketbase.io/docs/go-database/\#query-builder)
Instead of writing plain SQLs, you can also compose SQL statements programmatically using the db query
builder.
Every SQL keyword has a corresponding query building method. For example, `SELECT` corresponds
to `Select()`, `FROM` corresponds to `From()`,
`WHERE` corresponds to `Where()`, and so on.
``users := []struct {
Id string `db:"id" json:"id"`
Email string `db:"email" json:"email"`
}{}
app.DB().
Select("id", "email").
From("users").
AndWhere(dbx.Like("email", "example.com")).
Limit(100).
OrderBy("created ASC").
All(&users)``
##### [Select(), AndSelect(), Distinct()](https://pocketbase.io/docs/go-database/\#select-andselect-distinct)
The `Select(...cols)` method initializes a `SELECT` query builder. It accepts a list
of the column names to be selected.
To add additional columns to an existing select query, you can call `AndSelect()`.
To select distinct rows, you can call `Distinct(true)`.
`app.DB().
Select("id", "avatar as image").
AndSelect("(firstName || ' ' || lastName) as fullName").
Distinct(true)
...`
##### [From()](https://pocketbase.io/docs/go-database/\#from)
The `From(...tables)` method specifies which tables to select from (plain table names are automatically
quoted).
`app.DB().
Select("table1.id", "table2.name").
From("table1", "table2")
...`
##### [Join()](https://pocketbase.io/docs/go-database/\#join)
The `Join(type, table, on)` method specifies a `JOIN` clause. It takes 3 parameters:
- `type` \- join type string like `INNER JOIN`, `LEFT JOIN`, etc.
- `table` \- the name of the table to be joined
- `on` \- optional `dbx.Expression` as an `ON` clause
For convenience, you can also use the shortcuts `InnerJoin(table, on)`,
`LeftJoin(table, on)`,
`RightJoin(table, on)` to specify `INNER JOIN`, `LEFT JOIN` and
`RIGHT JOIN`, respectively.
`app.DB().
Select("users.*").
From("users").
InnerJoin("profiles", dbx.NewExp("profiles.user_id = users.id")).
Join("FULL OUTER JOIN", "department", dbx.NewExp("department.id = {:id}", dbx.Params{ "id": "someId" }))
...`
##### [Where(), AndWhere(), OrWhere()](https://pocketbase.io/docs/go-database/\#where-andwhere-orwhere)
The `Where(exp)` method specifies the `WHERE` condition of the query.
You can also use `AndWhere(exp)` or `OrWhere(exp)` to append additional one or more
conditions to an existing `WHERE` clause.
Each where condition accepts a single `dbx.Expression` (see below for full list).
`/*
SELECT users.*
FROM users
WHERE id = "someId" AND
status = "public" AND
name like "%john%" OR
(
role = "manager" AND
fullTime IS TRUE AND
experience > 10
)
*/
app.DB().
Select("users.*").
From("users").
Where(dbx.NewExp("id = {:id}", dbx.Params{ "id": "someId" })).
AndWhere(dbx.HashExp{"status": "public"}).
AndWhere(dbx.Like("name", "john")).
OrWhere(dbx.And(
dbx.HashExp{
"role": "manager",
"fullTime": true,
},
dbx.NewExp("experience > {:exp}", dbx.Params{ "exp": 10 })
))
...`
The following `dbx.Expression` methods are available:
- ` dbx.NewExp(raw, optParams) `
Generates an expression with the specified raw query fragment. Use the `optParams` to bind
`dbx.Params` to the expression.
`dbx.NewExp("status = 'public'")
dbx.NewExp("total > {:min} AND total < {:max}", dbx.Params{ "min": 10, "max": 30 })`
- ` dbx.HashExp{k:v} `
Generates a hash expression from a map whose keys are DB column names which need to be filtered according
to the corresponding values.
`// slug = "example" AND active IS TRUE AND tags in ("tag1", "tag2", "tag3") AND parent IS NULL
dbx.HashExp{
"slug": "example",
"active": true,
"tags": []any{"tag1", "tag2", "tag3"},
"parent": nil,
}`
- ` dbx.Not(exp) `
Negates a single expression by wrapping it with `NOT()`.
`// NOT(status = 1)
dbx.Not(dbx.NewExp("status = 1"))`
- ` dbx.And(...exps) `
Creates a new expression by concatenating the specified ones with `AND`.
`// (status = 1 AND username like "%john%")
dbx.And(
dbx.NewExp("status = 1"),
dbx.Like("username", "john"),
)`
- ` dbx.Or(...exps) `
Creates a new expression by concatenating the specified ones with `OR`.
`// (status = 1 OR username like "%john%")
dbx.Or(
dbx.NewExp("status = 1"),
dbx.Like("username", "john")
)`
- ` dbx.In(col, ...values) `
Generates an `IN` expression for the specified column and the list of allowed values.
`// status IN ("public", "reviewed")
dbx.In("status", "public", "reviewed")`
- ` dbx.NotIn(col, ...values) `
Generates an `NOT IN` expression for the specified column and the list of allowed values.
`// status NOT IN ("public", "reviewed")
dbx.NotIn("status", "public", "reviewed")`
- ` dbx.Like(col, ...values) `
Generates a `LIKE` expression for the specified column and the possible strings that the
column should be like. If multiple values are present, the column should be like
**all** of them.
By default, each value will be surrounded by _"%"_ to enable partial matching. Special
characters like _"%"_, _"\\"_, _"\_"_ will also be properly escaped. You may call
`Escape(...pairs)` and/or `Match(left, right)` to change the default behavior.
`// name LIKE "%test1%" AND name LIKE "%test2%"
dbx.Like("name", "test1", "test2")
// name LIKE "test1%"
dbx.Like("name", "test1").Match(false, true)`
- ` dbx.NotLike(col, ...values) `
Generates a `NOT LIKE` expression in similar manner as `Like()`.
`// name NOT LIKE "%test1%" AND name NOT LIKE "%test2%"
dbx.NotLike("name", "test1", "test2")
// name NOT LIKE "test1%"
dbx.NotLike("name", "test1").Match(false, true)`
- ` dbx.OrLike(col, ...values) `
This is similar to `Like()` except that the column must be one of the provided values, aka.
multiple values are concatenated with `OR` instead of `AND`.
`// name LIKE "%test1%" OR name LIKE "%test2%"
dbx.OrLike("name", "test1", "test2")
// name LIKE "test1%" OR name LIKE "test2%"
dbx.OrLike("name", "test1", "test2").Match(false, true)`
- ` dbx.OrNotLike(col, ...values) `
This is similar to `NotLike()` except that the column must not be one of the provided
values, aka. multiple values are concatenated with `OR` instead of `AND`.
`// name NOT LIKE "%test1%" OR name NOT LIKE "%test2%"
dbx.OrNotLike("name", "test1", "test2")
// name NOT LIKE "test1%" OR name NOT LIKE "test2%"
dbx.OrNotLike("name", "test1", "test2").Match(false, true)`
- ` dbx.Exists(exp) `
Prefix with `EXISTS` the specified expression (usually a subquery).
`// EXISTS (SELECT 1 FROM users WHERE status = 'active')
dbx.Exists(dbx.NewExp("SELECT 1 FROM users WHERE status = 'active'"))`
- ` dbx.NotExists(exp) `
Prefix with `NOT EXISTS` the specified expression (usually a subquery).
`// NOT EXISTS (SELECT 1 FROM users WHERE status = 'active')
dbx.NotExists(dbx.NewExp("SELECT 1 FROM users WHERE status = 'active'"))`
- ` dbx.Between(col, from, to) `
Generates a `BETWEEN` expression with the specified range.
`// age BETWEEN 3 and 99
dbx.Between("age", 3, 99)`
- ` dbx.NotBetween(col, from, to) `
Generates a `NOT BETWEEN` expression with the specified range.
`// age NOT BETWEEN 3 and 99
dbx.NotBetween("age", 3, 99)`
##### [OrderBy(), AndOrderBy()](https://pocketbase.io/docs/go-database/\#orderby-andorderby)
The `OrderBy(...cols)` specifies the `ORDER BY` clause of the query.
A column name can contain _"ASC"_ or _"DESC"_ to indicate its ordering direction.
You can also use `AndOrderBy(...cols)` to append additional columns to an existing
`ORDER BY` clause.
`app.DB().
Select("users.*").
From("users").
OrderBy("created ASC", "updated DESC").
AndOrderBy("title ASC")
...`
##### [GroupBy(), AndGroupBy()](https://pocketbase.io/docs/go-database/\#groupby-andgroupby)
The `GroupBy(...cols)` specifies the `GROUP BY` clause of the query.
You can also use `AndGroupBy(...cols)` to append additional columns to an existing
`GROUP BY` clause.
`app.DB().
Select("users.*").
From("users").
GroupBy("department", "level")
...`
##### [Having(), AndHaving(), OrHaving()](https://pocketbase.io/docs/go-database/\#having-andhaving-orhaving)
The `Having(exp)` specifies the `HAVING` clause of the query.
Similarly to
`Where(exp)`, it accept a single `dbx.Expression` (see all available expressions
listed above).
You can also use `AndHaving(exp)` or `OrHaving(exp)` to append additional one or
more conditions to an existing `HAVING` clause.
`app.DB().
Select("users.*").
From("users").
GroupBy("department", "level").
Having(dbx.NewExp("sum(level) > {:sum}", dbx.Params{ sum: 10 }))
...`
##### [Limit()](https://pocketbase.io/docs/go-database/\#limit)
The `Limit(number)` method specifies the `LIMIT` clause of the query.
`app.DB().
Select("users.*").
From("users").
Limit(30)
...`
##### [Offset()](https://pocketbase.io/docs/go-database/\#offset)
The `Offset(number)` method specifies the `OFFSET` clause of the query. Usually used
together with `Limit(number)`.
`app.DB().
Select("users.*").
From("users").
Offset(5).
Limit(30)
...`
### [Transaction](https://pocketbase.io/docs/go-database/\#transaction)
To execute multiple queries in a transaction you can use
[`app.RunInTransaction(fn)`](https://pkg.go.dev/github.com/pocketbase/pocketbase/core#BaseApp.RunInTransaction)
.
The DB operations are persisted only if the transaction returns `nil`.
It is safe to nest `RunInTransaction` calls as long as you use the callback's
`txApp` argument.
Inside the transaction function always use its `txApp` argument and not the original
`app` instance because we allow only a single writer/transaction at a time and it could
result in a deadlock.
To avoid performance issues, try to minimize slow/long running tasks such as sending emails,
connecting to external services, etc. as part of the transaction.
`err := app.RunInTransaction(func(txApp core.App) error {
// update a record
record, err := txApp.FindRecordById("articles", "RECORD_ID")
if err != nil {
return err
}
record.Set("status", "active")
if err := txApp.Save(record); err != nil {
return err
}
// run a custom raw query (doesn't fire event hooks)
rawQuery := "DELETE FROM articles WHERE status = 'pending'"
if _, err := txApp.NonconcurrentDB().NewQuery(rawQuery).Execute(); err != nil {
return err
}
return nil
})`
* * *
[Prev: Routing](https://pocketbase.io/docs/go-routing) [Next: Record operations](https://pocketbase.io/docs/go-records)
## Collection Operations
Collection operations
Collections are usually managed via the Dashboard interface, but there are some situations where you may
want to create or edit a collection programmatically (usually as part of a
[DB migration](https://pocketbase.io/docs/js-migrations)). You can find all available Collection related operations
and methods in
[`$app`](https://pocketbase.io/jsvm/modules/_app.html)
and
[`Collection`](https://pocketbase.io/jsvm/classes/Collection.html)
, but below are listed some of the most common ones:
- [Fetch collections](https://pocketbase.io/docs/js-collections/#fetch-collections)
- [Fetch single collection](https://pocketbase.io/docs/js-collections/#fetch-single-collection)
- [Fetch multiple collections](https://pocketbase.io/docs/js-collections/#fetch-multiple-collections)
- [Custom collection query](https://pocketbase.io/docs/js-collections/#custom-collection-query)
- [Field definitions](https://pocketbase.io/docs/js-collections/#field-definitions)
- [Create new collection](https://pocketbase.io/docs/js-collections/#create-new-collection)
- [Update existing collection](https://pocketbase.io/docs/js-collections/#update-existing-collection)
- [Delete collection](https://pocketbase.io/docs/js-collections/#delete-collection)
### [Fetch collections](https://pocketbase.io/docs/js-collections/\#fetch-collections)
##### [Fetch single collection](https://pocketbase.io/docs/js-collections/\#fetch-single-collection)
All single collection retrieval methods throw an error if no collection is found.
`let collection = $app.findCollectionByNameOrId("example")`
##### [Fetch multiple collections](https://pocketbase.io/docs/js-collections/\#fetch-multiple-collections)
All multiple collections retrieval methods return an empty array if no collections are found.
`let allCollections = $app.findAllCollections(/* optional types */)
// only specific types
let authAndViewCollections := $app.findAllCollections("auth", "view")`
##### [Custom collection query](https://pocketbase.io/docs/js-collections/\#custom-collection-query)
In addition to the above query helpers, you can also create custom Collection queries using
[`$app.collectionQuery()`](https://pocketbase.io/jsvm/functions/_app.collectionQuery.html)
method. It returns a SELECT DB builder that can be used with the same methods described in the
[Database guide](https://pocketbase.io/docs/js-database).
`let collections = arrayOf(new Collection)
$app.collectionQuery().
andWhere($dbx.hashExp({"viewRule": null})).
orderBy("created DESC").
all(collections)`
### [Field definitions](https://pocketbase.io/docs/js-collections/\#field-definitions)
All collection fields _(with exception of the `JSONField`)_ are non-nullable and
uses a zero-default for their respective type as fallback value when missing.
- [`new BoolField({ ... })`](https://pocketbase.io/jsvm/classes/BoolField.html)
- [`new NumberField({ ... })`](https://pocketbase.io/jsvm/classes/NumberField.html)
- [`new TextField({ ... })`](https://pocketbase.io/jsvm/classes/TextField.html)
- [`new EmailField({ ... })`](https://pocketbase.io/jsvm/classes/EmailField.html)
- [`new URLField({ ... })`](https://pocketbase.io/jsvm/classes/URLField.html)
- [`new EditorField({ ... })`](https://pocketbase.io/jsvm/classes/EditorField.html)
- [`new DateField({ ... })`](https://pocketbase.io/jsvm/classes/DateField.html)
- [`new AutodateField({ ... })`](https://pocketbase.io/jsvm/classes/AutodateField.html)
- [`new SelectField({ ... })`](https://pocketbase.io/jsvm/classes/SelectField.html)
- [`new FileField({ ... })`](https://pocketbase.io/jsvm/classes/FileField.html)
- [`new RelationField({ ... })`](https://pocketbase.io/jsvm/classes/RelationField.html)
- [`new JSONField({ ... })`](https://pocketbase.io/jsvm/classes/JSONField.html)
### [Create new collection](https://pocketbase.io/docs/js-collections/\#create-new-collection)
`// missing default options, system fields like id, email, etc. are initialized automatically
// and will be merged with the provided configuration
let collection = new Collection({
type: "base", // base | auth | view
name: "example",
listRule: null,
viewRule: "@request.auth.id != ''",
createRule: "",
updateRule: "@request.auth.id != ''",
deleteRule: null,
fields: [\
{\
name: "title",\
type: "text",\
required: true,\
max: 10,\
},\
{\
name: "user",\
type: "relation",\
required: true,\
maxSelect: 1,\
collectionId: "ae40239d2bc4477",\
cascadeDelete: true,\
},\
],
indexes: [\
"CREATE UNIQUE INDEX idx_user ON example (user)"\
],
})
// validate and persist
// (use saveNoValidate to skip fields validation)
$app.save(collection)`
### [Update existing collection](https://pocketbase.io/docs/js-collections/\#update-existing-collection)
`let collection = $app.findCollectionByNameOrId("example")
// change the collection name
collection.name = "example_update"
// add new editor field
collection.fields.add(new EditorField({
name: "description",
required: true,
}))
// change existing field
// (returns a pointer and direct modifications are allowed without the need of reinsert)
let titleField = collection.fields.getByName("title")
titleField.min = 10
// or: collection.indexes.push("CREATE INDEX idx_example_title ON example (title)")
collection.addIndex("idx_example_title", false, "title", "")
// validate and persist
// (use saveNoValidate to skip fields validation)
$app.save(collection)`
### [Delete collection](https://pocketbase.io/docs/js-collections/\#delete-collection)
`let collection = $app.findCollectionByNameOrId("example")
$app.delete(collection)`
* * *
[Prev: Record operations](https://pocketbase.io/docs/js-records) [Next: Migrations](https://pocketbase.io/docs/js-migrations)
## PocketBase Record Operations
Record operations
The most common task when extending PocketBase probably would be querying and working with your collection
records.
You could find detailed documentation about all the supported Record model methods in
[`core.Record`](https://pocketbase.io/jsvm/interfaces/core.Record.html)
type interface but below are some examples with the most common ones.
- [Set field value](https://pocketbase.io/docs/js-records/#set-field-value)
- [Get field value](https://pocketbase.io/docs/js-records/#get-field-value)
- [Auth accessors](https://pocketbase.io/docs/js-records/#auth-accessors)
- [Copies](https://pocketbase.io/docs/js-records/#copies)
- [Hide/Unhide fields](https://pocketbase.io/docs/js-records/#hideunhide-fields)
- [Fetch records](https://pocketbase.io/docs/js-records/#fetch-records)
- [Fetch single record](https://pocketbase.io/docs/js-records/#fetch-single-record)
- [Fetch multiple records](https://pocketbase.io/docs/js-records/#fetch-multiple-records)
- [Fetch auth records](https://pocketbase.io/docs/js-records/#fetch-auth-records)
- [Custom record query](https://pocketbase.io/docs/js-records/#custom-record-query)
- [Create new record](https://pocketbase.io/docs/js-records/#create-new-record)
- [Create new record programmatically](https://pocketbase.io/docs/js-records/#create-new-record-programmatically)
- [Intercept create request](https://pocketbase.io/docs/js-records/#intercept-create-request)
- [Update existing record](https://pocketbase.io/docs/js-records/#update-existing-record)
- [Update existing record programmatically](https://pocketbase.io/docs/js-records/#update-existing-record-programmatically)
- [Intercept update request](https://pocketbase.io/docs/js-records/#intercept-update-request)
- [Delete record](https://pocketbase.io/docs/js-records/#delete-record)
- [Transaction](https://pocketbase.io/docs/js-records/#transaction)
- [Programmatically expanding relations](https://pocketbase.io/docs/js-records/#programmatically-expanding-relations)
- [Check if record can be accessed](https://pocketbase.io/docs/js-records/#check-if-record-can-be-accessed)
- [Generating and validating tokens](https://pocketbase.io/docs/js-records/#generating-and-validating-tokens)
### [Set field value](https://pocketbase.io/docs/js-records/\#set-field-value)
`// sets the value of a single record field
// (field type specific modifiers are also supported)
record.set("title", "example")
record.set("users+", "6jyr1y02438et52") // append to existing value
// populates a record from a data map
// (calls set() for each entry of the map)
record.load(data)`
### [Get field value](https://pocketbase.io/docs/js-records/\#get-field-value)
`// retrieve a single record field value
// (field specific modifiers are also supported)
record.get("someField") // -> any (without cast)
record.getBool("someField") // -> cast to bool
record.getString("someField") // -> cast to string
record.getInt("someField") // -> cast to int
record.getFloat("someField") // -> cast to float64
record.getDateTime("someField") // -> cast to types.DateTime
record.getStringSlice("someField") // -> cast to []string
// retrieve the new uploaded files
// (e.g. for inspecting and modifying the file(s) before save)
record.getUnsavedFiles("someFileField")
// unmarshal a single json field value into the provided result
let result = new DynamicModel({ ... })
record.unmarshalJSONField("someJsonField", result)
// retrieve a single or multiple expanded data
record.expandedOne("author") // -> as null|Record
record.expandedAll("categories") // -> as []Record
// export all the public safe record fields in a plain object
// (note: "json" type field values are exported as raw bytes array)
record.publicExport()`
### [Auth accessors](https://pocketbase.io/docs/js-records/\#auth-accessors)
`record.isSuperuser() // alias for record.collection().name == "_superusers"
record.email() // alias for record.get("email")
record.setEmail(email) // alias for record.set("email", email)
record.verified() // alias for record.get("verified")
record.setVerified(false) // alias for record.set("verified", false)
record.tokenKey() // alias for record.get("tokenKey")
record.setTokenKey(key) // alias for record.set("tokenKey", key)
record.refreshTokenKey() // alias for record.set("tokenKey:autogenerate", "")
record.validatePassword(pass)
record.setPassword(pass) // alias for record.set("password", pass)
record.setRandomPassword() // sets cryptographically random 30 characters string as password`
### [Copies](https://pocketbase.io/docs/js-records/\#copies)
`// returns a shallow copy of the current record model populated
// with its ORIGINAL db data state and everything else reset to the defaults
// (usually used for comparing old and new field values)
record.original()
// returns a shallow copy of the current record model populated
// with its LATEST data state and everything else reset to the defaults
// (aka. no expand, no custom fields and with default visibility flags)
record.fresh()
// returns a shallow copy of the current record model populated
// with its ALL collection and custom fields data, expand and visibility flags
record.clone()`
### [Hide/Unhide fields](https://pocketbase.io/docs/js-records/\#hideunhide-fields)
Collection fields can be marked as "Hidden" from the Dashboard to prevent regular user access to the field
values.
Record models provide an option to further control the fields serialization visibility in addition to the
"Hidden" fields option using the
[`record.hide(fieldNames...)`](https://pocketbase.io/jsvm/interfaces/core.Record.html#hide)
and
[`record.unhide(fieldNames...)`](https://pocketbase.io/jsvm/interfaces/core.Record.html#unhide)
methods.
Often the `hide/unhide` methods are used in combination with the `onRecordEnrich` hook
invoked on every record enriching (list, view, create, update, realtime change, etc.). For example:
`onRecordEnrich((e) => {
// dynamically show/hide a record field depending on whether the current
// authenticated user has a certain "role" (or any other field constraint)
if (
!e.requestInfo.auth ||
(!e.requestInfo.auth.isSuperuser() && e.requestInfo.auth.get("role") != "staff")
) {
e.record.hide("someStaffOnlyField")
}
e.next()
}, "articles")`
For custom fields, not part of the record collection schema, it is required to call explicitly
`record.withCustomData(true)` to allow them in the public serialization.
### [Fetch records](https://pocketbase.io/docs/js-records/\#fetch-records)
##### [Fetch single record](https://pocketbase.io/docs/js-records/\#fetch-single-record)
All single record retrieval methods throw an error if no record is found.
`// retrieve a single "articles" record by its id
let record = $app.findRecordById("articles", "RECORD_ID")
// retrieve a single "articles" record by a single key-value pair
let record = $app.findFirstRecordByData("articles", "slug", "test")
// retrieve a single "articles" record by a string filter expression
// (NB! use "{:placeholder}" to safely bind untrusted user input parameters)
let record = $app.findFirstRecordByFilter(
"articles",
"status = 'public' && category = {:category}",
{ "category": "news" },
)`
##### [Fetch multiple records](https://pocketbase.io/docs/js-records/\#fetch-multiple-records)
All multiple records retrieval methods return an empty array if no records are found.
`// retrieve multiple "articles" records by their ids
let records = $app.findRecordsByIds("articles", ["RECORD_ID1", "RECORD_ID2"])
// retrieve the total number of "articles" records in a collection with optional dbx expressions
let totalPending = $app.countRecords("articles", $dbx.hashExp({"status": "pending"}))
// retrieve multiple "articles" records with optional dbx expressions
let records = $app.findAllRecords("articles",
$dbx.exp("LOWER(username) = {:username}", {"username": "John.Doe"}),
$dbx.hashExp({"status": "pending"}),
)
// retrieve multiple paginated "articles" records by a string filter expression
// (NB! use "{:placeholder}" to safely bind untrusted user input parameters)
let records = $app.findRecordsByFilter(
"articles", // collection
"status = 'public' && category = {:category}", // filter
"-published", // sort
10, // limit
0, // offset
{ "category": "news" }, // optional filter params
)`
##### [Fetch auth records](https://pocketbase.io/docs/js-records/\#fetch-auth-records)
`// retrieve a single auth record by its email
let user = $app.findAuthRecordByEmail("users", "test@example.com")
// retrieve a single auth record by JWT
// (you could also specify an optional list of accepted token types)
let user = $app.findAuthRecordByToken("YOUR_TOKEN", "auth")`
##### [Custom record query](https://pocketbase.io/docs/js-records/\#custom-record-query)
In addition to the above query helpers, you can also create custom Record queries using
[`$app.recordQuery(collection)`](https://pocketbase.io/jsvm/functions/_app.recordQuery.html)
method. It returns a SELECT DB builder that can be used with the same methods described in the
[Database guide](https://pocketbase.io/docs/js-database).
`function findTopArticle() {
let record = new Record();
$app.recordQuery("articles")
.andWhere($dbx.hashExp({ "status": "active" }))
.orderBy("rank ASC")
.limit(1)
.one(record)
return record
}
let article = findTopArticle()`
For retrieving **multiple** Record models with the `all()` executor, you can use
`arrayOf(new Record)`
to create an array placeholder in which to populate the resolved DB result.
`// the below is identical to
// $app.findRecordsByFilter("articles", "status = 'active'", '-published', 10)
// but allows more advanced use cases and filtering (aggregations, subqueries, etc.)
function findLatestArticles() {
let records = arrayOf(new Record);
$app.recordQuery("articles")
.andWhere($dbx.hashExp({ "status": "active" }))
.orderBy("published DESC")
.limit(10)
.all(records)
return records
}
let articles = findLatestArticles()`
### [Create new record](https://pocketbase.io/docs/js-records/\#create-new-record)
##### [Create new record programmatically](https://pocketbase.io/docs/js-records/\#create-new-record-programmatically)
`let collection = $app.findCollectionByNameOrId("articles")
let record = new Record(collection)
record.set("title", "Lorem ipsum")
record.set("active", true)
// field type specific modifiers can also be used
record.set("slug:autogenerate", "post-")
// new files must be one or a slice of filesystem.File values
//
// note1: see all factories in /jsvm/modules/_filesystem.html
// note2: for reading files from a request event you can also use e.findUploadedFiles("fileKey")
let f1 = $filesystem.fileFromPath("/local/path/to/file1.txt")
let f2 = $filesystem.fileFromBytes("test content", "file2.txt")
let f3 = $filesystem.fileFromURL("https://example.com/file3.pdf")
record.set("documents", [f1, f2, f3])
// validate and persist
// (use saveNoValidate to skip fields validation)
$app.save(record);`
##### [Intercept create request](https://pocketbase.io/docs/js-records/\#intercept-create-request)
`onRecordCreateRequest((e) => {
// ignore for superusers
if (e.hasSuperuserAuth()) {
return e.next()
}
// overwrite the submitted "status" field value
e.record.set("status", "pending")
// or you can also prevent the create event by returning an error
let status = e.record.get("status")
if (
status != "pending" &&
// guest or not an editor
(!e.auth || e.auth.get("role") != "editor")
) {
throw new BadRequestError("Only editors can set a status different from pending")
}
e.next()
}, "articles")`
### [Update existing record](https://pocketbase.io/docs/js-records/\#update-existing-record)
##### [Update existing record programmatically](https://pocketbase.io/docs/js-records/\#update-existing-record-programmatically)
`let record = $app.findRecordById("articles", "RECORD_ID")
record.set("title", "Lorem ipsum")
// delete existing record files by specifying their file names
record.set("documents-", ["file1_abc123.txt", "file3_abc123.txt"])
// append one or more new files to the already uploaded list
//
// note1: see all factories in /jsvm/modules/_filesystem.html
// note2: for reading files from a request event you can also use e.findUploadedFiles("fileKey")
let f1 = $filesystem.fileFromPath("/local/path/to/file1.txt")
let f2 = $filesystem.fileFromBytes("test content", "file2.txt")
let f3 = $filesystem.fileFromURL("https://example.com/file3.pdf")
record.set("documents+", [f1, f2, f3])
// validate and persist
// (use saveNoValidate to skip fields validation)
$app.save(record);`
##### [Intercept update request](https://pocketbase.io/docs/js-records/\#intercept-update-request)
`onRecordUpdateRequest((e) => {
// ignore for superusers
if (e.hasSuperuserAuth()) {
return e.next()
}
// overwrite the submitted "status" field value
e.record.set("status", "pending")
// or you can also prevent the create event by returning an error
let status = e.record.get("status")
if (
status != "pending" &&
// guest or not an editor
(!e.auth || e.auth.get("role") != "editor")
) {
throw new BadRequestError("Only editors can set a status different from pending")
}
e.next()
}, "articles")`
### [Delete record](https://pocketbase.io/docs/js-records/\#delete-record)
`let record = $app.findRecordById("articles", "RECORD_ID")
$app.delete(record)`
### [Transaction](https://pocketbase.io/docs/js-records/\#transaction)
To execute multiple queries in a transaction you can use
[`$app.runInTransaction(fn)`](https://pocketbase.io/jsvm/functions/_app.runInTransaction.html)
.
The DB operations are persisted only if the transaction completes without throwing an error.
It is safe to nest `runInTransaction` calls as long as you use the callback's
`txApp` argument.
Inside the transaction function always use its `txApp` argument and not the original
`$app` instance because we allow only a single writer/transaction at a time and it could
result in a deadlock.
To avoid performance issues, try to minimize slow/long running tasks such as sending emails,
connecting to external services, etc. as part of the transaction.
`let titles = ["title1", "title2", "title3"]
let collection = $app.findCollectionByNameOrId("articles")
$app.runInTransaction((txApp) => {
// create new record for each title
for (let title of titles) {
let record = new Record(collection)
record.set("title", title)
txApp.save(record)
}
})`
### [Programmatically expanding relations](https://pocketbase.io/docs/js-records/\#programmatically-expanding-relations)
To expand record relations programmatically you can use
[`$app.expandRecord(record, expands, customFetchFunc)`](https://pocketbase.io/jsvm/functions/_app.expandRecord.html)
for single or
[`$app.expandRecords(records, expands, customFetchFunc)`](https://pocketbase.io/jsvm/functions/_app.expandRecords.html)
for multiple records.
Once loaded, you can access the expanded relations via
[`record.expandedOne(relName)`](https://pocketbase.io/jsvm/interfaces/core.Record.html#expandedOne)
or
[`record.expandedAll(relName)` methods.](https://pocketbase.io/jsvm/interfaces/core.Record.html#expandedAll)
For example:
`let record = $app.findFirstRecordByData("articles", "slug", "lorem-ipsum")
// expand the "author" and "categories" relations
$app.expandRecord(record, ["author", "categories"], null)
// print the expanded records
console.log(record.expandedOne("author"))
console.log(record.expandedAll("categories"))`
### [Check if record can be accessed](https://pocketbase.io/docs/js-records/\#check-if-record-can-be-accessed)
To check whether a custom client request or user can access a single record, you can use the
[`$app.canAccessRecord(record, requestInfo, rule)`](https://pocketbase.io/jsvm/functions/_app.canAccessRecord.html)
method.
Below is an example of creating a custom route to retrieve a single article and checking the request
satisfy the View API rule of the record collection:
`routerAdd("GET", "/articles/{slug}", (e) => {
let slug = e.request.pathValue("slug")
let record = e.app.findFirstRecordByData("articles", "slug", slug)
let canAccess = e.app.canAccessRecord(record, e.requestInfo(), record.collection().viewRule)
if (!canAccess) {
throw new ForbiddenError()
}
return e.json(200, record)
})`
### [Generating and validating tokens](https://pocketbase.io/docs/js-records/\#generating-and-validating-tokens)
PocketBase Web APIs are fully stateless (aka. there are no sessions in the traditional sense) and an auth
record is considered authenticated if the submitted request contains a valid
`Authorization: TOKEN`
header
_(see also [Builtin auth middlewares](https://pocketbase.io/docs/js-routing/#builtin-middlewares) and_
_[Retrieving the current auth state from a route](https://pocketbase.io/docs/js-routing/#retrieving-the-current-auth-state)_
_)_
.
If you want to issue and verify manually a record JWT (auth, verification, password reset, etc.), you
could do that using the record token type specific methods:
`let token = record.newAuthToken()
let token = record.newVerificationToken()
let token = record.newPasswordResetToken()
let token = record.newEmailChangeToken(newEmail)
let token = record.newFileToken() // for protected files
let token = record.newStaticAuthToken(optCustomDuration) // non-refreshable auth token`
Each token type has its own secret and the token duration is managed via its type related collection auth
option ( _the only exception is `newStaticAuthToken`_).
To validate a record token you can use the
[`$app.findAuthRecordByToken`](https://pocketbase.io/jsvm/functions/_app.findAuthRecordByToken.html)
method. The token related auth record is returned only if the token is not expired and its signature is valid.
Here is an example how to validate an auth token:
`let record = $app.findAuthRecordByToken("YOUR_TOKEN", "auth")`
* * *
[Prev: Database](https://pocketbase.io/docs/js-database) [Next: Collection operations](https://pocketbase.io/docs/js-collections)
## PocketBase Go Overview
Overview
- [Getting started](https://pocketbase.io/docs/go-overview/#getting-started)
- [Custom SQLite driver](https://pocketbase.io/docs/go-overview/#custom-sqlite-driver)
### [Getting started](https://pocketbase.io/docs/go-overview/\#getting-started)
PocketBase can be used as regular Go package that exposes various helpers and hooks to help you implement
you own custom portable application.
A new PocketBase instance is created via
[`pocketbase.New()`](https://pkg.go.dev/github.com/pocketbase/pocketbase#New)
or
[`pocketbase.NewWithConfig(config)`](https://pkg.go.dev/github.com/pocketbase/pocketbase#NewWithConfig)
.
Once created you can register your custom business logic via the available
[event hooks](https://pocketbase.io/docs/go-event-hooks/)
and call
[`app.Start()`](https://pkg.go.dev/github.com/pocketbase/pocketbase#PocketBase.Start)
to start the application.
Below is a minimal example:
0. [Install Go 1.23+](https://go.dev/doc/install)
1. Create a new project directory with `main.go` file inside it.
As a reference, you can also explore the prebuilt executable
[`example/base/main.go`](https://github.com/pocketbase/pocketbase/blob/master/examples/base/main.go)
file.
`package main
import (
"log"
"os"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/apis"
"github.com/pocketbase/pocketbase/core"
)
func main() {
app := pocketbase.New()
app.OnServe().BindFunc(func(se *core.ServeEvent) error {
// serves static files from the provided public dir (if exists)
se.Router.GET("/{path...}", apis.Static(os.DirFS("./pb_public"), false))
return se.Next()
})
if err := app.Start(); err != nil {
log.Fatal(err)
}
}`
2. To init the dependencies, run `go mod init myapp && go mod tidy`.
3. To start the application, run `go run . serve`.
4. To build a statically linked executable, run `go build`
and then you can start the created executable with
`./myapp serve`.
### [Custom SQLite driver](https://pocketbase.io/docs/go-overview/\#custom-sqlite-driver)
**The general recommendation is to use the builtin SQLite setup** but if you need more
advanced configuration or extensions like ICU, FTS5, etc. you'll have to specify a custom driver/build.
Note that PocketBase by default doesn't require CGO because it uses the pure Go SQLite port
[modernc.org/sqlite](https://pkg.go.dev/modernc.org/sqlite), but this may not be the case when using a custom SQLite driver!
PocketBase v0.23+ added supported for defining a `DBConnect` function as app configuration to
load custom SQLite builds and drivers compatible with the standard Go `database/sql`.
**The `DBConnect` function is called twice** \- once for
`pb_data/data.db`
(the main database file) and second time for `pb_data/auxiliary.db` (used for logs and other ephemeral
system meta information).
If you want to load your custom driver conditionally and fallback to the default handler, then you can
call
[`core.DefaultDBConnect`](https://pkg.go.dev/github.com/pocketbase/pocketbase/core#DefaultDBConnect)
.
_As a side-note, if you are not planning to use `core.DefaultDBConnect`_
_fallback as part of your custom driver registration you can exclude the default pure Go driver with_
_`go build -tags no_default_driver` to reduce the binary size a little (~4MB)._
Below are some minimal examples with commonly used external SQLite drivers:
**[github.com/mattn/go-sqlite3](https://pocketbase.io/docs/go-overview/#github-commattngo-sqlite3)**
_For all available options please refer to the_
_[`github.com/mattn/go-sqlite3`](https://github.com/mattn/go-sqlite3)_
_README._
``package main
import (
"database/sql"
"log"
"github.com/mattn/go-sqlite3"
"github.com/pocketbase/dbx"
"github.com/pocketbase/pocketbase"
)
// register a new driver with default PRAGMAs and the same query
// builder implementation as the already existing sqlite3 builder
func init() {
// initialize default PRAGMAs for each new connection
sql.Register("pb_sqlite3",
&sqlite3.SQLiteDriver{
ConnectHook: func(conn *sqlite3.SQLiteConn) error {
_, err := conn.Exec(`
PRAGMA busy_timeout = 10000;
PRAGMA journal_mode = WAL;
PRAGMA journal_size_limit = 200000000;
PRAGMA synchronous = NORMAL;
PRAGMA foreign_keys = ON;
PRAGMA temp_store = MEMORY;
PRAGMA cache_size = -16000;
`, nil)
return err
},
},
)
dbx.BuilderFuncMap["pb_sqlite3"] = dbx.BuilderFuncMap["sqlite3"]
}
func main() {
app := pocketbase.NewWithConfig(pocketbase.Config{
DBConnect: func(dbPath string) (*dbx.DB, error) {
return dbx.Open("pb_sqlite3", dbPath)
},
})
// any custom hooks or plugins...
if err := app.Start(); err != nil {
log.Fatal(err)
}
}``
**[github.com/ncruces/go-sqlite3](https://pocketbase.io/docs/go-overview/#github-comncrucesgo-sqlite3)**
_For all available options please refer to the_
_[`github.com/ncruces/go-sqlite3`](https://github.com/ncruces/go-sqlite3)_
_README._
`package main
import (
"log"
"github.com/pocketbase/dbx"
"github.com/pocketbase/pocketbase"
_ "github.com/ncruces/go-sqlite3/driver"
_ "github.com/ncruces/go-sqlite3/embed"
)
func main() {
app := pocketbase.NewWithConfig(pocketbase.Config{
DBConnect: func(dbPath string) (*dbx.DB, error) {
const pragmas = "?_pragma=busy_timeout(10000)&_pragma=journal_mode(WAL)&_pragma=journal_size_limit(200000000)&_pragma=synchronous(NORMAL)&_pragma=foreign_keys(ON)&_pragma=temp_store(MEMORY)&_pragma=cache_size(-16000)"
return dbx.Open("sqlite3", "file:"+dbPath+pragmas)
},
})
// custom hooks and plugins...
if err := app.Start(); err != nil {
log.Fatal(err)
}
}`
**[github.com/tursodatabase/libsql-client-go/libsql](https://pocketbase.io/docs/go-overview/#github-comtursodatabaselibsql-client-golibsql)**
_For all available options please refer to the_
_[Turso Go docs](https://docs.turso.tech/sdk/go/quickstart#remote-only)_
_._
`package main
import (
"log"
"github.com/pocketbase/dbx"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/core"
_ "github.com/tursodatabase/libsql-client-go/libsql"
)
// register the libsql driver to use the same query builder
// implementation as the already existing sqlite3 builder
func init() {
dbx.BuilderFuncMap["libsql"] = dbx.BuilderFuncMap["sqlite3"]
}
func main() {
app := pocketbase.NewWithConfig(pocketbase.Config{
DBConnect: func(dbPath string) (*dbx.DB, error) {
if strings.Contains(dbPath, "data.db") {
return dbx.Open("libsql", "libsql://[data.db DATABASE].turso.io?authToken=[TOKEN]")
}
// optionally for the logs (aka. pb_data/auxiliary.db) use the default local filesystem driver
return core.DefaultDBConnect(dbPath)
},
})
// any custom hooks or plugins...
if err := app.Start(); err != nil {
log.Fatal(err)
}
}`
* * *
[Next: Event hooks](https://pocketbase.io/docs/go-event-hooks)
## PocketBase Routing Guide
Routing
You can register custom routes and middlewares by using the top-level
[`routerAdd()`](https://pocketbase.io/jsvm/functions/routerAdd.html)
and
[`routerUse()`](https://pocketbase.io/jsvm/functions/routerUse.html)
functions.
- [Routes](https://pocketbase.io/docs/js-routing/#routes)
- [Registering new routes](https://pocketbase.io/docs/js-routing/#registering-new-routes)
- [Path parameters and matching rules](https://pocketbase.io/docs/js-routing/#path-parameters-and-matching-rules)
- [Reading path parameters](https://pocketbase.io/docs/js-routing/#reading-path-parameters)
- [Retrieving the current auth state](https://pocketbase.io/docs/js-routing/#retrieving-the-current-auth-state)
- [Reading query parameters](https://pocketbase.io/docs/js-routing/#reading-query-parameters)
- [Reading request headers](https://pocketbase.io/docs/js-routing/#reading-request-headers)
- [Writing response headers](https://pocketbase.io/docs/js-routing/#writing-response-headers)
- [Retrieving uploaded files](https://pocketbase.io/docs/js-routing/#retrieving-uploaded-files)
- [Reading request body](https://pocketbase.io/docs/js-routing/#reading-request-body)
- [Writing response body](https://pocketbase.io/docs/js-routing/#writing-response-body)
- [Reading the client IP](https://pocketbase.io/docs/js-routing/#reading-the-client-ip)
- [Request store](https://pocketbase.io/docs/js-routing/#request-store)
- [Middlewares](https://pocketbase.io/docs/js-routing/#middlewares)
- [Registering middlewares](https://pocketbase.io/docs/js-routing/#registering-middlewares)
- [Builtin middlewares](https://pocketbase.io/docs/js-routing/#builtin-middlewares)
- [Default globally registered middlewares](https://pocketbase.io/docs/js-routing/#default-globally-registered-middlewares)
- [Error response](https://pocketbase.io/docs/js-routing/#error-response)
- [Helpers](https://pocketbase.io/docs/js-routing/#helpers)
- [Serving static directory](https://pocketbase.io/docs/js-routing/#serving-static-directory)
- [Auth response](https://pocketbase.io/docs/js-routing/#auth-response)
- [Enrich record(s)](https://pocketbase.io/docs/js-routing/#enrich-records)
- [Sending request to custom routes using the SDKs](https://pocketbase.io/docs/js-routing/#sending-request-to-custom-routes-using-the-sdks)
### [Routes](https://pocketbase.io/docs/js-routing/\#routes)
##### [Registering new routes](https://pocketbase.io/docs/js-routing/\#registering-new-routes)
Every route have a path, handler function and eventually middlewares attached to it. For example:
`// register "GET /hello/{name}" route (allowed for everyone)
routerAdd("GET", "/hello/{name}", (e) => {
let name = e.request.pathValue("name")
return e.json(200, { "message": "Hello " + name })
})
// register "POST /api/myapp/settings" route (allowed only for authenticated users)
routerAdd("POST", "/api/myapp/settings", (e) => {
// do something ...
return e.json(200, {"success": true})
}, $apis.requireAuth())`
##### [Path parameters and matching rules](https://pocketbase.io/docs/js-routing/\#path-parameters-and-matching-rules)
Because PocketBase routing is based on top of the Go standard router mux, we follow the same pattern
matching rules. Below you could find a short overview but for more details please refer to
[`net/http.ServeMux`](https://pkg.go.dev/net/http#ServeMux).
In general, a route pattern looks like `[METHOD ][HOST]/[PATH]`.
Route paths can include parameters in the format `{paramName}`.
You can also use `{paramName...}` format to specify a parameter that target more than one path
segment.
A pattern ending with a trailing slash `/` acts as anonymous wildcard and matches any requests
that begins with the defined route. If you want to have a trailing slash but to indicate the end of the
URL then you need to end the path with the special
`{$}` parameter.
If your route path starts with `/api/`
consider combining it with your unique app name like `/api/myapp/...` to avoid collisions
with system routes.
Here are some examples:
`// match "GET example.com/index.html"
routerAdd("GET", "example.com/index.html", ...)
// match "GET /index.html" (for any host)
routerAdd("GET", "/index.html", ...)
// match "GET /static/", "GET /static/a/b/c", etc.
routerAdd("GET", "/static/", ...)
// match "GET /static/", "GET /static/a/b/c", etc.
// (similar to the above but with a named wildcard parameter)
routerAdd("GET", "/static/{path...}", ...)
// match only "GET /static/" (if no "/static" is registered, it is 301 redirected)
routerAdd("GET", "/static/{$}", ...)
// match "GET /customers/john", "GET /customer/jane", etc.
routerAdd("GET", "/customers/{name}", ...)`
* * *
In the following examples `e` is usually
[`core.RequestEvent`](https://pocketbase.io/jsvm/interfaces/core.RequestEvent.html) value.
* * *
##### [Reading path parameters](https://pocketbase.io/docs/js-routing/\#reading-path-parameters)
`let id = e.request.pathValue("id")`
##### [Retrieving the current auth state](https://pocketbase.io/docs/js-routing/\#retrieving-the-current-auth-state)
The request auth state can be accessed (or set) via the `RequestEvent.auth` field.
`let authRecord = e.auth
let isGuest = !e.auth
// the same as "e.auth?.isSuperuser()"
let isSuperuser = e.hasSuperuserAuth()`
Alternatively you could also access the request data from the summarized request info instance
_(usually used in hooks like the `onRecordEnrich` where there is no direct access to the request)_.
`let info = e.requestInfo()
let authRecord = info.auth
let isGuest = !info.auth
// the same as "info.auth?.isSuperuser()"
let isSuperuser = info.hasSuperuserAuth()`
##### [Reading query parameters](https://pocketbase.io/docs/js-routing/\#reading-query-parameters)
`let search = e.request.url.query().get("search")
// or via the parsed request info
let search = e.requestInfo().query["search"]`
##### [Reading request headers](https://pocketbase.io/docs/js-routing/\#reading-request-headers)
`let token = e.request.header.get("Some-Header")
// or via the parsed request info
// (the header value is always normalized per the @request.headers.* API rules format)
let token = e.requestInfo().headers["some_header"]`
##### [Writing response headers](https://pocketbase.io/docs/js-routing/\#writing-response-headers)
`e.response.header().set("Some-Header", "123")`
##### [Retrieving uploaded files](https://pocketbase.io/docs/js-routing/\#retrieving-uploaded-files)
`// retrieve the uploaded files and parse the found multipart data into a ready-to-use []*filesystem.File
let files = e.findUploadedFiles("document")
// or retrieve the raw single multipart/form-data file and header
let [mf, mh] = e.request.formFile("document")`
##### [Reading request body](https://pocketbase.io/docs/js-routing/\#reading-request-body)
Body parameters can be read either via
[`e.bindBody`](https://pocketbase.io/jsvm/interfaces/core.RequestEvent.html#bindBody)
OR through the parsed request info.
`// read the body via the parsed request object
let body = e.requestInfo().body
console.log(body.title)
// read/scan the request body fields into a typed object
const data = new DynamicModel({
// describe the fields to read (used also as initial values)
someTextField: "",
someIntValue: 0,
someFloatValue: -0,
someBoolField: false,
someArrayField: [],
someObjectField: {}, // object props are accessible via .get(key)
})
e.bindBody(data)
console.log(data.sometextField)`
##### [Writing response body](https://pocketbase.io/docs/js-routing/\#writing-response-body)
`// send response with JSON body
// (it also provides a generic response fields picker/filter if the "fields" query parameter is set)
e.json(200, {"name": "John"})
// send response with string body
e.string(200, "Lorem ipsum...")
// send response with HTML body
// (check also the "Rendering templates" section)
e.html(200, "
Hello!
")
// redirect
e.redirect(307, "https://example.com")
// send response with no body
e.noContent(204)
// serve a single file
e.fileFS($os.dirFS("..."), "example.txt")
// stream the specified reader
e.stream(200, "application/octet-stream", reader)
// send response with blob (bytes array) body
e.blob(200, "application/octet-stream", [ ... ])`
##### [Reading the client IP](https://pocketbase.io/docs/js-routing/\#reading-the-client-ip)
`// The IP of the last client connecting to your server.
// The returned IP is safe and can be always trusted.
// When behind a reverse proxy (e.g. nginx) this method returns the IP of the proxy.
// (/jsvm/interfaces/core.RequestEvent.html#remoteIP)
let ip = e.remoteIP()
// The "real" IP of the client based on the configured Settings.trustedProxy header(s).
// If such headers are not set, it fallbacks to e.remoteIP().
// (/jsvm/interfaces/core.RequestEvent.html#realIP)
let ip = e.realIP()`
##### [Request store](https://pocketbase.io/docs/js-routing/\#request-store)
The `core.RequestEvent` comes with a local store that you can use to share custom data between
[middlewares](https://pocketbase.io/docs/js-routing/#middlewares) and the route action.
`// store for the duration of the request
e.set("someKey", 123)
// retrieve later
let val = e.get("someKey") // 123`
### [Middlewares](https://pocketbase.io/docs/js-routing/\#middlewares)
Middlewares allow inspecting, intercepting and filtering route requests.
Middlewares can be registered both to a single route (by passing them after the handler) and globally usually
by using `routerUse(middleware)`.
##### [Registering middlewares](https://pocketbase.io/docs/js-routing/\#registering-middlewares)
Here is a minimal example of a what global middleware looks like:
`// register a global middleware
routerUse((e) => {
if (e.request.header.get("Something") == "") {
throw new BadRequestError("Something header value is missing!")
}
return e.next()
})`
Middleware can be either registered as simple functions ( `function(e){}` ) or if you want
to specify a custom priority and id - as a
[`Middleware`](https://pocketbase.io/jsvm/classes/Middleware.html)
class instance.
Below is a slightly more advanced example showing all options and the execution sequence:
`// attach global middleware
routerUse((e) => {
console.log(1)
return e.next()
})
// attach global middleware with a custom priority
routerUse(new Middleware((e) => {
console.log(2)
return e.next()
}, -1))
// attach middleware to a single route
//
// "GET /hello" should print the sequence: 2,1,3,4
routerAdd("GET", "/hello", (e) => {
console.log(4)
return e.string(200, "Hello!")
}, (e) => {
console.log(3)
return e.next()
})`
##### [Builtin middlewares](https://pocketbase.io/docs/js-routing/\#builtin-middlewares)
The global
[`$apis.*`](https://pocketbase.io/jsvm/modules/_apis.html)
object exposes several middlewares that you can use as part of your application.
`// Require the request client to be unauthenticated (aka. guest).
$apis.requireGuestOnly()
// Require the request client to be authenticated
// (optionally specify a list of allowed auth collection names, default to any).
$apis.requireAuth(optCollectionNames...)
// Require the request client to be authenticated as superuser
// (this is an alias for $apis.requireAuth("_superusers")).
$apis.requireSuperuserAuth()
// Require the request client to be authenticated as superuser OR
// regular auth record with id matching the specified route parameter (default to "id").
$apis.requireSuperuserOrOwnerAuth(ownerIdParam)
// Changes the global 32MB default request body size limit (set it to 0 for no limit).
// Note that system record routes have dynamic body size limit based on their collection field types.
$apis.bodyLimit(limitBytes)
// Compresses the HTTP response using Gzip compression scheme.
$apis.gzip()
// Instructs the activity logger to log only requests that have failed/returned an error.
$apis.skipSuccessActivityLog()`
##### [Default globally registered middlewares](https://pocketbase.io/docs/js-routing/\#default-globally-registered-middlewares)
The below list is mostly useful for users that may want to plug their own custom middlewares before/after
the priority of the default global ones, for example: registering a custom auth loader before the rate
limiter with `-1001` so that the rate limit can be applied properly based on the loaded auth state.
All PocketBase applications have the below internal middlewares registered out of the box ( _sorted by their priority_):
- **WWW redirect**(id: pbWWWRedirect, priority: -99999)
_Performs www -> non-www redirect(s) if the request host matches with one of the values in_
_certificate host policy._
- **CORS**(id: pbCors, priority: -1041)
_By default all origins are allowed (PocketBase is stateless and doesn't rely on cookies) but this_
_can be configured with the `--origins` flag._
- **Activity logger**(id: pbActivityLogger, priority: -1040)
_Saves request information into the logs auxiliary database._
- **Auto panic recover**(id: pbPanicRecover, priority: -1030)
_Default panic-recover handler._
- **Auth token loader**(id: pbLoadAuthToken, priority: -1020)
_Loads the auth token from the `Authorization` header and populates the related auth_
_record into the request event (aka. `e.auth`)._
- **Security response headers**(id: pbSecurityHeaders, priority: -1010)
_Adds default common security headers ( `X-XSS-Protection`,_
_`X-Content-Type-Options`,_
_`X-Frame-Options`) to the response (can be overwritten by other middlewares or from_
_inside the route action)._
- **Rate limit**(id: pbRateLimit, priority: -1000)
_Rate limits client requests based on the configured app settings (it does nothing if the rate_
_limit option is not enabled)._
- **Body limit**(id: pbBodyLimit, priority: -990)
_Applies a default max ~32MB request body limit for all custom routes ( system record routes have_
_dynamic body size limit based on their collection field types). Can be overwritten on group/route_
_level by simply rebinding the `$apis.bodyLimit(limitBytes)` middleware._
### [Error response](https://pocketbase.io/docs/js-routing/\#error-response)
PocketBase has a global error handler and every returned or thrown `Error` from a route or
middleware will be safely converted by default to a generic API error to avoid accidentally leaking
sensitive information (the original error will be visible only in the _Dashboard > Logs_ or when in
`--dev` mode).
To make it easier returning formatted json error responses, PocketBase provides
`ApiError` constructor that can be instantiated directly or using the builtin factories.
`ApiError.data` will be returned in the response only if it is a map of
`ValidationError` items.
`// construct ApiError with custom status code and validation data error
throw new ApiError(500, "something went wrong", {
"title": new ValidationError("invalid_title", "Invalid or missing title"),
})
// if message is empty string, a default one will be set
throw new BadRequestError(optMessage, optData) // 400 ApiError
throw new UnauthorizedError(optMessage, optData) // 401 ApiError
throw new ForbiddenError(optMessage, optData) // 403 ApiError
throw new NotFoundError(optMessage, optData) // 404 ApiError
throw new TooManyrequestsError(optMessage, optData) // 429 ApiError
throw new InternalServerError(optMessage, optData) // 500 ApiError`
### [Helpers](https://pocketbase.io/docs/js-routing/\#helpers)
##### [Serving static directory](https://pocketbase.io/docs/js-routing/\#serving-static-directory)
[`$apis.static()`](https://pocketbase.io/jsvm/functions/_apis.static.html)
serves static directory content from `fs.FS` instance.
Expects the route to have a `{path...}` wildcard parameter.
`// serves static files from the provided dir (if exists)
routerAdd("GET", "/{path...}", $apis.static($os.dirFS("/path/to/public"), false))`
##### [Auth response](https://pocketbase.io/docs/js-routing/\#auth-response)
[`$apis.recordAuthResponse()`](https://pocketbase.io/jsvm/functions/_apis.recordAuthResponse.html)
writes standardized JSON record auth response (aka. token + record data) into the specified request body. Could
be used as a return result from a custom auth route.
`routerAdd("POST", "/phone-login", (e) => {
const data = new DynamicModel({
phone: "",
password: "",
})
e.bindBody(data)
let record = e.app.findFirstRecordByData("users", "phone", data.phone)
if !record.validatePassword(data.password) {
// return generic 400 error as a basic enumeration protection
throw new BadRequestError("Invalid credentials")
}
return $apis.recordAuthResponse(e, record, "phone")
})`
##### [Enrich record(s)](https://pocketbase.io/docs/js-routing/\#enrich-records)
[`$apis.enrichRecord()`](https://pocketbase.io/jsvm/functions/_apis.enrichRecord.html)
and
[`$apis.enrichRecords()`](https://pocketbase.io/jsvm/functions/_apis.enrichRecords.html)
helpers parses the request context and enrich the provided record(s) by:
- expands relations (if `defaultExpands` and/or `?expand` query parameter is set)
- ensures that the emails of the auth record and its expanded auth relations are visible only for the
current logged superuser, record owner or record with manage access
These helpers are also responsible for triggering the `onRecordEnrich` hook events.
`routerAdd("GET", "/custom-article", (e) => {
let records = e.app.findRecordsByFilter("article", "status = 'active'", "-created", 40, 0)
// enrich the records with the "categories" relation as default expand
$apis.enrichRecords(e, records, "categories")
return e.json(200, records)
})`
### [Sending request to custom routes using the SDKs](https://pocketbase.io/docs/js-routing/\#sending-request-to-custom-routes-using-the-sdks)
The official PocketBase SDKs expose the internal `send()` method that could be used to send requests
to your custom route(s).
JavaScript
Dart
`import PocketBase from 'pocketbase';
const pb = new PocketBase('http://127.0.0.1:8090');
await pb.send("/hello", {
// for other options check
// https://developer.mozilla.org/en-US/docs/Web/API/fetch#options
query: { "abc": 123 },
});`
`import 'package:pocketbase/pocketbase.dart';
final pb = PocketBase('http://127.0.0.1:8090');
await pb.send("/hello", query: { "abc": 123 })`
* * *
[Prev: Event hooks](https://pocketbase.io/docs/js-event-hooks) [Next: Database](https://pocketbase.io/docs/js-database)
## PocketBase Logging Guide
Logging
`$app.logger()` could be used to writes any logs into the database so that they can be later
explored from the PocketBase _Dashboard > Logs_ section.
For better performance and to minimize blocking on hot paths, logs are written with debounce and
on batches:
- 3 seconds after the last debounced log write
- when the batch threshold is reached (currently 200)
- right before app termination to attempt saving everything from the existing logs queue
- [Logger methods](https://pocketbase.io/docs/js-logging/#logger-methods)
- [debug(message, attrs...)](https://pocketbase.io/docs/js-logging/#debugmessage-attrs-)
- [info(message, attrs...)](https://pocketbase.io/docs/js-logging/#infomessage-attrs-)
- [warn(message, attrs...)](https://pocketbase.io/docs/js-logging/#warnmessage-attrs-)
- [error(message, attrs...)](https://pocketbase.io/docs/js-logging/#errormessage-attrs-)
- [with(attrs...)](https://pocketbase.io/docs/js-logging/#withattrs-)
- [withGroup(name)](https://pocketbase.io/docs/js-logging/#withgroupname)
- [Logs settings](https://pocketbase.io/docs/js-logging/#logs-settings)
- [Custom log queries](https://pocketbase.io/docs/js-logging/#custom-log-queries)
### [Logger methods](https://pocketbase.io/docs/js-logging/\#logger-methods)
All standard
[`slog.Logger`](https://pocketbase.io/jsvm/interfaces/slog.Logger.html)
methods are available but below is a list with some of the most notable ones. Note that attributes are represented
as key-value pair arguments.
##### [debug(message, attrs...)](https://pocketbase.io/docs/js-logging/\#debugmessage-attrs-)
`$app.logger().debug("Debug message!")
$app.logger().debug(
"Debug message with attributes!",
"name", "John Doe",
"id", 123,
)`
##### [info(message, attrs...)](https://pocketbase.io/docs/js-logging/\#infomessage-attrs-)
`$app.logger().info("Info message!")
$app.logger().info(
"Info message with attributes!",
"name", "John Doe",
"id", 123,
)`
##### [warn(message, attrs...)](https://pocketbase.io/docs/js-logging/\#warnmessage-attrs-)
`$app.logger().warn("Warning message!")
$app.logger().warn(
"Warning message with attributes!",
"name", "John Doe",
"id", 123,
)`
##### [error(message, attrs...)](https://pocketbase.io/docs/js-logging/\#errormessage-attrs-)
`$app.logger().error("Error message!")
$app.logger().error(
"Error message with attributes!",
"id", 123,
"error", err,
)`
##### [with(attrs...)](https://pocketbase.io/docs/js-logging/\#withattrs-)
`with(atrs...)` creates a new local logger that will "inject" the specified attributes with each
following log.
`const l = $app.logger().with("total", 123)
// results in log with data {"total": 123}
l.info("message A")
// results in log with data {"total": 123, "name": "john"}
l.info("message B", "name", "john")`
##### [withGroup(name)](https://pocketbase.io/docs/js-logging/\#withgroupname)
`withGroup(name)` creates a new local logger that wraps all logs attributes under the specified
group name.
`const l = $app.logger().withGroup("sub")
// results in log with data {"sub": { "total": 123 }}
l.info("message A", "total", 123)`
### [Logs settings](https://pocketbase.io/docs/js-logging/\#logs-settings)
You can control various log settings like logs retention period, minimal log level, request IP logging,
etc. from the logs settings panel:

### [Custom log queries](https://pocketbase.io/docs/js-logging/\#custom-log-queries)
The logs are usually meant to be filtered from the UI but if you want to programmatically retrieve and
filter the stored logs you can make use of the
[`$app.logQuery()`](https://pocketbase.io/jsvm/functions/_app.logQuery.html) query builder method. For example:
`let logs = arrayOf(new DynamicModel({
id: "",
created: "",
message: "",
level: 0,
data: {},
}))
// see https://pocketbase.io/docs/js-database/#query-builder
$app.logQuery().
// target only debug and info logs
andWhere($dbx.in("level", -4, 0)).
// the data column is serialized json object and could be anything
andWhere($dbx.exp("json_extract(data, '$.type') = 'request'")).
orderBy("created DESC").
limit(100).
all(logs)`
* * *
[Prev: Filesystem](https://pocketbase.io/docs/js-filesystem) [Next: Types reference](https://pocketbase.io/jsvm/index.html)
## PocketBase Filesystem Guide
Filesystem
PocketBase comes with a thin abstraction between the local filesystem and S3.
To configure which one will be used you can adjust the storage settings from
_Dashboard > Settings > Files storage_ section.
The filesystem abstraction can be accessed programmatically via the
[`app.NewFilesystem()`](https://pkg.go.dev/github.com/pocketbase/pocketbase/core#BaseApp.NewFilesystem)
method.
Below are listed some of the most common operations but you can find more details in the
[`filesystem`](https://pkg.go.dev/github.com/pocketbase/pocketbase/tools/filesystem)
subpackage.
Always make sure to call `Close()` at the end for both the created filesystem instance and
the retrieved file readers to prevent leaking resources.
- [Reading files](https://pocketbase.io/docs/go-filesystem/#reading-files)
- [Saving files](https://pocketbase.io/docs/go-filesystem/#saving-files)
- [Deleting files](https://pocketbase.io/docs/go-filesystem/#deleting-files)
### [Reading files](https://pocketbase.io/docs/go-filesystem/\#reading-files)
To retrieve the file content of a single stored file you can use
[`GetFile(key)`](https://pkg.go.dev/github.com/pocketbase/pocketbase/tools/filesystem#System.GetFile)
.
Note that file keys often contain a **prefix** (aka. the "path" to the file). For record
files the full key is
`collectionId/recordId/filename`.
To retrieve multiple files matching a specific _prefix_ you can use
[`List(prefix)`](https://pkg.go.dev/github.com/pocketbase/pocketbase/tools/filesystem#System.List)
.
The below code shows a minimal example how to retrieve a single record file and copy its content into a
`bytes.Buffer`.
`record, err := app.FindAuthRecordByEmail("users", "test@example.com")
if err != nil {
return err
}
// construct the full file key by concatenating the record storage path with the specific filename
avatarKey := record.BaseFilesPath() + "/" + record.GetString("avatar")
// initialize the filesystem
fsys, err := app.NewFilesystem()
if err != nil {
return err
}
defer fsys.Close()
// retrieve a file reader for the avatar key
r, err := fsys.GetFile(avatarKey)
if err != nil {
return err
}
defer r.Close()
// do something with the reader...
content := new(bytes.Buffer)
_, err = io.Copy(content, r)
if err != nil {
return err
}`
### [Saving files](https://pocketbase.io/docs/go-filesystem/\#saving-files)
There are several methods to save _(aka. write/upload)_ files depending on the available file content
source:
- [`Upload([]byte, key)`](https://pkg.go.dev/github.com/pocketbase/pocketbase/tools/filesystem#System.Upload)
- [`UploadFile(*filesystem.File, key)`](https://pkg.go.dev/github.com/pocketbase/pocketbase/tools/filesystem#System.UploadFile)
- [`UploadMultipart(*multipart.FileHeader, key)`](https://pkg.go.dev/github.com/pocketbase/pocketbase/tools/filesystem#System.UploadFile)
Most users rarely will have to use the above methods directly because for collection records the file
persistence is handled transparently when saving the record model (it will also perform size and MIME type
validation based on the collection `file` field options). For example:
`record, err := app.FindRecordById("articles", "RECORD_ID")
if err != nil {
return err
}
// Other available File factories
// - filesystem.NewFileFromBytes(data, name)
// - filesystem.NewFileFromURL(ctx, url)
// - filesystem.NewFileFromMultipart(mh)
f, err := filesystem.NewFileFromPath("/local/path/to/file")
// set new file (can be single *filesytem.File or multiple []*filesystem.File)
// (if the record has an old file it is automatically deleted on successful Save)
record.Set("yourFileField", f)
err = app.Save(record)
if err != nil {
return err
}`
### [Deleting files](https://pocketbase.io/docs/go-filesystem/\#deleting-files)
Files can be deleted from the storage filesystem using
[`Delete(key)`](https://pkg.go.dev/github.com/pocketbase/pocketbase/tools/filesystem#System.Delete)
.
Similar to the previous section, most users rarely will have to use the `Delete` file method directly
because for collection records the file deletion is handled transparently when removing the existing filename
from the record model (this also ensure that the db entry referencing the file is also removed). For example:
`record, err := app.FindRecordById("articles", "RECORD_ID")
if err != nil {
return err
}
// if you want to "reset" a file field (aka. deleting the associated single or multiple files)
// you can set it to nil
record.Set("yourFileField", nil)
// OR if you just want to remove individual file(s) from a multiple file field you can use the "-" modifier
// (the value could be a single filename string or slice of filename strings)
record.Set("yourFileField-", "example_52iWbGinWd.txt")
err = app.Save(record)
if err != nil {
return err
}`
* * *
[Prev: Realtime messaging](https://pocketbase.io/docs/go-realtime) [Next: Logging](https://pocketbase.io/docs/go-logging)
## PocketBase Database Guide
Database
[`$app`](https://pocketbase.io/jsvm/modules/_app.html)
is the main interface to interact with your database.
`$app.db()`
returns a `dbx.Builder` that could run all kind of SQL statements, including raw queries.
For more details and examples how to interact with Record and Collection models programmatically
you could also check [Collection operations](https://pocketbase.io/docs/js-collections)
and
[Record operations](https://pocketbase.io/docs/js-records) sections.
- [Executing queries](https://pocketbase.io/docs/js-database/#executing-queries)
- [Binding parameters](https://pocketbase.io/docs/js-database/#binding-parameters)
- [Query builder](https://pocketbase.io/docs/js-database/#query-builder)
- [select(), andSelect(), distinct()](https://pocketbase.io/docs/js-database/#select-andselect-distinct)
- [from()](https://pocketbase.io/docs/js-database/#from)
- [join()](https://pocketbase.io/docs/js-database/#join)
- [where(), andWhere(), orWhere()](https://pocketbase.io/docs/js-database/#where-andwhere-orwhere)
- [orderBy(), andOrderBy()](https://pocketbase.io/docs/js-database/#orderby-andorderby)
- [groupBy(), andGroupBy()](https://pocketbase.io/docs/js-database/#groupby-andgroupby)
- [having(), andHaving(), orHaving()](https://pocketbase.io/docs/js-database/#having-andhaving-orhaving)
- [limit()](https://pocketbase.io/docs/js-database/#limit)
- [offset()](https://pocketbase.io/docs/js-database/#offset)
- [Transaction](https://pocketbase.io/docs/js-database/#transaction)
### [Executing queries](https://pocketbase.io/docs/js-database/\#executing-queries)
To execute DB queries you can start with the `newQuery("...")` statement and then call one of:
- ` execute() `
\- for any query statement that is not meant to retrieve data:
`$app.db()
.newQuery("DELETE FROM articles WHERE status = 'archived'")
.execute() // throw an error on db failure`
- ` one() `
\- to populate a single row into `DynamicModel` object:
`const result = new DynamicModel({
// describe the shape of the data (used also as initial values)
// the keys cannot start with underscore and must be a valid Go struct field name
"id": "",
"status": false,
"age": 0, // use -0 for a float value
"roles": [], // serialized json db arrays are decoded as plain arrays
})
$app.db()
.newQuery("SELECT id, status, age, roles FROM users WHERE id=1")
.one(result) // throw an error on db failure or missing row
console.log(result.id)`
- ` all() `
\- to populate multiple rows into an array of objects (note that the array must be created with
`arrayOf`):
`const result = arrayOf(new DynamicModel({
// describe the shape of the data (used also as initial values)
// the keys cannot start with underscore and must be a valid Go struct field name
"id": "",
"status": false,
"age": 0, // use -0 for a float value
"roles": [], // serialized json db arrays are decoded as plain arrays
}))
$app.db()
.newQuery("SELECT id, status, age, roles FROM users LIMIT 100")
.all(result) // throw an error on db failure
if (result.length > 0) {
console.log(result[0].id)
}`
### [Binding parameters](https://pocketbase.io/docs/js-database/\#binding-parameters)
To prevent SQL injection attacks, you should use named parameters for any expression value that comes from
user input. This could be done using the named `{:paramName}`
placeholders in your SQL statement and then define the parameter values for the query with
`bind(params)`. For example:
`const result = arrayOf(new DynamicModel({
"name": "",
"created": "",
}))
$app.db()
.newQuery("SELECT name, created FROM posts WHERE created >= {:from} and created <= {:to}")
.bind({
"from": "2023-06-25 00:00:00.000Z",
"to": "2023-06-28 23:59:59.999Z",
})
.all(result)
console.log(result.length)`
### [Query builder](https://pocketbase.io/docs/js-database/\#query-builder)
Instead of writing plain SQLs, you can also compose SQL statements programmatically using the db query
builder.
Every SQL keyword has a corresponding query building method. For example, `SELECT` corresponds
to `select()`, `FROM` corresponds to `from()`,
`WHERE` corresponds to `where()`, and so on.
`const result = arrayOf(new DynamicModel({
"id": "",
"email": "",
}))
$app.db()
.select("id", "email")
.from("users")
.andWhere($dbx.like("email", "example.com"))
.limit(100)
.orderBy("created ASC")
.all(result)`
##### [select(), andSelect(), distinct()](https://pocketbase.io/docs/js-database/\#select-andselect-distinct)
The `select(...cols)` method initializes a `SELECT` query builder. It accepts a list
of the column names to be selected.
To add additional columns to an existing select query, you can call `andSelect()`.
To select distinct rows, you can call `distinct(true)`.
`$app.db()
.select("id", "avatar as image")
.andSelect("(firstName || ' ' || lastName) as fullName")
.distinct(true)
...`
##### [from()](https://pocketbase.io/docs/js-database/\#from)
The `from(...tables)` method specifies which tables to select from (plain table names are automatically
quoted).
`$app.db()
.select("table1.id", "table2.name")
.from("table1", "table2")
...`
##### [join()](https://pocketbase.io/docs/js-database/\#join)
The `join(type, table, on)` method specifies a `JOIN` clause. It takes 3 parameters:
- `type` \- join type string like `INNER JOIN`, `LEFT JOIN`, etc.
- `table` \- the name of the table to be joined
- `on` \- optional `dbx.Expression` as an `ON` clause
For convenience, you can also use the shortcuts `innerJoin(table, on)`,
`leftJoin(table, on)`,
`rightJoin(table, on)` to specify `INNER JOIN`, `LEFT JOIN` and
`RIGHT JOIN`, respectively.
`$app.db()
.select("users.*")
.from("users")
.innerJoin("profiles", $dbx.exp("profiles.user_id = users.id"))
.join("FULL OUTER JOIN", "department", $dbx.exp("department.id = {:id}", {id: "someId"}))
...`
##### [where(), andWhere(), orWhere()](https://pocketbase.io/docs/js-database/\#where-andwhere-orwhere)
The `where(exp)` method specifies the `WHERE` condition of the query.
You can also use `andWhere(exp)` or `orWhere(exp)` to append additional one or more
conditions to an existing `WHERE` clause.
Each where condition accepts a single `dbx.Expression` (see below for full list).
`/*
SELECT users.*
FROM users
WHERE id = "someId" AND
status = "public" AND
name like "%john%" OR
(
role = "manager" AND
fullTime IS TRUE AND
experience > 10
)
*/
$app.db()
.select("users.*")
.from("users")
.where($dbx.exp("id = {:id}", { id: "someId" }))
.andWhere($dbx.hashExp({ status: "public" }))
.andWhere($dbx.like("name", "john"))
.orWhere($dbx.and(
$dbx.hashExp({
role: "manager",
fullTime: true,
}),
$dbx.exp("experience > {:exp}", { exp: 10 })
))
...`
The following `dbx.Expression` methods are available:
- ` $dbx.exp(raw, optParams) `
Generates an expression with the specified raw query fragment. Use the `optParams` to bind
parameters to the expression.
`$dbx.exp("status = 'public'")
$dbx.exp("total > {:min} AND total < {:max}", { min: 10, max: 30 })`
- ` $dbx.hashExp(pairs) `
Generates a hash expression from a map whose keys are DB column names which need to be filtered according
to the corresponding values.
`// slug = "example" AND active IS TRUE AND tags in ("tag1", "tag2", "tag3") AND parent IS NULL
$dbx.hashExp({
slug: "example",
active: true,
tags: ["tag1", "tag2", "tag3"],
parent: null,
})`
- ` $dbx.not(exp) `
Negates a single expression by wrapping it with `NOT()`.
`// NOT(status = 1)
$dbx.not($dbx.exp("status = 1"))`
- ` $dbx.and(...exps) `
Creates a new expression by concatenating the specified ones with `AND`.
`// (status = 1 AND username like "%john%")
$dbx.and($dbx.exp("status = 1"), $dbx.like("username", "john"))`
- ` $dbx.or(...exps) `
Creates a new expression by concatenating the specified ones with `OR`.
`// (status = 1 OR username like "%john%")
$dbx.or($dbx.exp("status = 1"), $dbx.like("username", "john"))`
- ` $dbx.in(col, ...values) `
Generates an `IN` expression for the specified column and the list of allowed values.
`// status IN ("public", "reviewed")
$dbx.in("status", "public", "reviewed")`
- ` $dbx.notIn(col, ...values) `
Generates an `NOT IN` expression for the specified column and the list of allowed values.
`// status NOT IN ("public", "reviewed")
$dbx.notIn("status", "public", "reviewed")`
- ` $dbx.like(col, ...values) `
Generates a `LIKE` expression for the specified column and the possible strings that the
column should be like. If multiple values are present, the column should be like
**all** of them.
By default, each value will be surrounded by _"%"_ to enable partial matching. Special
characters like _"%"_, _"\\"_, _"\_"_ will also be properly escaped. You may call
`escape(...pairs)` and/or `match(left, right)` to change the default behavior.
`// name LIKE "%test1%" AND name LIKE "%test2%"
$dbx.like("name", "test1", "test2")
// name LIKE "test1%"
$dbx.like("name", "test1").match(false, true)`
- ` $dbx.notLike(col, ...values) `
Generates a `NOT LIKE` expression in similar manner as `like()`.
`// name NOT LIKE "%test1%" AND name NOT LIKE "%test2%"
$dbx.notLike("name", "test1", "test2")
// name NOT LIKE "test1%"
$dbx.notLike("name", "test1").match(false, true)`
- ` $dbx.orLike(col, ...values) `
This is similar to `like()` except that the column must be one of the provided values, aka.
multiple values are concatenated with `OR` instead of `AND`.
`// name LIKE "%test1%" OR name LIKE "%test2%"
$dbx.orLike("name", "test1", "test2")
// name LIKE "test1%" OR name LIKE "test2%"
$dbx.orLike("name", "test1", "test2").match(false, true)`
- ` $dbx.orNotLike(col, ...values) `
This is similar to `notLike()` except that the column must not be one of the provided
values, aka. multiple values are concatenated with `OR` instead of `AND`.
`// name NOT LIKE "%test1%" OR name NOT LIKE "%test2%"
$dbx.orNotLike("name", "test1", "test2")
// name NOT LIKE "test1%" OR name NOT LIKE "test2%"
$dbx.orNotLike("name", "test1", "test2").match(false, true)`
- ` $dbx.exists(exp) `
Prefix with `EXISTS` the specified expression (usually a subquery).
`// EXISTS (SELECT 1 FROM users WHERE status = 'active')
$dbx.exists(dbx.exp("SELECT 1 FROM users WHERE status = 'active'"))`
- ` $dbx.notExists(exp) `
Prefix with `NOT EXISTS` the specified expression (usually a subquery).
`// NOT EXISTS (SELECT 1 FROM users WHERE status = 'active')
$dbx.notExists(dbx.exp("SELECT 1 FROM users WHERE status = 'active'"))`
- ` $dbx.between(col, from, to) `
Generates a `BETWEEN` expression with the specified range.
`// age BETWEEN 3 and 99
$dbx.between("age", 3, 99)`
- ` $dbx.notBetween(col, from, to) `
Generates a `NOT BETWEEN` expression with the specified range.
`// age NOT BETWEEN 3 and 99
$dbx.notBetween("age", 3, 99)`
##### [orderBy(), andOrderBy()](https://pocketbase.io/docs/js-database/\#orderby-andorderby)
The `orderBy(...cols)` specifies the `ORDER BY` clause of the query.
A column name can contain _"ASC"_ or _"DESC"_ to indicate its ordering direction.
You can also use `andOrderBy(...cols)` to append additional columns to an existing
`ORDER BY` clause.
`$app.db()
.select("users.*")
.from("users")
.orderBy("created ASC", "updated DESC")
.andOrderBy("title ASC")
...`
##### [groupBy(), andGroupBy()](https://pocketbase.io/docs/js-database/\#groupby-andgroupby)
The `groupBy(...cols)` specifies the `GROUP BY` clause of the query.
You can also use `andGroupBy(...cols)` to append additional columns to an existing
`GROUP BY` clause.
`$app.db()
.select("users.*")
.from("users")
.groupBy("department", "level")
...`
##### [having(), andHaving(), orHaving()](https://pocketbase.io/docs/js-database/\#having-andhaving-orhaving)
The `having(exp)` specifies the `HAVING` clause of the query.
Similarly to
`where(exp)`, it accept a single `dbx.Expression` (see all available expressions
listed above).
You can also use `andHaving(exp)` or `orHaving(exp)` to append additional one or
more conditions to an existing `HAVING` clause.
`$app.db()
.select("users.*")
.from("users")
.groupBy("department", "level")
.having($dbx.exp("sum(level) > {:sum}", { sum: 10 }))
...`
##### [limit()](https://pocketbase.io/docs/js-database/\#limit)
The `limit(number)` method specifies the `LIMIT` clause of the query.
`$app.db()
.select("users.*")
.from("users")
.limit(30)
...`
##### [offset()](https://pocketbase.io/docs/js-database/\#offset)
The `offset(number)` method specifies the `OFFSET` clause of the query. Usually used
together with `limit(number)`.
`$app.db()
.select("users.*")
.from("users")
.offset(5)
.limit(30)
...`
### [Transaction](https://pocketbase.io/docs/js-database/\#transaction)
To execute multiple queries in a transaction you can use
[`$app.runInTransaction(fn)`](https://pocketbase.io/jsvm/functions/_app.runInTransaction.html)
.
The DB operations are persisted only if the transaction completes without throwing an error.
It is safe to nest `runInTransaction` calls as long as you use the callback's
`txApp` argument.
Inside the transaction function always use its `txApp` argument and not the original
`$app` instance because we allow only a single writer/transaction at a time and it could
result in a deadlock.
To avoid performance issues, try to minimize slow/long running tasks such as sending emails,
connecting to external services, etc. as part of the transaction.
`$app.runInTransaction((txApp) => {
// update a record
const record = txApp.findRecordById("articles", "RECORD_ID")
record.set("status", "active")
txApp.save(record)
// run a custom raw query (doesn't fire event hooks)
txApp.db().newQuery("DELETE FROM articles WHERE status = 'pending'").execute()
})`
* * *
[Prev: Routing](https://pocketbase.io/docs/js-routing) [Next: Record operations](https://pocketbase.io/docs/js-records)
## API Logs Management
API Logs
**[List logs](https://pocketbase.io/docs/api-logs/#list-logs)**
Returns a paginated logs list.
Only superusers can perform this action.
JavaScript
Dart
`import PocketBase from 'pocketbase';
const pb = new PocketBase('http://127.0.0.1:8090');
...
await pb.collection("_superusers").authWithPassword('test@example.com', '1234567890');
const pageResult = await pb.logs.getList(1, 20, {
filter: 'data.status >= 400'
});`
`import 'package:pocketbase/pocketbase.dart';
final pb = PocketBase('http://127.0.0.1:8090');
...
await pb.collection("_superusers").authWithPassword('test@example.com', '1234567890');
final pageResult = await pb.logs.getList(
page: 1,
perPage: 20,
filter: 'data.status >= 400',
);`
###### API details
**GET**
/api/logs
Requires `Authorization:TOKEN`
Query parameters
| Param | Type | Description |
| --- | --- | --- |
| page | Number | The page (aka. offset) of the paginated list ( _default to 1_). |
| perPage | Number | The max returned logs per page ( _default to 30_). |
| sort | String | Specify the _ORDER BY_ fields. Add `-` / `+` (default) in front of the attribute for DESC / ASC order, e.g.: `// DESC by the insertion rowid and ASC by level ?sort=-rowid,level` **Supported log sort fields:** `@random`, `rowid`, `id`, `created`, `updated`, `level`, `message` and any `data.*` attribute. |
| filter | String | Filter expression to filter/search the returned logs list, e.g.: `?filter=(data.url~'test.com' && level>0)` **Supported log filter fields:** `id`, `created`, `updated`, `level`, `message` and any `data.*` attribute. The syntax basically follows the format `OPERAND OPERATOR OPERAND`, where: - `OPERAND` \- could be any field literal, string (single or double quoted), number, null, true, false - `OPERATOR` \- is one of:
- `=` Equal - `!=` NOT equal - `>` Greater than - `>=` Greater than or equal - `<` Less than - `<=` Less than or equal - `~` Like/Contains (if not specified auto wraps the right string OPERAND in a "%" for wildcard match) - `!~` NOT Like/Contains (if not specified auto wraps the right string OPERAND in a "%" for wildcard match) - `?=` _Any/At least one of_ Equal - `?!=` _Any/At least one of_ NOT equal - `?>` _Any/At least one of_ Greater than - `?>=` _Any/At least one of_ Greater than or equal - `?<` _Any/At least one of_ Less than - `?<=` _Any/At least one of_ Less than or equal - `?~` _Any/At least one of_ Like/Contains (if not specified auto wraps the right string OPERAND in a "%" for wildcard match) - `?!~` _Any/At least one of_ NOT Like/Contains (if not specified auto wraps the right string OPERAND in a "%" for wildcard match) To group and combine several expressions you can use parenthesis `(...)`, `&&` (AND) and `||` (OR) tokens. Single line comments are also supported: `// Example comment`. |
| fields | String | Comma separated string of the fields to return in the JSON response _(by default returns all fields)_. Ex.:
`?fields=*,expand.relField.name` `*` targets all keys from the specific depth level. In addition, the following field modifiers are also supported: - `:excerpt(maxLength, withEllipsis?)`
Returns a short plain text version of the field string value.
Ex.: `?fields=*,description:excerpt(200,true)` |
Responses
200 400 401 403
`{
"page": 1,
"perPage": 20,
"totalItems": 2,
"items": [\
{\
"id": "ai5z3aoed6809au",\
"created": "2024-10-27 09:28:19.524Z",\
"data": {\
"auth": "_superusers",\
"execTime": 2.392327,\
"method": "GET",\
"referer": "http://localhost:8090/_/",\
"remoteIP": "127.0.0.1",\
"status": 200,\
"type": "request",\
"url": "/api/collections/_pbc_2287844090/records?page=1&perPage=1&filter=&fields=id",\
"userAgent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36",\
"userIP": "127.0.0.1"\
},\
"message": "GET /api/collections/_pbc_2287844090/records?page=1&perPage=1&filter=&fields=id",\
"level": 0\
},\
{\
"id": "26apis4s3sm9yqm",\
"created": "2024-10-27 09:28:19.524Z",\
"data": {\
"auth": "_superusers",\
"execTime": 2.392327,\
"method": "GET",\
"referer": "http://localhost:8090/_/",\
"remoteIP": "127.0.0.1",\
"status": 200,\
"type": "request",\
"url": "/api/collections/_pbc_2287844090/records?page=1&perPage=1&filter=&fields=id",\
"userAgent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36",\
"userIP": "127.0.0.1"\
},\
"message": "GET /api/collections/_pbc_2287844090/records?page=1&perPage=1&filter=&fields=id",\
"level": 0\
}\
]
}`
`{
"status": 400,
"message": "Something went wrong while processing your request. Invalid filter.",
"data": {}
}`
`{
"status": 401,
"message": "The request requires valid record authorization token.",
"data": {}
}`
`{
"status": 403,
"message": "The authorized record is not allowed to perform this action.",
"data": {}
}`
**[View log](https://pocketbase.io/docs/api-logs/#view-log)**
Returns a single log by its ID.
Only superusers can perform this action.
JavaScript
Dart
`import PocketBase from 'pocketbase';
const pb = new PocketBase('http://127.0.0.1:8090');
...
await pb.collection("_superusers").authWithEmail('test@example.com', '123456');
const log = await pb.logs.getOne('LOG_ID');`
`import 'package:pocketbase/pocketbase.dart';
final pb = PocketBase('http://127.0.0.1:8090');
...
await pb.collection("_superusers").authWithEmail('test@example.com', '123456');
final log = await pb.logs.getOne('LOG_ID');`
###### API details
**GET**
/api/logs/ `id`
Requires `Authorization:TOKEN`
Path parameters
| Param | Type | Description |
| --- | --- | --- |
| id | String | ID of the log to view. |
Query parameters
| Param | Type | Description |
| --- | --- | --- |
| fields | String | Comma separated string of the fields to return in the JSON response _(by default returns all fields)_. Ex.:
`?fields=*,expand.relField.name` `*` targets all keys from the specific depth level. In addition, the following field modifiers are also supported: - `:excerpt(maxLength, withEllipsis?)`
Returns a short plain text version of the field string value.
Ex.: `?fields=*,description:excerpt(200,true)` |
Responses
200 401 403 404
`{
"id": "ai5z3aoed6809au",
"created": "2024-10-27 09:28:19.524Z",
"data": {
"auth": "_superusers",
"execTime": 2.392327,
"method": "GET",
"referer": "http://localhost:8090/_/",
"remoteIP": "127.0.0.1",
"status": 200,
"type": "request",
"url": "/api/collections/_pbc_2287844090/records?page=1&perPage=1&filter=&fields=id",
"userAgent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36",
"userIP": "127.0.0.1"
},
"message": "GET /api/collections/_pbc_2287844090/records?page=1&perPage=1&filter=&fields=id",
"level": 0
}`
`{
"status": 401,
"message": "The request requires valid record authorization token.",
"data": {}
}`
`{
"status": 403,
"message": "The authorized record is not allowed to perform this action.",
"data": {}
}`
`{
"status": 404,
"message": "The requested resource wasn't found.",
"data": {}
}`
**[Logs statistics](https://pocketbase.io/docs/api-logs/#logs-statistics)**
Returns hourly aggregated logs statistics.
Only superusers can perform this action.
JavaScript
Dart
`import PocketBase from 'pocketbase';
const pb = new PocketBase('http://127.0.0.1:8090');
...
await pb.collection("_superusers").authWithPassword('test@example.com', '123456');
const stats = await pb.logs.getStats({
filter: 'data.status >= 400'
});`
`import 'package:pocketbase/pocketbase.dart';
final pb = PocketBase('http://127.0.0.1:8090');
...
await pb.collection("_superusers").authWithPassword('test@example.com', '123456');
final stats = await pb.logs.getStats(
filter: 'data.status >= 400'
);`
###### API details
**GET**
/api/logs/stats
Requires `Authorization:TOKEN`
Query parameters
| Param | Type | Description |
| --- | --- | --- |
| filter | String | Filter expression to filter/search the logs, e.g.: `?filter=(data.url~'test.com' && level>0)` **Supported log filter fields:** `rowid`, `id`, `created`, `updated`, `level`, `message` and any `data.*` attribute. The syntax basically follows the format `OPERAND OPERATOR OPERAND`, where: - `OPERAND` \- could be any field literal, string (single or double quoted), number, null, true, false - `OPERATOR` \- is one of:
- `=` Equal - `!=` NOT equal - `>` Greater than - `>=` Greater than or equal - `<` Less than - `<=` Less than or equal - `~` Like/Contains (if not specified auto wraps the right string OPERAND in a "%" for wildcard match) - `!~` NOT Like/Contains (if not specified auto wraps the right string OPERAND in a "%" for wildcard match) - `?=` _Any/At least one of_ Equal - `?!=` _Any/At least one of_ NOT equal - `?>` _Any/At least one of_ Greater than - `?>=` _Any/At least one of_ Greater than or equal - `?<` _Any/At least one of_ Less than - `?<=` _Any/At least one of_ Less than or equal - `?~` _Any/At least one of_ Like/Contains (if not specified auto wraps the right string OPERAND in a "%" for wildcard match) - `?!~` _Any/At least one of_ NOT Like/Contains (if not specified auto wraps the right string OPERAND in a "%" for wildcard match) To group and combine several expressions you can use parenthesis `(...)`, `&&` (AND) and `||` (OR) tokens. Single line comments are also supported: `// Example comment`. |
| fields | String | Comma separated string of the fields to return in the JSON response _(by default returns all fields)_. Ex.:
`?fields=*,expand.relField.name` `*` targets all keys from the specific depth level. In addition, the following field modifiers are also supported: - `:excerpt(maxLength, withEllipsis?)`
Returns a short plain text version of the field string value.
Ex.: `?fields=*,description:excerpt(200,true)` |
Responses
200 400 401 403
`[\
{\
"total": 4,\
"date": "2022-06-01 19:00:00.000"\
},\
{\
"total": 1,\
"date": "2022-06-02 12:00:00.000"\
},\
{\
"total": 8,\
"date": "2022-06-02 13:00:00.000"\
}\
]`
`{
"status": 400,
"message": "Something went wrong while processing your request. Invalid filter.",
"data": {}
}`
`{
"status": 401,
"message": "The request requires valid record authorization token.",
"data": {}
}`
`{
"status": 403,
"message": "The authorized record is not allowed to perform this action.",
"data": {}
}`
* * *
[Prev: API Settings](https://pocketbase.io/docs/api-settings) [Next: API Crons](https://pocketbase.io/docs/api-crons)
## PocketBase Logging Guide
Logging
`app.Logger()` provides access to a standard `slog.Logger` implementation that
writes any logs into the database so that they can be later explored from the PocketBase
_Dashboard > Logs_ section.
For better performance and to minimize blocking on hot paths, logs are written with debounce and
on batches:
- 3 seconds after the last debounced log write
- when the batch threshold is reached (currently 200)
- right before app termination to attempt saving everything from the existing logs queue
- [Log methods](https://pocketbase.io/docs/go-logging/#log-methods)
- [Debug(message, attrs...)](https://pocketbase.io/docs/go-logging/#debugmessage-attrs-)
- [Info(message, attrs...)](https://pocketbase.io/docs/go-logging/#infomessage-attrs-)
- [Warn(message, attrs...)](https://pocketbase.io/docs/go-logging/#warnmessage-attrs-)
- [Error(message, attrs...)](https://pocketbase.io/docs/go-logging/#errormessage-attrs-)
- [With(attrs...)](https://pocketbase.io/docs/go-logging/#withattrs-)
- [WithGroup(name)](https://pocketbase.io/docs/go-logging/#withgroupname)
- [Logs settings](https://pocketbase.io/docs/go-logging/#logs-settings)
- [Custom log queries](https://pocketbase.io/docs/go-logging/#custom-log-queries)
### [Log methods](https://pocketbase.io/docs/go-logging/\#log-methods)
All standard
[`slog.Logger`](https://pkg.go.dev/log/slog)
methods are available but below is a list with some of the most notable ones.
##### [Debug(message, attrs...)](https://pocketbase.io/docs/go-logging/\#debugmessage-attrs-)
`app.Logger().Debug("Debug message!")
app.Logger().Debug(
"Debug message with attributes!",
"name", "John Doe",
"id", 123,
)`
##### [Info(message, attrs...)](https://pocketbase.io/docs/go-logging/\#infomessage-attrs-)
`app.Logger().Info("Info message!")
app.Logger().Info(
"Info message with attributes!",
"name", "John Doe",
"id", 123,
)`
##### [Warn(message, attrs...)](https://pocketbase.io/docs/go-logging/\#warnmessage-attrs-)
`app.Logger().Warn("Warning message!")
app.Logger().Warn(
"Warning message with attributes!",
"name", "John Doe",
"id", 123,
)`
##### [Error(message, attrs...)](https://pocketbase.io/docs/go-logging/\#errormessage-attrs-)
`app.Logger().Error("Error message!")
app.Logger().Error(
"Error message with attributes!",
"id", 123,
"error", err,
)`
##### [With(attrs...)](https://pocketbase.io/docs/go-logging/\#withattrs-)
`With(atrs...)` creates a new local logger that will "inject" the specified attributes with each
following log.
`l := app.Logger().With("total", 123)
// results in log with data {"total": 123}
l.Info("message A")
// results in log with data {"total": 123, "name": "john"}
l.Info("message B", "name", "john")`
##### [WithGroup(name)](https://pocketbase.io/docs/go-logging/\#withgroupname)
`WithGroup(name)` creates a new local logger that wraps all logs attributes under the specified
group name.
`l := app.Logger().WithGroup("sub")
// results in log with data {"sub": { "total": 123 }}
l.Info("message A", "total", 123)`
### [Logs settings](https://pocketbase.io/docs/go-logging/\#logs-settings)
You can control various log settings like logs retention period, minimal log level, request IP logging,
etc. from the logs settings panel:

### [Custom log queries](https://pocketbase.io/docs/go-logging/\#custom-log-queries)
The logs are usually meant to be filtered from the UI but if you want to programmatically retrieve and
filter the stored logs you can make use of the
[`app.LogQuery()`](https://pkg.go.dev/github.com/pocketbase/pocketbase/core#BaseApp.LogsQuery) query builder method. For example:
`logs := []*core.Log{}
// see https://pocketbase.io/docs/go-database/#query-builder
err := app.LogQuery().
// target only debug and info logs
AndWhere(dbx.In("level", -4, 0).
// the data column is serialized json object and could be anything
AndWhere(dbx.NewExp("json_extract(data, '$.type') = 'request'")).
OrderBy("created DESC").
Limit(100).
All(&logs)`
* * *
[Prev: Filesystem](https://pocketbase.io/docs/go-filesystem) [Next: Testing](https://pocketbase.io/docs/go-testing)
## PocketBase API Backups
API Backups
**[List backups](https://pocketbase.io/docs/api-backups/#list-backups)**
Returns list with all available backup files.
Only superusers can perform this action.
JavaScript
Dart
`import PocketBase from 'pocketbase';
const pb = new PocketBase('http://127.0.0.1:8090');
...
await pb.collection("_superusers").authWithPassword('test@example.com', '1234567890');
const backups = await pb.backups.getFullList();`
`import 'package:pocketbase/pocketbase.dart';
final pb = PocketBase('http://127.0.0.1:8090');
...
await pb.collection("_superusers").authWithPassword('test@example.com', '1234567890');
final backups = await pb.backups.getFullList();`
###### API details
**GET**
/api/backups
Query parameters
| Param | Type | Description |
| --- | --- | --- |
| fields | String | Comma separated string of the fields to return in the JSON response _(by default returns all fields)_. Ex.:
`?fields=*,expand.relField.name` `*` targets all keys from the specific depth level. In addition, the following field modifiers are also supported: - `:excerpt(maxLength, withEllipsis?)`
Returns a short plain text version of the field string value.
Ex.: `?fields=*,description:excerpt(200,true)` |
Responses
200 400 401 403
`[\
{\
"key": "pb_backup_20230519162514.zip",\
"modified": "2023-05-19 16:25:57.542Z",\
"size": 251316185\
},\
{\
"key": "pb_backup_20230518162514.zip",\
"modified": "2023-05-18 16:25:57.542Z",\
"size": 251314010\
}\
]`
`{
"status": 400,
"message": "Failed to load backups filesystem.",
"data": {}
}`
`{
"status": 401,
"message": "The request requires valid record authorization token.",
"data": {}
}`
`{
"status": 403,
"message": "Only superusers can perform this action.",
"data": {}
}`
**[Create backup](https://pocketbase.io/docs/api-backups/#create-backup)**
Creates a new app data backup.
This action will return an error if there is another backup/restore operation already in progress.
Only superusers can perform this action.
JavaScript
Dart
`import PocketBase from 'pocketbase';
const pb = new PocketBase('http://127.0.0.1:8090');
...
await pb.collection("_superusers").authWithPassword('test@example.com', '1234567890');
await pb.backups.create('new_backup.zip');`
`import 'package:pocketbase/pocketbase.dart';
final pb = PocketBase('http://127.0.0.1:8090');
...
await pb.collection("_superusers").authWithPassword('test@example.com', '1234567890');
await pb.backups.create('new_backup.zip');`
###### API details
**POST**
/api/backups
Requires `Authorization:TOKEN`
Body Parameters
| Param | Type | Description |
| --- | --- | --- |
| Optionalname | String | The base name of the backup file to create.
Must be in the format `[a-z0-9_-].zip` If not set, it will be auto generated. |
Body parameters could be sent as _JSON_ or
_multipart/form-data_.
Responses
204 400 401 403
`null`
`{
"status": 400,
"message": "Try again later - another backup/restore process has already been started.",
"data": {}
}`
`{
"status": 401,
"message": "The request requires valid record authorization token.",
"data": {}
}`
`{
"status": 403,
"message": "The authorized record is not allowed to perform this action.",
"data": {}
}`
**[Upload backup](https://pocketbase.io/docs/api-backups/#upload-backup)**
Uploads an existing backup zip file.
Only superusers can perform this action.
JavaScript
Dart
`import PocketBase from 'pocketbase';
const pb = new PocketBase('http://127.0.0.1:8090');
...
await pb.collection("_superusers").authWithPassword('test@example.com', '1234567890');
await pb.backups.upload({ file: new Blob([...]) });`
`import 'package:pocketbase/pocketbase.dart';
final pb = PocketBase('http://127.0.0.1:8090');
...
await pb.collection("_superusers").authWithPassword('test@example.com', '1234567890');
await pb.backups.upload(http.MultipartFile.fromBytes('file', ...));`
###### API details
**POST**
/api/backups/upload
Requires `Authorization:TOKEN`
Body Parameters
| Param | Type | Description |
| --- | --- | --- |
| Requiredfile | File | The zip archive to upload. |
Uploading files is supported only via _multipart/form-data_.
Responses
204 400 401 403
`null`
`{
"status": 400,
"message": "Something went wrong while processing your request.",
"data": {
"file": {
"code": "validation_invalid_mime_type",
"message": "\"test_backup.txt\" mime type must be one of: application/zip."
}
}
}
}`
`{
"status": 401,
"message": "The request requires valid record authorization token.",
"data": {}
}`
`{
"status": 403,
"message": "The authorized record is not allowed to perform this action.",
"data": {}
}`
**[Delete backup](https://pocketbase.io/docs/api-backups/#delete-backup)**
Deletes a single backup by its name.
This action will return an error if the backup to delete is still being generated or part of a
restore operation.
Only superusers can perform this action.
JavaScript
Dart
`import PocketBase from 'pocketbase';
const pb = new PocketBase('http://127.0.0.1:8090');
...
await pb.collection("_superusers").authWithPassword('test@example.com', '1234567890');
await pb.backups.delete('pb_data_backup.zip');`
`import 'package:pocketbase/pocketbase.dart';
final pb = PocketBase('http://127.0.0.1:8090');
...
await pb.collection("_superusers").authWithPassword('test@example.com', '1234567890');
await pb.backups.delete('pb_data_backup.zip');`
###### API details
**DELETE**
/api/backups/ `key`
Requires `Authorization:TOKEN`
Path parameters
| Param | Type | Description |
| --- | --- | --- |
| key | String | The key of the backup file to delete. |
Responses
204 400 401 403
`null`
`{
"status": 400,
"message": "Try again later - another backup/restore process has already been started.",
"data": {}
}`
`{
"status": 401,
"message": "The request requires valid record authorization token.",
"data": {}
}`
`{
"status": 403,
"message": "The authorized record is not allowed to perform this action.",
"data": {}
}`
**[Restore backup](https://pocketbase.io/docs/api-backups/#restore-backup)**
Restore a single backup by its name and restarts the current running PocketBase process.
This action will return an error if there is another backup/restore operation already in progress.
Only superusers can perform this action.
JavaScript
Dart
`import PocketBase from 'pocketbase';
const pb = new PocketBase('http://127.0.0.1:8090');
...
await pb.collection("_superusers").authWithPassword('test@example.com', '1234567890');
await pb.backups.restore('pb_data_backup.zip');`
`import 'package:pocketbase/pocketbase.dart';
final pb = PocketBase('http://127.0.0.1:8090');
...
await pb.collection("_superusers").authWithPassword('test@example.com', '1234567890');
await pb.backups.restore('pb_data_backup.zip');`
###### API details
**POST**
/api/backups/ `key`/restore
Requires `Authorization:TOKEN`
Path parameters
| Param | Type | Description |
| --- | --- | --- |
| key | String | The key of the backup file to restore. |
Responses
204 400 401 403
`null`
`{
"status": 400,
"message": "Try again later - another backup/restore process has already been started.",
"data": {}
}`
`{
"status": 401,
"message": "The request requires valid record authorization token.",
"data": {}
}`
`{
"status": 403,
"message": "The authorized record is not allowed to perform this action.",
"data": {}
}`
**[Download backup](https://pocketbase.io/docs/api-backups/#download-backup)**
Downloads a single backup file.
Only superusers can perform this action.
JavaScript
Dart
`import PocketBase from 'pocketbase';
const pb = new PocketBase('http://127.0.0.1:8090');
...
await pb.collection("_superusers").authWithPassword('test@example.com', '1234567890');
const token = await pb.files.getToken();
const url = pb.backups.getDownloadUrl(token, 'pb_data_backup.zip');`
`import 'package:pocketbase/pocketbase.dart';
final pb = PocketBase('http://127.0.0.1:8090');
...
await pb.collection("_superusers").authWithPassword('test@example.com', '1234567890');
final token = await pb.files.getToken();
final url = pb.backups.getDownloadUrl(token, 'pb_data_backup.zip');`
###### API details
**GET**
/api/backups/ `key`
Path parameters
| Param | Type | Description |
| --- | --- | --- |
| key | String | The key of the backup file to download. |
Query parameters
| Param | Type | Description |
| --- | --- | --- |
| token | String | Superuser **file token** for granting access to the **backup file**. |
Responses
200 400 404
`[file resource]`
`{
"status": 400,
"message": "Filesystem initialization failure.",
"data": {}
}`
`{
"status": 404,
"message": "The requested resource wasn't found.",
"data": {}
}`
* * *
[Prev: API Crons](https://pocketbase.io/docs/api-crons) [Next: API Health](https://pocketbase.io/docs/api-health)
## PocketBase Filesystem Guide
Filesystem
PocketBase comes with a thin abstraction between the local filesystem and S3.
To configure which one will be used you can adjust the storage settings from
_Dashboard > Settings > Files storage_ section.
The filesystem abstraction can be accessed programmatically via the
[`$app.newFilesystem()`](https://pocketbase.io/jsvm/functions/_app.newFilesystem.html)
method.
Below are listed some of the most common operations but you can find more details in the
[`filesystem.System`](https://pocketbase.io/jsvm/interfaces/filesystem.System.html)
interface.
Always make sure to call `close()` at the end for both the created filesystem instance and
the retrieved file readers to prevent leaking resources.
- [Reading files](https://pocketbase.io/docs/js-filesystem/#reading-files)
- [Saving files](https://pocketbase.io/docs/js-filesystem/#saving-files)
- [Deleting files](https://pocketbase.io/docs/js-filesystem/#deleting-files)
### [Reading files](https://pocketbase.io/docs/js-filesystem/\#reading-files)
To retrieve the file content of a single stored file you can use
[`getFile(key)`](https://pocketbase.io/jsvm/interfaces/filesystem.System.html#getFile)
.
Note that file keys often contain a **prefix** (aka. the "path" to the file). For record
files the full key is
`collectionId/recordId/filename`.
To retrieve multiple files matching a specific _prefix_ you can use
[`list(prefix)`](https://pocketbase.io/jsvm/interfaces/filesystem.System.html#list)
.
The below code shows a minimal example how to retrieve the content of a single record file as string.
`let record = $app.findAuthRecordByEmail("users", "test@example.com")
// construct the full file key by concatenating the record storage path with the specific filename
let avatarKey = record.baseFilesPath() + "/" + record.get("avatar")
let fsys, file, content;
try {
// initialize the filesystem
fsys = $app.newFilesystem();
// retrieve a file reader for the avatar key
file = fsys.getFile(avatarKey)
// copy as plain string
content = toString(file)
} finally {
file?.close();
fsys?.close();
}`
### [Saving files](https://pocketbase.io/docs/js-filesystem/\#saving-files)
There are several methods to save _(aka. write/upload)_ files depending on the available file content
source:
- [`upload(content, key)`](https://pocketbase.io/jsvm/interfaces/filesystem.System.html#upload)
- [`uploadFile(file, key)`](https://pocketbase.io/jsvm/interfaces/filesystem.System.html#uploadFile)
- [`uploadMultipart(mfh, key)`](https://pocketbase.io/jsvm/interfaces/filesystem.System.html#uploadMultipart)
Most users rarely will have to use the above methods directly because for collection records the file
persistence is handled transparently when saving the record model (it will also perform size and MIME type
validation based on the collection `file` field options). For example:
`let record = $app.findRecordById("articles", "RECORD_ID")
// Other available File factories
// - $filesystem.fileFromBytes(content, name)
// - $filesystem.fileFromURL(url)
// - $filesystem.fileFromMultipart(mfh)
let file = $filesystem.fileFromPath("/local/path/to/file")
// set new file (can be single or array of File values)
// (if the record has an old file it is automatically deleted on successful save)
record.set("yourFileField", file)
$app.save(record)`
### [Deleting files](https://pocketbase.io/docs/js-filesystem/\#deleting-files)
Files can be deleted from the storage filesystem using
[`delete(key)`](https://pocketbase.io/jsvm/interfaces/filesystem.System.html#delete)
.
Similar to the previous section, most users rarely will have to use the `delete` file method directly
because for collection records the file deletion is handled transparently when removing the existing filename
from the record model (this also ensure that the db entry referencing the file is also removed). For example:
`let record = $app.findRecordById("articles", "RECORD_ID")
// if you want to "reset" a file field (aka. deleting the associated single or multiple files)
// you can set it to null
record.set("yourFileField", null)
// OR if you just want to remove individual file(s) from a multiple file field you can use the "-" modifier
// (the value could be a single filename string or slice of filename strings)
record.set("yourFileField-", "example_52iWbGinWd.txt")
$app.save(record)`
* * *
[Prev: Realtime messaging](https://pocketbase.io/docs/js-realtime) [Next: Logging](https://pocketbase.io/docs/js-logging)
## File Handling Guide
Files upload and handling
- [Uploading files](https://pocketbase.io/docs/files-handling/#uploading-files)
- [Deleting files](https://pocketbase.io/docs/files-handling/#deleting-files)
- [File URL](https://pocketbase.io/docs/files-handling/#file-url)
- [Protected files](https://pocketbase.io/docs/files-handling/#protected-files)
- [Storage options](https://pocketbase.io/docs/files-handling/#storage-options)
### [Uploading files](https://pocketbase.io/docs/files-handling/\#uploading-files)
To upload files, you must first add a `file` field to your collection:

Once added, you could create/update a Record and upload "documents" files by sending a
`multipart/form-data` request using the _Records create/update APIs_.
Each uploaded file will be stored with the original filename (sanitized) and suffixed with a
random part (usually 10 characters). For example `test_52iwbgds7l.png`.
The max allowed size of a single file currently is limited to ~8GB (253-1 bytes).
Here is an example how to create a new record and upload multiple files to the example "documents"
`file` field using the SDKs:
JavaScript
Dart
`import PocketBase from 'pocketbase';
const pb = new PocketBase('http://127.0.0.1:8090');
...
// create a new record and upload multiple files
// (files must be Blob or File instances)
const createdRecord = await pb.collection('example').create({
title: 'Hello world!', // regular text field
'documents': [\
new File(['content 1...'], 'file1.txt'),\
new File(['content 2...'], 'file2.txt'),\
]
});
// -----------------------------------------------------------
// Alternative FormData + plain HTML file input example
//
// -----------------------------------------------------------
const fileInput = document.getElementById('fileInput');
const formData = new FormData();
// set regular text field
formData.append('title', 'Hello world!');
// listen to file input changes and add the selected files to the form data
fileInput.addEventListener('change', function () {
for (let file of fileInput.files) {
formData.append('documents', file);
}
});
...
// upload and create new record
const createdRecord = await pb.collection('example').create(formData);`
`import 'package:pocketbase/pocketbase.dart';
import 'package:http/http.dart' as http;
final pb = PocketBase('http://127.0.0.1:8090');
...
// create a new record and upload multiple files
final record = await pb.collection('example').create(
body: {
'title': 'Hello world!', // regular text field
},
files: [\
http.MultipartFile.fromString(\
'documents',\
'example content 1...',\
filename: 'file1.txt',\
),\
http.MultipartFile.fromString(\
'documents',\
'example content 2...',\
filename: 'file2.txt',\
),\
],
);`
If your `file` field supports uploading multiple files (aka.
**Max Files option is >= 2**) you can use the `+` prefix/suffix field name modifier
to respectively prepend/append new files alongside the already uploaded ones. For example:
JavaScript
Dart
`import PocketBase from 'pocketbase';
const pb = new PocketBase('http://127.0.0.1:8090');
...
const createdRecord = await pb.collection('example').update('RECORD_ID', {
"documents+": new File(["content 3..."], "file3.txt")
});`
`import 'package:pocketbase/pocketbase.dart';
import 'package:http/http.dart' as http;
final pb = PocketBase('http://127.0.0.1:8090');
...
final record = await pb.collection('example').update(
'RECORD_ID',
files: [\
http.MultipartFile.fromString(\
'documents+',\
'example content 3...',\
filename: 'file3.txt',\
),\
],
);`
### [Deleting files](https://pocketbase.io/docs/files-handling/\#deleting-files)
To delete uploaded file(s), you could either edit the Record from the Dashboard, or use the API and set
the file field to a zero-value
(empty string, `[]`).
If you want to **delete individual file(s) from a multiple file upload field**, you could
suffix the field name with `-` and specify the filename(s) you want to delete. Here are some examples
using the SDKs:
JavaScript
Dart
`import PocketBase from 'pocketbase';
const pb = new PocketBase('http://127.0.0.1:8090');
...
// delete all "documents" files
await pb.collection('example').update('RECORD_ID', {
'documents': [],
});
// delete individual files
await pb.collection('example').update('RECORD_ID', {
'documents-': ["file1.pdf", "file2.txt"],
});`
`import 'package:pocketbase/pocketbase.dart';
final pb = PocketBase('http://127.0.0.1:8090');
...
// delete all "documents" files
await pb.collection('example').update('RECORD_ID', body: {
'documents': [],
});
// delete individual files
await pb.collection('example').update('RECORD_ID', body: {
'documents-': ["file1.pdf", "file2.txt"],
});`
The above examples use the JSON object data format, but you could also use `FormData` instance
for _multipart/form-data_ requests. If using
`FormData` set the file field to an empty string.
### [File URL](https://pocketbase.io/docs/files-handling/\#file-url)
Each uploaded file could be accessed by requesting its file url:
`http://127.0.0.1:8090/api/files/COLLECTION_ID_OR_NAME/RECORD_ID/FILENAME`
If your file field has the **Thumb sizes** option, you can get a thumb of the image file
(currently limited to jpg, png, and partially gif – its first frame) by adding the `thumb`
query parameter to the url like this:
`http://127.0.0.1:8090/api/files/COLLECTION_ID_OR_NAME/RECORD_ID/FILENAME?thumb=100x300`
The following thumb formats are currently supported:
- **WxH**
(e.g. 100x300) - crop to WxH viewbox (from center)
- **WxHt**
(e.g. 100x300t) - crop to WxH viewbox (from top)
- **WxHb**
(e.g. 100x300b) - crop to WxH viewbox (from bottom)
- **WxHf**
(e.g. 100x300f) - fit inside a WxH viewbox (without cropping)
- **0xH**
(e.g. 0x300) - resize to H height preserving the aspect ratio
- **Wx0**
(e.g. 100x0) - resize to W width preserving the aspect ratio
The original file would be returned, if the requested thumb size is not found or the file is not an image!
If you already have a Record model instance, the SDKs provide a convenient method to generate a file url
by its name.
JavaScript
Dart
`import PocketBase from 'pocketbase';
const pb = new PocketBase('http://127.0.0.1:8090');
...
const record = await pb.collection('example').getOne('RECORD_ID');
// get only the first filename from "documents"
//
// note:
// "documents" is an array of filenames because
// the "documents" field was created with "Max Files" option > 1;
// if "Max Files" was 1, then the result property would be just a string
const firstFilename = record.documents[0];
// returns something like:
// http://127.0.0.1:8090/api/files/example/kfzjt5oy8r34hvn/test_52iWbGinWd.png?thumb=100x250
const url = pb.files.getURL(record, firstFilename, {'thumb': '100x250'});`
`import 'package:pocketbase/pocketbase.dart';
final pb = PocketBase('http://127.0.0.1:8090');
...
final record = await pb.collection('example').getOne('RECORD_ID');
// get only the first filename from "documents"
//
// note:
// "documents" is an array of filenames because
// the "documents" field was created with "Max Files" option > 1;
// if "Max Files" was 1, then the result property would be just a string
final firstFilename = record.getListValue('documents')[0];
// returns something like:
// http://127.0.0.1:8090/api/files/example/kfzjt5oy8r34hvn/test_52iWbGinWd.png?thumb=100x250
final url = pb.files.getURL(record, firstFilename, thumb: '100x250');`
Additionally, to instruct the browser to always download the file instead of showing a preview when
accessed directly, you can append the `?download=1` query parameter to the file url.
### [Protected files](https://pocketbase.io/docs/files-handling/\#protected-files)
By default all files are public accessible if you know their full url.
For most applications this is fine and reasonably safe because all files have a random part appended to
their name, but in some cases you may want an extra security to prevent unauthorized access to sensitive
files like ID card or Passport copies, contracts, etc.
To do this you can mark the `file` field as _Protected_ from its field options in the
Dashboard and then request the file with a special **short-lived file token**.
Only requests that satisfy the **View API rule** of the record collection will be able
to access or download the protected file(s).
JavaScript
Dart
`import PocketBase from 'pocketbase';
const pb = new PocketBase('http://127.0.0.1:8090');
...
// authenticate
await pb.collection('users').authWithPassword('test@example.com', '1234567890');
// generate a file token
const fileToken = await pb.files.getToken();
// retrieve an example protected file url (will be valid ~2min)
const record = await pb.collection('example').getOne('RECORD_ID');
const url = pb.files.getURL(record, record.myPrivateFile, {'token': fileToken});`
`import 'package:pocketbase/pocketbase.dart';
final pb = PocketBase('http://127.0.0.1:8090');
...
// authenticate
await pb.collection('users').authWithPassword('test@example.com', '1234567890');
// generate a file token
final fileToken = await pb.files.getToken();
// retrieve an example protected file url (will be valid ~2min)
final record = await pb.collection('example').getOne('RECORD_ID');
final url = pb.files.getURL(record, record.getStringValue('myPrivateFile'), token: fileToken);`
### [Storage options](https://pocketbase.io/docs/files-handling/\#storage-options)
By default PocketBase stores uploaded files in the `pb_data/storage` directory on the local file
system. For the majority of cases this is usually the recommended storage option because it is very fast, easy
to work with and backup.
But if you have limited disk space you could switch to an external S3 compatible storage (AWS S3, MinIO,
Wasabi, DigitalOcean Spaces, Vultr Object Storage, etc.). The easiest way to setup the connection settings
is from the _Dashboard_ \> _Settings_ \> _Files storage_:

* * *
[Prev: Authentication](https://pocketbase.io/docs/authentication) [Next: Working with relations](https://pocketbase.io/docs/working-with-relations)
## PocketBase JavaScript Overview
Overview
- [JavaScript engine](https://pocketbase.io/docs/js-overview/#javascript-engine)
- [Global objects](https://pocketbase.io/docs/js-overview/#global-objects)
- [TypeScript declarations and code completion](https://pocketbase.io/docs/js-overview/#typescript-declarations-and-code-completion)
- [Caveats and limitations](https://pocketbase.io/docs/js-overview/#caveats-and-limitations)
- [Handlers scope](https://pocketbase.io/docs/js-overview/#handlers-scope)
- [Relative paths](https://pocketbase.io/docs/js-overview/#relative-paths)
- [Loading modules](https://pocketbase.io/docs/js-overview/#loading-modules)
- [Performance](https://pocketbase.io/docs/js-overview/#performance)
- [Engine limitations](https://pocketbase.io/docs/js-overview/#engine-limitations)
### [JavaScript engine](https://pocketbase.io/docs/js-overview/\#javascript-engine)
The prebuilt PocketBase v0.17+ executable comes with embedded ES5 JavaScript engine ( [goja](https://github.com/dop251/goja)) which enables you to write custom server-side code using plain JavaScript.
You can start by creating `*.pb.js` file(s) inside a `pb_hooks`
directory next to your executable.
`// pb_hooks/main.pb.js
routerAdd("GET", "/hello/{name}", (e) => {
let name = e.request.pathValue("name")
return e.json(200, { "message": "Hello " + name })
})
onRecordAfterUpdateSuccess((e) => {
console.log("user updated...", e.record.get("email"))
e.next()
}, "users")`
_For convenience, when making changes to the files inside `pb_hooks`, the process will_
_automatically restart/reload itself (currently supported only on UNIX based platforms). The_
_`*.pb.js` files are loaded per their filename sort order._
For most parts, the JavaScript APIs are derived from [Go](https://pocketbase.io/docs/go-overview) with 2 main differences:
- Go exported method and field names are converted to camelCase, for example:
`app.FindRecordById("example", "RECORD_ID")` becomes
`$app.findRecordById("example", "RECORD_ID")`.
- Errors are thrown as regular JavaScript exceptions and not returned as Go values.
##### [Global objects](https://pocketbase.io/docs/js-overview/\#global-objects)
Below is a list with some of the commonly used global objects that are accessible from everywhere:
- [`__hooks`](https://pocketbase.io/jsvm/variables/__hooks.html)
\- The absolute path to the app `pb_hooks` directory.
- [`$app`](https://pocketbase.io/jsvm/modules/_app.html) \- The current running PocketBase application instance.
- [`$apis.*`](https://pocketbase.io/jsvm/modules/_apis.html) \- API routing helpers and middlewares.
- [`$os.*`](https://pocketbase.io/jsvm/modules/_os.html) \- OS level primitives (deleting directories, executing shell commands, etc.).
- [`$security.*`](https://pocketbase.io/jsvm/modules/_security.html) \- Low level helpers for creating and parsing JWTs, random string generation, AES encryption, etc.
- And many more - for all exposed APIs, please refer to the
[JSVM reference docs](https://pocketbase.io/jsvm/index.html).
### [TypeScript declarations and code completion](https://pocketbase.io/docs/js-overview/\#typescript-declarations-and-code-completion)
While you can't use directly TypeScript ( _without transpiling it to JS on your own_), PocketBase
comes with builtin **ambient TypeScript declarations** that can help providing information
and documentation about the available global variables, methods and arguments, code completion, etc. as
long as your editor has TypeScript LSP support
_(most editors either have it builtin or available as plugin)_.
The types declarations are stored in
`pb_data/types.d.ts` file. You can point to those declarations using the
[reference triple-slash directive](https://www.typescriptlang.org/docs/handbook/triple-slash-directives.html#-reference-path-)
at the top of your JS file:
`///
onBootstrap((e) => {
e.next()
console.log("App initialized!")
})`
If after referencing the types your editor still doesn't perform linting, then you can try to rename your
file to have `.pb.ts` extension.
### [Caveats and limitations](https://pocketbase.io/docs/js-overview/\#caveats-and-limitations)
##### [Handlers scope](https://pocketbase.io/docs/js-overview/\#handlers-scope)
Each handler function (hook, route, middleware, etc.) is
**serialized and executed in its own isolated context as a separate "program"**. This means
that you don't have access to custom variables and functions declared outside of the handler scope. For
example, the below code will fail:
`const name = "test"
onBootstrap((e) => {
e.next()
console.log(name) // <-- name will be undefined inside the handler
})`
The above serialization and isolation context is also the reason why error stack trace line numbers may
not be accurate.
One possible workaround for sharing/reusing code across different handlers could be to move and export the
reusable code portion as local module and load it with `require()` inside the handler but keep in
mind that the loaded modules use a shared registry and mutations should be avoided when possible to prevent
concurrency issues:
``onBootstrap((e) => {
e.next()
const config = require(`${__hooks}/config.js`)
console.log(config.name)
})``
##### [Relative paths](https://pocketbase.io/docs/js-overview/\#relative-paths)
Relative file paths are relative to the current working directory (CWD) and not to the
`pb_hooks`.
To get an absolute path to the `pb_hooks` directory you can use the global
`__hooks` variable.
##### [Loading modules](https://pocketbase.io/docs/js-overview/\#loading-modules)
Please note that the embedded JavaScript engine is not a Node.js or browser environment, meaning
that modules that relies on APIs like _window_, _fs_,
_fetch_, _buffer_ or any other runtime specific API not part of the ES5 spec may not
work!
You can load modules either by specifying their local filesystem path or by using their name, which will
automatically search in:
- the current working directory ( _affects also relative paths_)
- any `node_modules` directory
- any parent `node_modules` directory
Currently only CommonJS (CJS) modules are supported and can be loaded with
`const x = require(...)`.
ECMAScript modules (ESM) can be loaded by first precompiling and transforming your dependencies with a bundler
like
[rollup](https://rollupjs.org/),
[webpack](https://webpack.js.org/),
[browserify](https://browserify.org/), etc.
A common usage of local modules is for loading shared helpers or configuration parameters, for example:
`// pb_hooks/utils.js
module.exports = {
hello: (name) => {
console.log("Hello " + name)
}
}`
``// pb_hooks/main.pb.js
onBootstrap((e) => {
e.next()
const utils = require(`${__hooks}/utils.js`)
utils.hello("world")
})``
Loaded modules use a shared registry and mutations should be avoided when possible to prevent
concurrency issues.
##### [Performance](https://pocketbase.io/docs/js-overview/\#performance)
The prebuilt executable comes with a **prewarmed pool of 15 JS runtimes**, which helps
maintaining the handlers execution times on par with the Go equivalent code (see
[benchmarks](https://github.com/pocketbase/benchmarks/blob/master/results/hetzner_cax11.md#go-vs-js-route-execution)). You can adjust the pool size manually with the `--hooksPool=50` flag ( _increasing the pool size may improve the performance in high concurrent scenarios but also will_
_increase the memory usage_).
Note that the handlers performance may degrade if you have heavy computational tasks in pure JavaScript
(encryption, random generators, etc.). For such cases prefer using the exposed [Go bindings](https://pocketbase.io/jsvm/index.html)
(e.g. `$security.randomString(10)`).
##### [Engine limitations](https://pocketbase.io/docs/js-overview/\#engine-limitations)
We inherit some of the limitations and caveats of the embedded JavaScript engine
( [goja](https://github.com/dop251/goja)):
- Has most of ES6 functionality already implemented but it is not fully spec compliant yet.
- No concurrent execution inside a single handler (aka. no `setTimeout`/ `setInterval`).
- Wrapped Go structural types (such as maps, slices) comes with some peculiarities and do not behave the
exact same way as native ECMAScript values (for more details see
[goja ToValue](https://pkg.go.dev/github.com/dop251/goja#Runtime.ToValue)).
- In relation to the above, DB `json` field values require the use of `get()` and
`set()` helpers ( _this may change in the future_).
* * *
[Next: Event hooks](https://pocketbase.io/docs/js-event-hooks)
## PocketBase Testing Guide
Testing
PocketBase exposes several test mocks and stubs (eg. `tests.TestApp`,
`tests.ApiScenario`, `tests.MockMultipartData`, etc.) to help you write unit and
integration tests for your app.
You could find more information in the
[`github.com/pocketbase/pocketbase/tests`](https://pkg.go.dev/github.com/pocketbase/pocketbase/tests)
sub package, but here is a simple example.
- [1\. Setup](https://pocketbase.io/docs/go-testing/#1-setup)
- [2\. Prepare test data](https://pocketbase.io/docs/go-testing/#2-prepare-test-data)
- [3\. Integration test](https://pocketbase.io/docs/go-testing/#3-integration-test)
### [1\. Setup](https://pocketbase.io/docs/go-testing/\#1-setup)
Let's say that we have a custom API route `GET /my/hello` that requires superuser authentication:
`// main.go
package main
import (
"log"
"net/http"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/apis"
"github.com/pocketbase/pocketbase/core"
)
func bindAppHooks(app core.App) {
app.OnServe().BindFunc(func(se *core.ServeEvent) error {
se.Router.Get("/my/hello", func(e *core.RequestEvent) error {
return e.JSON(http.StatusOK, "Hello world!")
}).Bind(apis.RequireSuperuserAuth())
return se.Next()
})
}
func main() {
app := pocketbase.New()
bindAppHooks(app)
if err := app.Start(); err != nil {
log.Fatal(err)
}
}`
### [2\. Prepare test data](https://pocketbase.io/docs/go-testing/\#2-prepare-test-data)
Now we have to prepare our test/mock data. There are several ways you can approach this, but the easiest
one would be to start your application with a custom `test_pb_data` directory, e.g.:
`./pocketbase serve --dir="./test_pb_data" --automigrate=0`
Go to your browser and create the test data via the Dashboard (both collections and records). Once
completed you can stop the server (you could also commit `test_pb_data` to your repo).
### [3\. Integration test](https://pocketbase.io/docs/go-testing/\#3-integration-test)
To test the example endpoint, we want to:
- ensure it handles only GET requests
- ensure that it can be accessed only by superusers
- check if the response body is properly set
Below is a simple integration test for the above test cases. We'll also use the test data created in the
previous step.
`// main_test.go
package main
import (
"net/http"
"testing"
"github.com/pocketbase/pocketbase/core"
"github.com/pocketbase/pocketbase/tests"
)
const testDataDir = "./test_pb_data"
func generateToken(collectionNameOrId string, email string) (string, error) {
app, err := tests.NewTestApp(testDataDir)
if err != nil {
return "", err
}
defer app.Cleanup()
record, err := app.FindAuthRecordByEmail(collectionNameOrId, email)
if err != nil {
return "", err
}
return record.NewAuthToken()
}
func TestHelloEndpoint(t *testing.T) {
recordToken, err := generateToken("users", "test@example.com")
if err != nil {
t.Fatal(err)
}
superuserToken, err := generateToken(core.CollectionNameSuperusers, "test@example.com")
if err != nil {
t.Fatal(err)
}
// setup the test ApiScenario app instance
setupTestApp := func(t testing.TB) *tests.TestApp {
testApp, err := tests.NewTestApp(testDataDir)
if err != nil {
t.Fatal(err)
}
// no need to cleanup since scenario.Test() will do that for us
// defer testApp.Cleanup()
bindAppHooks(testApp)
return testApp
}
scenarios := []tests.ApiScenario{
{
Name: "try with different http method, e.g. POST",
Method: http.MethodPost,
URL: "/my/hello",
ExpectedStatus: 405,
ExpectedContent: []string{"\"data\":{}"},
TestAppFactory: setupTestApp,
},
{
Name: "try as guest (aka. no Authorization header)",
Method: http.MethodGet,
URL: "/my/hello",
ExpectedStatus: 401,
ExpectedContent: []string{"\"data\":{}"},
TestAppFactory: setupTestApp,
},
{
Name: "try as authenticated app user",
Method: http.MethodGet,
URL: "/my/hello",
Headers: map[string]string{
"Authorization": recordToken,
},
ExpectedStatus: 401,
ExpectedContent: []string{"\"data\":{}"},
TestAppFactory: setupTestApp,
},
{
Name: "try as authenticated superuser",
Method: http.MethodGet,
URL: "/my/hello",
Headers: map[string]string{
"Authorization": superuserToken,
},
ExpectedStatus: 200,
ExpectedContent: []string{"Hello world!"},
TestAppFactory: setupTestApp,
},
}
for _, scenario := range scenarios {
scenario.Test(t)
}
}`
* * *
[Prev: Logging](https://pocketbase.io/docs/go-logging) [Next: Miscellaneous](https://pocketbase.io/docs/go-miscellaneous)
## PocketBase API Crons
API Crons
**[List cron jobs](https://pocketbase.io/docs/api-crons/#list-cron-jobs)**
Returns list with all registered app level cron jobs.
Only superusers can perform this action.
JavaScript
Dart
`import PocketBase from 'pocketbase';
const pb = new PocketBase('http://127.0.0.1:8090');
...
await pb.collection("_superusers").authWithPassword('test@example.com', '1234567890');
const jobs = await pb.crons.getFullList();`
`import 'package:pocketbase/pocketbase.dart';
final pb = PocketBase('http://127.0.0.1:8090');
...
await pb.collection("_superusers").authWithPassword('test@example.com', '1234567890');
final jobs = await pb.crons.getFullList();`
###### API details
**GET**
/api/crons
Query parameters
| Param | Type | Description |
| --- | --- | --- |
| fields | String | Comma separated string of the fields to return in the JSON response _(by default returns all fields)_. Ex.:
`?fields=*,expand.relField.name` `*` targets all keys from the specific depth level. In addition, the following field modifiers are also supported: - `:excerpt(maxLength, withEllipsis?)`
Returns a short plain text version of the field string value.
Ex.: `?fields=*,description:excerpt(200,true)` |
Responses
200 400 401 403
`[\
{\
"id": "__pbDBOptimize__",\
"expression": "0 0 * * *"\
},\
{\
"id": "__pbMFACleanup__",\
"expression": "0 * * * *"\
},\
{\
"id": "__pbOTPCleanup__",\
"expression": "0 * * * *"\
},\
{\
"id": "__pbLogsCleanup__",\
"expression": "0 */6 * * *"\
}\
]`
`{
"status": 400,
"message": "Failed to load backups filesystem.",
"data": {}
}`
`{
"status": 401,
"message": "The request requires valid record authorization token.",
"data": {}
}`
`{
"status": 403,
"message": "Only superusers can perform this action.",
"data": {}
}`
**[Run cron job](https://pocketbase.io/docs/api-crons/#run-cron-job)**
Triggers a single cron job by its id.
Only superusers can perform this action.
JavaScript
Dart
`import PocketBase from 'pocketbase';
const pb = new PocketBase('http://127.0.0.1:8090');
...
await pb.collection("_superusers").authWithPassword('test@example.com', '1234567890');
await pb.crons.run('__pbLogsCleanup__');`
`import 'package:pocketbase/pocketbase.dart';
final pb = PocketBase('http://127.0.0.1:8090');
...
await pb.collection("_superusers").authWithPassword('test@example.com', '1234567890');
await pb.crons.run('__pbLogsCleanup__');`
###### API details
**POST**
/api/crons/ `jobId`
Requires `Authorization:TOKEN`
Path parameters
| Param | Type | Description |
| --- | --- | --- |
| jobId | String | The identifier of the cron job to run. |
Responses
204 401 403 404
`null`
`{
"status": 401,
"message": "The request requires valid record authorization token.",
"data": {}
}`
`{
"status": 403,
"message": "The authorized record is not allowed to perform this action.",
"data": {}
}`
`{
"status": 404,
"message": "Missing or invalid cron job.",
"data": {}
}`
* * *
[Prev: API Logs](https://pocketbase.io/docs/api-logs) [Next: API Backups](https://pocketbase.io/docs/api-backups)
## PocketBase Miscellaneous
Miscellaneous
- [app.Store()](https://pocketbase.io/docs/go-miscellaneous/#app-store)
- [Security helpers](https://pocketbase.io/docs/go-miscellaneous/#security-helpers)
- [Generating random strings](https://pocketbase.io/docs/go-miscellaneous/#generating-random-strings)
- [Compare strings with constant time](https://pocketbase.io/docs/go-miscellaneous/#compare-strings-with-constant-time)
- [AES Encrypt/Decrypt](https://pocketbase.io/docs/go-miscellaneous/#aes-encryptdecrypt)
### [app.Store()](https://pocketbase.io/docs/go-miscellaneous/\#app-store)
[`app.Store()`](https://pkg.go.dev/github.com/pocketbase/pocketbase/core#BaseApp.Store)
returns a concurrent-safe application memory store that you can use to store anything for the duration of the
application process (e.g. cache, config flags, etc.).
You can find more details about the available store methods in the
[`store.Store`](https://pkg.go.dev/github.com/pocketbase/pocketbase/tools/store#Store)
documentation but the most commonly used ones are `Get(key)`, `Set(key, value)` and
`GetOrSet(key, setFunc)`.
`app.Store().Set("example", 123)
v1 := app.Store().Get("example").(int) // 123
v2 := app.Store().GetOrSet("example2", func() any {
// this setter is invoked only once unless "example2" is removed
// (e.g. suitable for instantiating singletons)
return 456
}).(int) // 456`
Keep in mind that the application store is also used internally usually with `pb*`
prefixed keys (e.g. the collections cache is stored under the `pbAppCachedCollections`
key) and changing these system keys or calling `RemoveAll()`/ `Reset()` could
have unintended side-effects.
If you want more advanced control you can initialize your own store independent from the
application instance via
`store.New[K, T](nil)`.
### [Security helpers](https://pocketbase.io/docs/go-miscellaneous/\#security-helpers)
_Below are listed some of the most commonly used security helpers but you can find detailed_
_documentation for all available methods in the_
_[`security`](https://pkg.go.dev/github.com/pocketbase/pocketbase/tools/security)_
_subpackage._
##### [Generating random strings](https://pocketbase.io/docs/go-miscellaneous/\#generating-random-strings)
`secret := security.RandomString(10) // e.g. a35Vdb10Z4
secret := security.RandomStringWithAlphabet(5, "1234567890") // e.g. 33215`
##### [Compare strings with constant time](https://pocketbase.io/docs/go-miscellaneous/\#compare-strings-with-constant-time)
`isEqual := security.Equal(hash1, hash2)`
##### [AES Encrypt/Decrypt](https://pocketbase.io/docs/go-miscellaneous/\#aes-encryptdecrypt)
`// must be random 32 characters string
const key = "C7aoNPItI2SATN2SSt7M0AATM8FWLqg5"
encrypted, err := security.Encrypt([]byte("test"), key)
if err != nil {
return err
}
decrypted := security.Decrypt(encrypted, key) // []byte("test")`
* * *
[Prev: Testing](https://pocketbase.io/docs/go-testing) [Next: Record proxy](https://pocketbase.io/docs/go-record-proxy)
## PocketBase Realtime Messaging
Realtime messaging
By default PocketBase sends realtime events only for Record create/update/delete operations ( _and for the OAuth2 auth redirect_), but you are free to send custom realtime messages to the connected clients via the
[`app.SubscriptionsBroker()`](https://pkg.go.dev/github.com/pocketbase/pocketbase/core#BaseApp.SubscriptionsBroker) instance.
[`app.SubscriptionsBroker().Clients()`](https://pkg.go.dev/github.com/pocketbase/pocketbase/tools/subscriptions#Broker.Clients)
returns all connected
[`subscriptions.Client`](https://pkg.go.dev/github.com/pocketbase/pocketbase/tools/subscriptions#Client)
indexed by their unique connection id.
[`app.SubscriptionsBroker().ChunkedClients(size)`](https://pkg.go.dev/github.com/pocketbase/pocketbase/tools/subscriptions#Broker.ChunkedClients)
is similar but return the result as a chunked slice allowing you to split the iteration across several goroutines
(usually combined with
[`errgroup`](https://pkg.go.dev/golang.org/x/sync/errgroup)
).
The current auth record associated with a client could be accessed through
`client.Get(apis.RealtimeClientAuthKey)`
Note that a single authenticated user could have more than one active realtime connection (aka.
multiple clients). This could happen for example when opening the same app in different tabs,
browsers, devices, etc.
Below you can find a minimal code sample that sends a JSON payload to all clients subscribed to the
"example" topic:
`func notify(app core.App, subscription string, data any) error {
rawData, err := json.Marshal(data)
if err != nil {
return err
}
message := subscriptions.Message{
Name: subscription,
Data: rawData,
}
group := new(errgroup.Group)
chunks := app.SubscriptionsBroker().ChunkedClients(300)
for _, chunk := range chunks {
group.Go(func() error {
for _, client := range chunk {
if !client.HasSubscription(subscription) {
continue
}
client.Send(message)
}
return nil
})
}
return group.Wait()
}
err := notify(app, "example", map[string]any{"test": 123})
if err != nil {
return err
}`
From the client-side, users can listen to the custom subscription topic by doing something like:
JavaScript
Dart
`import PocketBase from 'pocketbase';
const pb = new PocketBase('http://127.0.0.1:8090');
...
await pb.realtime.subscribe('example', (e) => {
console.log(e)
})`
`import 'package:pocketbase/pocketbase.dart';
final pb = PocketBase('http://127.0.0.1:8090');
...
await pb.realtime.subscribe('example', (e) {
print(e)
})`
* * *
[Prev: Console commands](https://pocketbase.io/docs/go-console-commands) [Next: Filesystem](https://pocketbase.io/docs/go-filesystem)
## API Health Check
API Health
**[Health check](https://pocketbase.io/docs/api-health/#health-check)**
Returns the health status of the server.
###### API details
**GET/HEAD**
/api/health
Query parameters
| Param | Type | Description |
| --- | --- | --- |
| fields | String | Comma separated string of the fields to return in the JSON response _(by default returns all fields)_. Ex.:
`?fields=*,expand.relField.name` `*` targets all keys from the specific depth level. In addition, the following field modifiers are also supported: - `:excerpt(maxLength, withEllipsis?)`
Returns a short plain text version of the field string value.
Ex.: `?fields=*,description:excerpt(200,true)` |
Responses
200
`{
"status": 200,
"message": "API is healthy.",
"data": {
"canBackup": false
}
}`
* * *
[Prev: API Backups](https://pocketbase.io/docs/api-backups)
## Using PocketBase APIs
How to use PocketBase
The easiest way to use PocketBase is by interacting with its Web APIs directly from the client-side (e.g.
mobile app or browser SPA).
It was designed with this exact use case in mind and it is also the reason why there are general purpose
JSON APIs for listing, pagination, sorting, filtering, etc.
The access and filter controls for your data is usually done through the
[collection API rules](https://pocketbase.io/docs/api-rules-and-filters/)
.
For the cases when you need more specialized handling (sending emails, intercepting the default actions,
creating new routes, etc.) you can
[extend PocketBase with Go or JavaScript](https://pocketbase.io/docs/use-as-framework/)
.
For interacting with the
[Web APIs](https://pocketbase.io/docs/api-records/)
you can make use of the official SDK clients:
- [JavaScript SDK](https://github.com/pocketbase/js-sdk)(Browser, Node.js, React Native)
- [Dart SDK](https://github.com/pocketbase/dart-sdk)(Web, Mobile, Desktop, CLI)
When used on the client-side, it is safe to have a single/global SDK instance for the entire lifecycle of
your application.
**[Web apps recommendation](https://pocketbase.io/docs/how-to-use/#web-apps-recommendation)**
Not everyone will agree with this, but if you are building a web app with PocketBase I recommend
developing the frontend as a **traditional client-side SPA** and for the cases where additional
server-side handling is needed (e.g. for payment webhooks, extra data server validations, etc.) you
could try to:
- [Use PocketBase as Go/JS framework](https://pocketbase.io/docs/use-as-framework) to create new routes or
intercept existing.
- Create one-off Node.js/Bun/Deno/etc. server-side actions that will interact with
PocketBase only as superuser and as pure data store (similar to traditional database
interactions but over HTTP). In this case it is safe to have a global superuser client
such as:
`// src/superuser.js
import PocketBase from "pocketbase"
const superuserClient = new PocketBase('https://example.com');
// disable autocancellation so that we can handle async requests from multiple users
superuserClient.autoCancellation(false);
// option 1: authenticate as superuser using email/password (could be filled with ENV params)
await superuserClient.collection('_superusers').authWithPassword(SUPERUSER_EMAIL, SUPERUSER_PASS, {
// This will trigger auto refresh or auto reauthentication in case
// the token has expired or is going to expire in the next 30 minutes.
autoRefreshThreshold: 30 * 60
})
// option 2: OR authenticate as superuser via long-lived "API key"
// (see https://pocketbase.io/docs/authentication/#api-keys)
superuserClient.authStore.save('YOUR_GENERATED_SUPERUSER_TOKEN')
export default superuserClient;`
Then you can import directly the file in your server-side actions and use the client as
usual:
`import superuserClient from './src/superuser.js'
async function serverAction(req, resp) {
... do some extra data validations or handling ...
// send a create request as superuser
await superuserClient.collection('example').create({ ... })
}`
**[Why not JS SSR](https://pocketbase.io/docs/how-to-use/#why-not-js-ssr)**
Using PocketBase with meta framework such as SvelteKit, Nuxt, Next.js, etc.
**in a JS SSR mode**
is possible but it comes with many complications and you need to carefully evaluate whether the cost
of having another backend (PocketBase) along-side your existing one (the Node.js server) is worth it.
You can read more about the potential problems in
[JS SSR - issues and recommendations #5313](https://github.com/pocketbase/pocketbase/discussions/5313) but some of the common pitfalls are:
- Security issues caused by incorrectly initialized and shared JS SDK instance in a long-running
server-side context.
- OAuth2 integration difficulties related to the server-side only OAuth2 flow (or its mixed
"all-in-one" client-side handling and sharing a cookie with the server-side).
- Proxying realtime connections and essentially duplicating the same thing PocketBase already
does.
- Performance bottlenecks caused by the default single-threaded Node.js process and the
excessive resources utilization due to the server-side rendering and heavy back-and-forth
requests communication between the different layers (client<->Node.js<->PocketBase).
This doesn't mean that using PocketBase with JS SSR is always a "bad thing" but based on the
dozens reported issues so far I would recommend it only after careful evaluation and only to more
experienced developers that have in-depth understanding of the used tools and their trade-offs. If
you still want to use PocketBase to handle regular users authentication with a JS SSR meta
framework, then you can find some JS SDK examples in the repo's
[JS SSR integration section](https://github.com/pocketbase/js-sdk#ssr-integration).
**[Why not htmx, Hotwire/Turbo, Unpoly, etc.](https://pocketbase.io/docs/how-to-use/#why-not-htmx-hotwireturbo-unpoly-etc-)**
htmx, Hotwire/Turbo, Unpoly and other similar tools are commonly used for building server rendered
applications but unfortunately they don't play well with the JSON APIs and fully stateless nature
of PocketBase.
It is possible to use them with PocketBase but at the moment I don't recommend it because we lack
the necessary helpers and utilities for building SSR-first applications, which means that you
might have to create from scratch a lot of things on your own such as middlewares for handling
cookies ( _and eventually taking care also for CORS and CSRF_) or custom authentication
endpoints and access controls ( _the collection API rules apply only for the builtin JSON routes_).
In the future we could eventually provide official SSR support in terms of guides and middlewares
for this use case but again - PocketBase wasn't designed with this in mind and you may want to
reevaluate the tech stack of your application and switch to a traditional client-side SPA as
mentioned earlier or use a different backend solution that might fit better with your use case.
**[Mobile apps auth persistence](https://pocketbase.io/docs/how-to-use/#mobile-apps-auth-persistence)**
When building mobile apps with the JavaScript SDK or Dart SDK you'll have to specify a custom
persistence store if you want to preserve the authentication between the various app activities
and open/close state.
The SDKs comes with a helper async storage implementation that allows you to hook any custom
persistent layer (local file, SharedPreferences, key-value based database, etc.). Here is a
minimal PocketBase SDKs initialization for React Native (JavaScript) and Flutter (Dart):
JavaScript
Dart
`// Node.js and React Native doesn't have native EventSource implementation
// so in order to use the realtime subscriptions you'll need to load EventSource polyfill,
// for example: npm install react-native-sse --save
import eventsource from 'react-native-sse';
import AsyncStorage from '@react-native-async-storage/async-storage';
import PocketBase, { AsyncAuthStore } from 'pocketbase';
// load the polyfill
global.EventSource = eventsource;
// initialize the async store
const store = new AsyncAuthStore({
save: async (serialized) => AsyncStorage.setItem('pb_auth', serialized),
initial: AsyncStorage.getItem('pb_auth'),
});
// initialize the PocketBase client
// (it is OK to have a single/global instance for the duration of your application)
const pb = new PocketBase('http://127.0.0.1:8090', store);
...
await pb.collection('users').authWithPassword('test@example.com', '1234567890');
console.log(pb.authStore.record)`
`import 'package:pocketbase/pocketbase.dart';
import 'package:shared_preferences/shared_preferences.dart';
// for simplicity we are using a simple SharedPreferences instance
// but you can also replace it with its safer EncryptedSharedPreferences alternative
final prefs = await SharedPreferences.getInstance();
// initialize the async store
final store = AsyncAuthStore(
save: (String data) async => prefs.setString('pb_auth', data),
initial: prefs.getString('pb_auth'),
);
// initialize the PocketBase client
// (it is OK to have a single/global instance for the duration of your application)
final pb = PocketBase('http://127.0.0.1:8090', authStore: store);
...
await pb.collection('users').authWithPassword('test@example.com', '1234567890');
print(pb.authStore.record);`
**[React Native file upload on Android and iOS](https://pocketbase.io/docs/how-to-use/#react-native-file-upload-on-android-and-ios)**
At the time of writing, React Native on Android and iOS seems to have a non-standard
`FormData` implementation and for uploading files on these platforms it requires the following
special object syntax:
`{
uri: "...",
type: "...",
name: "..."
}`
Or in other words, you may have to apply a conditional handling similar to:
`const data = new FormData();
// result is the resolved promise of ImagePicker.launchImageLibraryAsync
let imageUri = result.assets[0].uri;
if (Platform.OS === 'web') {
const req = await fetch(imageUri);
const blob = await req.blob();
data.append('avatar', blob); // regular File/Blob value
} else {
// the below object format works only on Android and iOS
// (FormData.set() also doesn't seem to be supported so we use FormData.append())
data.append('avatar', {
uri: imageUri,
type: 'image/*',
name: imageUri.split('/').pop(),
});
}
...
await pb.collection('example').create(data)`
The next couple pages have a little bit more information about the basic PocketBase components like
collections, records, authentication, relations, files handling, etc.
* * *
[Prev: Introduction](https://pocketbase.io/docs) [Next: Collections](https://pocketbase.io/docs/collections)
## PocketBase Event Hooks
Event hooks
You can extend the default PocketBase behavior with custom server-side code using the exposed JavaScript
app event hooks.
Throwing an error or not calling `e.next()` inside a handler function stops the hook execution chain.
All hook handler functions share the same `function(e){}` signature and expect the
user to call `e.next()` if they want to proceed with the execution chain.
- [App hooks](https://pocketbase.io/docs/js-event-hooks/#app-hooks)
- [Mailer hooks](https://pocketbase.io/docs/js-event-hooks/#mailer-hooks)
- [Realtime hooks](https://pocketbase.io/docs/js-event-hooks/#realtime-hooks)
- [Record model hooks](https://pocketbase.io/docs/js-event-hooks/#record-model-hooks)
- [Collection model hooks](https://pocketbase.io/docs/js-event-hooks/#collection-model-hooks)
- [Request hooks](https://pocketbase.io/docs/js-event-hooks/#request-hooks)
- [Base model hooks](https://pocketbase.io/docs/js-event-hooks/#base-model-hooks)
### [App hooks](https://pocketbase.io/docs/js-event-hooks/\#app-hooks)
**[onBootstrap](https://pocketbase.io/docs/js-event-hooks/#onbootstrap)**
`onBootstrap` hook is triggered when initializing the main
application resources (db, app settings, etc).
Note that attempting to access the database before the `e.next()` call will result in an error.
`onBootstrap((e) => {
e.next()
// e.app
})`
**[onSettingsReload](https://pocketbase.io/docs/js-event-hooks/#onsettingsreload)**
`onSettingsReload` hook is triggered every time when the `$app.settings()`
is being replaced with a new state.
Calling `e.app.settings()` after `e.next()` returns the new state.
`onSettingsReload((e) => {
e.next()
// e.app.settings()
})`
**[onBackupCreate](https://pocketbase.io/docs/js-event-hooks/#onbackupcreate)**
`onBackupCreate` is triggered on each `$app.createBackup` call.
`onBackupCreate((e) => {
// e.app
// e.name - the name of the backup to create
// e.exclude - list of pb_data dir entries to exclude from the backup
})`
**[onBackupRestore](https://pocketbase.io/docs/js-event-hooks/#onbackuprestore)**
`onBackupRestore` is triggered before app backup restore (aka. on `$app.restoreBackup` call).
`onBackupRestore((e) => {
// e.app
// e.name - the name of the backup to restore
// e.exclude - list of dir entries to exclude from the backup
})`
**[onTerminate](https://pocketbase.io/docs/js-event-hooks/#onterminate)**
`onTerminate` hook is triggered when the app is in the process
of being terminated (ex. on `SIGTERM` signal).
Note that the app could be terminated abruptly without awaiting the hook completion.
`onTerminate((e) => {
// e.app
// e.isRestart
})`
### [Mailer hooks](https://pocketbase.io/docs/js-event-hooks/\#mailer-hooks)
**[onMailerSend](https://pocketbase.io/docs/js-event-hooks/#onmailersend)**
`onMailerSend` hook is triggered every time when a new email is
being send using the `$app.newMailClient()` instance.
It allows intercepting the email message or to use a custom mailer client.
`onMailerSend((e) => {
// e.app
// e.mailer
// e.message
// ex. change the mail subject
e.message.subject = "new subject"
e.next()
})`
**[onMailerRecordAuthAlertSend](https://pocketbase.io/docs/js-event-hooks/#onmailerrecordauthalertsend)**
`onMailerRecordAuthAlertSend` hook is triggered when
sending a new device login auth alert email, allowing you to
intercept and customize the email message that is being sent.
`onMailerRecordAuthAlertSend((e) => {
// e.app
// e.mailer
// e.message
// e.record
// e.meta
// ex. change the mail subject
e.message.subject = "new subject"
e.next()
})`
**[onMailerRecordPasswordResetSend](https://pocketbase.io/docs/js-event-hooks/#onmailerrecordpasswordresetsend)**
`onMailerRecordPasswordResetSend` hook is triggered when
sending a password reset email to an auth record, allowing
you to intercept and customize the email message that is being sent.
`onMailerRecordPasswordResetSend((e) => {
// e.app
// e.mailer
// e.message
// e.record
// e.meta
// ex. change the mail subject
e.message.subject = "new subject"
e.next()
})`
**[onMailerRecordVerificationSend](https://pocketbase.io/docs/js-event-hooks/#onmailerrecordverificationsend)**
`onMailerRecordVerificationSend` hook is triggered when
sending a verification email to an auth record, allowing
you to intercept and customize the email message that is being sent.
`onMailerRecordVerificationSend((e) => {
// e.app
// e.mailer
// e.message
// e.record
// e.meta
// ex. change the mail subject
e.message.subject = "new subject"
e.next()
})`
**[onMailerRecordEmailChangeSend](https://pocketbase.io/docs/js-event-hooks/#onmailerrecordemailchangesend)**
`onMailerRecordEmailChangeSend` hook is triggered when sending a
confirmation new address email to an auth record, allowing
you to intercept and customize the email message that is being sent.
`onMailerRecordEmailChangeSend((e) => {
// e.app
// e.mailer
// e.message
// e.record
// e.meta
// ex. change the mail subject
e.message.subject = "new subject"
e.next()
})`
**[onMailerRecordOTPSend](https://pocketbase.io/docs/js-event-hooks/#onmailerrecordotpsend)**
`onMailerRecordOTPSend` hook is triggered when sending an OTP email
to an auth record, allowing you to intercept and customize the
email message that is being sent.
`onMailerRecordOTPSend((e) => {
// e.app
// e.mailer
// e.message
// e.record
// e.meta
// ex. change the mail subject
e.message.subject = "new subject"
e.next()
})`
### [Realtime hooks](https://pocketbase.io/docs/js-event-hooks/\#realtime-hooks)
**[onRealtimeConnectRequest](https://pocketbase.io/docs/js-event-hooks/#onrealtimeconnectrequest)**
`onRealtimeConnectRequest` hook is triggered when establishing the SSE client connection.
Any execution after e.next() of a hook handler happens after the client disconnects.
`onRealtimeConnectRequest((e) => {
// e.app
// e.client
// e.idleTimeout
// and all RequestEvent fields...
e.next()
})`
**[onRealtimeSubscribeRequest](https://pocketbase.io/docs/js-event-hooks/#onrealtimesubscriberequest)**
`onRealtimeSubscribeRequest` hook is triggered when updating the
client subscriptions, allowing you to further validate and
modify the submitted change.
`OnRealtimeSubscribeRequest((e) => {
// e.app
// e.client
// e.subscriptions
// and all RequestEvent fields...
e.next()
})`
**[onRealtimeMessageSend](https://pocketbase.io/docs/js-event-hooks/#onrealtimemessagesend)**
`onRealtimeMessageSend` hook is triggered when sending an SSE message to a client.
`onRealtimeMessageSend((e) => {
// e.app
// e.client
// e.message
// and all original connect RequestEvent fields...
e.next()
})`
### [Record model hooks](https://pocketbase.io/docs/js-event-hooks/\#record-model-hooks)
These are lower level Record model hooks and could be triggered from anywhere (custom console command, scheduled cron job, when calling `e.save(record)`, etc.) and therefore they have no access to the request context!
If you want to intercept the builtin Web APIs and to access their request body, query parameters, headers or the request auth state, then please use the designated
[Record `*Request` hooks](https://pocketbase.io/docs/js-event-hooks/#request-hooks)
.
**[onRecordEnrich](https://pocketbase.io/docs/js-event-hooks/#onrecordenrich)**
`onRecordEnrich` is triggered every time when a record is enriched
\- as part of the builtin Record responses, during realtime message serialization, or when `apis.enrichRecord` is invoked.
It could be used for example to redact/hide or add computed temporary
Record model props only for the specific request info.
`onRecordEnrich((e) => {
// hide one or more fields
e.record.hide("role")
// add new custom field for registered users
if (e.requestInfo.auth?.collection()?.name == "users") {
e.record.withCustomData(true) // for security custom props require to be enabled explicitly
e.record.set("computedScore", e.record.get("score") * e.requestInfo.auth.get("base"))
}
e.next()
}, "posts")`
**[onRecordValidate](https://pocketbase.io/docs/js-event-hooks/#onrecordvalidate)**
`onRecordValidate` is a Record proxy model hook of `onModelValidate`.
`onRecordValidate` is called every time when a Record is being validated,
e.g. triggered by `$app.validate()` or `$app.save()`.
`// fires for every record
onRecordValidate((e) => {
// e.app
// e.record
e.next()
})
// fires only for "users" and "articles" records
onRecordValidate((e) => {
// e.app
// e.record
e.next()
}, "users", "articles")`
###### [Record model create hooks](https://pocketbase.io/docs/js-event-hooks/\#record-model-create-hooks)
**[onRecordCreate](https://pocketbase.io/docs/js-event-hooks/#onrecordcreate)**
`onRecordCreate` is a Record proxy model hook of `onModelCreate`.
`onRecordCreate` is triggered every time when a new Record is being created,
e.g. triggered by `$app.save()`.
Operations BEFORE the `e.next()` execute before the Record validation
and the INSERT DB statement.
Operations AFTER the `e.next()` execute after the Record validation
and the INSERT DB statement.
Note that successful execution doesn't guarantee that the Record
is persisted in the database since its wrapping transaction may
not have been committed yet.
If you want to listen to only the actual persisted events, you can
bind to `onRecordAfterCreateSuccess` or `onRecordAfterCreateError` hooks.
`// fires for every record
onRecordCreate((e) => {
// e.app
// e.record
e.next()
})
// fires only for "users" and "articles" records
onRecordCreate((e) => {
// e.app
// e.record
e.next()
}, "users", "articles")`
**[onRecordCreateExecute](https://pocketbase.io/docs/js-event-hooks/#onrecordcreateexecute)**
`onRecordCreateExecute` is a Record proxy model hook of `onModelCreateExecute`.
`onRecordCreateExecute` is triggered after successful Record validation
and right before the model INSERT DB statement execution.
Usually it is triggered as part of the `$app.save()` in the following firing order:
`onRecordCreate`
-\> `onRecordValidate` (skipped with `$app.saveNoValidate()`)
-\> `onRecordCreateExecute`
Note that successful execution doesn't guarantee that the Record
is persisted in the database since its wrapping transaction may
not have been committed yet.
If you want to listen to only the actual persisted events, you can
bind to `onRecordAfterCreateSuccess` or `onRecordAfterCreateError` hooks.
`// fires for every record
onRecordCreateExecute((e) => {
// e.app
// e.record
e.next()
})
// fires only for "users" and "articles" records
onRecordCreateExecute((e) => {
// e.app
// e.record
e.next()
}, "users", "articles")`
**[onRecordAfterCreateSuccess](https://pocketbase.io/docs/js-event-hooks/#onrecordaftercreatesuccess)**
`onRecordAfterCreateSuccess` is a Record proxy model hook of `onModelAfterCreateSuccess`.
`onRecordAfterCreateSuccess` is triggered after each successful
Record DB create persistence.
Note that when a Record is persisted as part of a transaction,
this hook is delayed and executed only AFTER the transaction has been committed.
This hook is NOT triggered in case the transaction fails/rollbacks.
`// fires for every record
onRecordAfterCreateSuccess((e) => {
// e.app
// e.record
e.next()
})
// fires only for "users" and "articles" records
onRecordAfterCreateSuccess((e) => {
// e.app
// e.record
e.next()
}, "users", "articles")`
**[onRecordAfterCreateError](https://pocketbase.io/docs/js-event-hooks/#onrecordaftercreateerror)**
`onRecordAfterCreateError` is a Record proxy model hook of `onModelAfterCreateError`.
`onRecordAfterCreateError` is triggered after each failed
Record DB create persistence.
Note that the execution of this hook is either immediate or delayed
depending on the error:
- **immediate** on `$app.save()` failure
- **delayed** on transaction rollback
`// fires for every record
onRecordAfterCreateError((e) => {
// e.app
// e.record
// e.error
e.next()
})
// fires only for "users" and "articles" records
onRecordAfterCreateError((e) => {
// e.app
// e.record
// e.error
e.next()
}, "users", "articles")`
###### [Record model update hooks](https://pocketbase.io/docs/js-event-hooks/\#record-model-update-hooks)
**[onRecordUpdate](https://pocketbase.io/docs/js-event-hooks/#onrecordupdate)**
`onRecordUpdate` is a Record proxy model hook of `onModelUpdate`.
`onRecordUpdate` is triggered every time when a new Record is being updated,
e.g. triggered by `$app.save()`.
Operations BEFORE the `e.next()` execute before the Record validation
and the UPDATE DB statement.
Operations AFTER the `e.next()` execute after the Record validation
and the UPDATE DB statement.
Note that successful execution doesn't guarantee that the Record
is persisted in the database since its wrapping transaction may
not have been committed yet.
If you want to listen to only the actual persisted events, you can
bind to `onRecordAfterUpdateSuccess` or `onRecordAfterUpdateError` hooks.
`// fires for every record
onRecordUpdate((e) => {
// e.app
// e.record
e.next()
})
// fires only for "users" and "articles" records
onRecordUpdate((e) => {
// e.app
// e.record
e.next()
}, "users", "articles")`
**[onRecordUpdateExecute](https://pocketbase.io/docs/js-event-hooks/#onrecordupdateexecute)**
`onRecordUpdateExecute` is a Record proxy model hook of `onModelUpdateExecute`.
`onRecordUpdateExecute` is triggered after successful Record validation
and right before the model UPDATE DB statement execution.
Usually it is triggered as part of the `$app.save()` in the following firing order:
`onRecordUpdate`
-\> `onRecordValidate` (skipped with `$app.saveNoValidate()`)
-\> `onRecordUpdateExecute`
Note that successful execution doesn't guarantee that the Record
is persisted in the database since its wrapping transaction may
not have been committed yet.
If you want to listen to only the actual persisted events, you can
bind to `onRecordAfterUpdateSuccess` or `onRecordAfterUpdateError` hooks.
`// fires for every record
onRecordUpdateExecute((e) => {
// e.app
// e.record
e.next()
})
// fires only for "users" and "articles" records
onRecordUpdateExecute((e) => {
// e.app
// e.record
e.next()
}, "users", "articles")`
**[onRecordAfterUpdateSuccess](https://pocketbase.io/docs/js-event-hooks/#onrecordafterupdatesuccess)**
`onRecordAfterUpdateSuccess` is a Record proxy model hook of `onModelAfterUpdateSuccess`.
`onRecordAfterUpdateSuccess` is triggered after each successful
Record DB update persistence.
Note that when a Record is persisted as part of a transaction,
this hook is delayed and executed only AFTER the transaction has been committed.
This hook is NOT triggered in case the transaction fails/rollbacks.
`// fires for every record
onRecordAfterUpdateSuccess((e) => {
// e.app
// e.record
e.next()
})
// fires only for "users" and "articles" records
onRecordAfterUpdateSuccess((e) => {
// e.app
// e.record
e.next()
}, "users", "articles")`
**[onRecordAfterUpdateError](https://pocketbase.io/docs/js-event-hooks/#onrecordafterupdateerror)**
`onRecordAfterUpdateError` is a Record proxy model hook of `onModelAfterUpdateError`.
`onRecordAfterUpdateError` is triggered after each failed
Record DB update persistence.
Note that the execution of this hook is either immediate or delayed
depending on the error:
- **immediate** on `$app.save()` failure
- **delayed** on transaction rollback
`// fires for every record
onRecordAfterUpdateError((e) => {
// e.app
// e.record
// e.error
e.next()
})
// fires only for "users" and "articles" records
onRecordAfterUpdateError((e) => {
// e.app
// e.record
// e.error
e.next()
}, "users", "articles")`
###### [Record model delete hooks](https://pocketbase.io/docs/js-event-hooks/\#record-model-delete-hooks)
**[onRecordDelete](https://pocketbase.io/docs/js-event-hooks/#onrecorddelete)**
`onRecordDelete` is a Record proxy model hook of `onModelDelete`.
`onRecordDelete` is triggered every time when a new Record is being deleted,
e.g. triggered by `$app.delete()`.
Operations BEFORE the `e.next()` execute before the Record validation
and the UPDATE DB statement.
Operations AFTER the `e.next()` execute after the Record validation
and the UPDATE DB statement.
Note that successful execution doesn't guarantee that the Record
is deleted from the database since its wrapping transaction may
not have been committed yet.
If you want to listen to only the actual persisted deleted events, you can
bind to `onRecordAfterDeleteSuccess` or `onRecordAfterDeleteError` hooks.
`// fires for every record
onRecordDelete((e) => {
// e.app
// e.record
e.next()
})
// fires only for "users" and "articles" records
onRecordDelete((e) => {
// e.app
// e.record
e.next()
}, "users", "articles")`
**[onRecordDeleteExecute](https://pocketbase.io/docs/js-event-hooks/#onrecorddeleteexecute)**
`onRecordDeleteExecute` is a Record proxy model hook of `onModelDeleteExecute`.
`onRecordDeleteExecute` is triggered after the internal delete checks and
right before the Record the model DELETE DB statement execution.
Usually it is triggered as part of the `$app.delete()` in the following firing order:
`onRecordDelete`
-\>
internal delete checks
-\> `onRecordDeleteExecute`
Note that successful execution doesn't guarantee that the Record
is deleted from the database since its wrapping transaction may
not have been committed yet.
If you want to listen to only the actual persisted events, you can
bind to `onRecordAfterDeleteSuccess` or `onRecordAfterDeleteError` hooks.
`// fires for every record
onRecordDeleteExecute((e) => {
// e.app
// e.record
e.next()
})
// fires only for "users" and "articles" records
onRecordDeleteExecute((e) => {
// e.app
// e.record
e.next()
}, "users", "articles")`
**[onRecordAfterDeleteSuccess](https://pocketbase.io/docs/js-event-hooks/#onrecordafterdeletesuccess)**
`onRecordAfterDeleteSuccess` is a Record proxy model hook of `onModelAfterDeleteSuccess`.
`onRecordAfterDeleteSuccess` is triggered after each successful
Record DB delete persistence.
Note that when a Record is deleted as part of a transaction,
this hook is delayed and executed only AFTER the transaction has been committed.
This hook is NOT triggered in case the transaction fails/rollbacks.
`// fires for every record
onRecordAfterDeleteSuccess((e) => {
// e.app
// e.record
e.next()
})
// fires only for "users" and "articles" records
onRecordAfterDeleteSuccess((e) => {
// e.app
// e.record
e.next()
}, "users", "articles")`
**[onRecordAfterDeleteError](https://pocketbase.io/docs/js-event-hooks/#onrecordafterdeleteerror)**
`onRecordAfterDeleteError` is a Record proxy model hook of `onModelAfterDeleteError`.
`onRecordAfterDeleteError` is triggered after each failed
Record DB delete persistence.
Note that the execution of this hook is either immediate or delayed
depending on the error:
- **immediate** on `$app.delete()` failure
- **delayed** on transaction rollback
`// fires for every record
onRecordAfterDeleteError((e) => {
// e.app
// e.record
// e.error
e.next()
})
// fires only for "users" and "articles" records
onRecordAfterDeleteError((e) => {
// e.app
// e.record
// e.error
e.next()
}, "users", "articles")`
### [Collection model hooks](https://pocketbase.io/docs/js-event-hooks/\#collection-model-hooks)
These are lower level Collection model hooks and could be triggered from anywhere (custom console command, scheduled cron job, when calling `e.save(collection)`, etc.) and therefore they have no access to the request context!
If you want to intercept the builtin Web APIs and to access their request body, query parameters, headers or the request auth state, then please use the designated
[Collection `*Request` hooks](https://pocketbase.io/docs/js-event-hooks/#collection-request-hooks)
.
**[onCollectionValidate](https://pocketbase.io/docs/js-event-hooks/#oncollectionvalidate)**
`onCollectionValidate` is a Collection proxy model hook of `onModelValidate`.
`onCollectionValidate` is called every time when a Collection is being validated,
e.g. triggered by `$app.validate()` or `$app.save()`.
`// fires for every collection
onCollectionValidate((e) => {
// e.app
// e.collection
e.next()
})
// fires only for "users" and "articles" collections
onCollectionValidate((e) => {
// e.app
// e.collection
e.next()
}, "users", "articles")`
###### [Collection mode create hooks](https://pocketbase.io/docs/js-event-hooks/\#collection-mode-create-hooks)
**[onCollectionCreate](https://pocketbase.io/docs/js-event-hooks/#oncollectioncreate)**
`onCollectionCreate` is a Collection proxy model hook of `onModelCreate`.
`onCollectionCreate` is triggered every time when a new Collection is being created,
e.g. triggered by `$app.save()`.
Operations BEFORE the `e.next()` execute before the Collection validation
and the INSERT DB statement.
Operations AFTER the `e.next()` execute after the Collection validation
and the INSERT DB statement.
Note that successful execution doesn't guarantee that the Collection
is persisted in the database since its wrapping transaction may
not have been committed yet.
If you want to listen to only the actual persisted events, you can
bind to `onCollectionAfterCreateSuccess` or `onCollectionAfterCreateError` hooks.
`// fires for every collection
onCollectionCreate((e) => {
// e.app
// e.collection
e.next()
})
// fires only for "users" and "articles" collections
onCollectionCreate((e) => {
// e.app
// e.collection
e.next()
}, "users", "articles")`
**[onCollectionCreateExecute](https://pocketbase.io/docs/js-event-hooks/#oncollectioncreateexecute)**
`onCollectionCreateExecute` is a Collection proxy model hook of `onModelCreateExecute`.
`onCollectionCreateExecute` is triggered after successful Collection validation
and right before the model INSERT DB statement execution.
Usually it is triggered as part of the `$app.save()` in the following firing order:
`onCollectionCreate`
-\> `onCollectionValidate` (skipped with `$app.saveNoValidate()`)
-\> `onCollectionCreateExecute`
Note that successful execution doesn't guarantee that the Collection
is persisted in the database since its wrapping transaction may
not have been committed yet.
If you want to listen to only the actual persisted events, you can
bind to `onCollectionAfterCreateSuccess` or `onCollectionAfterCreateError` hooks.
`// fires for every collection
onCollectionCreateExecute((e) => {
// e.app
// e.collection
e.next()
})
// fires only for "users" and "articles" collections
onCollectionCreateExecute((e) => {
// e.app
// e.collection
e.next()
}, "users", "articles")`
**[onCollectionAfterCreateSuccess](https://pocketbase.io/docs/js-event-hooks/#oncollectionaftercreatesuccess)**
`onCollectionAfterCreateSuccess` is a Collection proxy model hook of `onModelAfterCreateSuccess`.
`onCollectionAfterCreateSuccess` is triggered after each successful
Collection DB create persistence.
Note that when a Collection is persisted as part of a transaction,
this hook is delayed and executed only AFTER the transaction has been committed.
This hook is NOT triggered in case the transaction fails/rollbacks.
`// fires for every collection
onCollectionAfterCreateSuccess((e) => {
// e.app
// e.collection
e.next()
})
// fires only for "users" and "articles" collections
onCollectionAfterCreateSuccess((e) => {
// e.app
// e.collection
e.next()
}, "users", "articles")`
**[onCollectionAfterCreateError](https://pocketbase.io/docs/js-event-hooks/#oncollectionaftercreateerror)**
`onCollectionAfterCreateError` is a Collection proxy model hook of `onModelAfterCreateError`.
`onCollectionAfterCreateError` is triggered after each failed
Collection DB create persistence.
Note that the execution of this hook is either immediate or delayed
depending on the error:
- **immediate** on `$app.save()` failure
- **delayed** on transaction rollback
`// fires for every collection
onCollectionAfterCreateError((e) => {
// e.app
// e.collection
// e.error
e.next()
})
// fires only for "users" and "articles" collections
onCollectionAfterCreateError((e) => {
// e.app
// e.collection
// e.error
e.next()
}, "users", "articles")`
###### [Collection mode update hooks](https://pocketbase.io/docs/js-event-hooks/\#collection-mode-update-hooks)
**[onCollectionUpdate](https://pocketbase.io/docs/js-event-hooks/#oncollectionupdate)**
`onCollectionUpdate` is a Collection proxy model hook of `onModelUpdate`.
`onCollectionUpdate` is triggered every time when a new Collection is being updated,
e.g. triggered by `$app.save()`.
Operations BEFORE the `e.next()` execute before the Collection validation
and the UPDATE DB statement.
Operations AFTER the `e.next()` execute after the Collection validation
and the UPDATE DB statement.
Note that successful execution doesn't guarantee that the Collection
is persisted in the database since its wrapping transaction may
not have been committed yet.
If you want to listen to only the actual persisted events, you can
bind to `onCollectionAfterUpdateSuccess` or `onCollectionAfterUpdateError` hooks.
`// fires for every collection
onCollectionUpdate((e) => {
// e.app
// e.collection
e.next()
})
// fires only for "users" and "articles" collections
onCollectionUpdate((e) => {
// e.app
// e.collection
e.next()
}, "users", "articles")`
**[onCollectionUpdateExecute](https://pocketbase.io/docs/js-event-hooks/#oncollectionupdateexecute)**
`onCollectionUpdateExecute` is a Collection proxy model hook of `onModelUpdateExecute`.
`onCollectionUpdateExecute` is triggered after successful Collection validation
and right before the model UPDATE DB statement execution.
Usually it is triggered as part of the `$app.save()` in the following firing order:
`onCollectionUpdate`
-\> `onCollectionValidate` (skipped with `$app.saveNoValidate()`)
-\> `onCollectionUpdateExecute`
Note that successful execution doesn't guarantee that the Collection
is persisted in the database since its wrapping transaction may
not have been committed yet.
If you want to listen to only the actual persisted events, you can
bind to `onCollectionAfterUpdateSuccess` or `onCollectionAfterUpdateError` hooks.
`// fires for every collection
onCollectionUpdateExecute((e) => {
// e.app
// e.collection
e.next()
})
// fires only for "users" and "articles" collections
onCollectionUpdateExecute((e) => {
// e.app
// e.collection
e.next()
}, "users", "articles")`
**[onCollectionAfterUpdateSuccess](https://pocketbase.io/docs/js-event-hooks/#oncollectionafterupdatesuccess)**
`onCollectionAfterUpdateSuccess` is a Collection proxy model hook of `onModelAfterUpdateSuccess`.
`onCollectionAfterUpdateSuccess` is triggered after each successful
Collection DB update persistence.
Note that when a Collection is persisted as part of a transaction,
this hook is delayed and executed only AFTER the transaction has been committed.
This hook is NOT triggered in case the transaction fails/rollbacks.
`// fires for every collection
onCollectionAfterUpdateSuccess((e) => {
// e.app
// e.collection
e.next()
})
// fires only for "users" and "articles" collections
onCollectionAfterUpdateSuccess((e) => {
// e.app
// e.collection
e.next()
}, "users", "articles")`
**[onCollectionAfterUpdateError](https://pocketbase.io/docs/js-event-hooks/#oncollectionafterupdateerror)**
`onCollectionAfterUpdateError` is a Collection proxy model hook of `onModelAfterUpdateError`.
`onCollectionAfterUpdateError` is triggered after each failed
Collection DB update persistence.
Note that the execution of this hook is either immediate or delayed
depending on the error:
- **immediate** on `$app.save()` failure
- **delayed** on transaction rollback
`// fires for every collection
onCollectionAfterUpdateError((e) => {
// e.app
// e.collection
// e.error
e.next()
})
// fires only for "users" and "articles" collections
onCollectionAfterUpdateError((e) => {
// e.app
// e.collection
// e.error
e.next()
}, "users", "articles")`
###### [Collection mode delete hooks](https://pocketbase.io/docs/js-event-hooks/\#collection-mode-delete-hooks)
**[onCollectionDelete](https://pocketbase.io/docs/js-event-hooks/#oncollectiondelete)**
`onCollectionDelete` is a Collection proxy model hook of `onModelDelete`.
`onCollectionDelete` is triggered every time when a new Collection is being deleted,
e.g. triggered by `$app.delete()`.
Operations BEFORE the `e.next()` execute before the Collection validation
and the UPDATE DB statement.
Operations AFTER the `e.next()` execute after the Collection validation
and the UPDATE DB statement.
Note that successful execution doesn't guarantee that the Collection
is deleted from the database since its wrapping transaction may
not have been committed yet.
If you want to listen to only the actual persisted deleted events, you can
bind to `onCollectionAfterDeleteSuccess` or `onCollectionAfterDeleteError` hooks.
`// fires for every collection
onCollectionDelete((e) => {
// e.app
// e.collection
e.next()
})
// fires only for "users" and "articles" collections
onCollectionDelete((e) => {
// e.app
// e.collection
e.next()
}, "users", "articles")`
**[onCollectionDeleteExecute](https://pocketbase.io/docs/js-event-hooks/#oncollectiondeleteexecute)**
`onCollectionDeleteExecute` is a Collection proxy model hook of `onModelDeleteExecute`.
`onCollectionDeleteExecute` is triggered after the internal delete checks and
right before the Collection the model DELETE DB statement execution.
Usually it is triggered as part of the `$app.delete()` in the following firing order:
`onCollectionDelete`
-\>
internal delete checks
-\> `onCollectionDeleteExecute`
Note that successful execution doesn't guarantee that the Collection
is deleted from the database since its wrapping transaction may
not have been committed yet.
If you want to listen to only the actual persisted events, you can
bind to `onCollectionAfterDeleteSuccess` or `onCollectionAfterDeleteError` hooks.
`// fires for every collection
onCollectionDeleteExecute((e) => {
// e.app
// e.collection
e.next()
})
// fires only for "users" and "articles" collections
onCollectionDeleteExecute((e) => {
// e.app
// e.collection
e.next()
}, "users", "articles")`
**[onCollectionAfterDeleteSuccess](https://pocketbase.io/docs/js-event-hooks/#oncollectionafterdeletesuccess)**
`onCollectionAfterDeleteSuccess` is a Collection proxy model hook of `onModelAfterDeleteSuccess`.
`onCollectionAfterDeleteSuccess` is triggered after each successful
Collection DB delete persistence.
Note that when a Collection is deleted as part of a transaction,
this hook is delayed and executed only AFTER the transaction has been committed.
This hook is NOT triggered in case the transaction fails/rollbacks.
`// fires for every collection
onCollectionAfterDeleteSuccess((e) => {
// e.app
// e.collection
e.next()
})
// fires only for "users" and "articles" collections
onCollectionAfterDeleteSuccess((e) => {
// e.app
// e.collection
e.next()
}, "users", "articles")`
**[onCollectionAfterDeleteError](https://pocketbase.io/docs/js-event-hooks/#oncollectionafterdeleteerror)**
`onCollectionAfterDeleteError` is a Collection proxy model hook of `onModelAfterDeleteError`.
`onCollectionAfterDeleteError` is triggered after each failed
Collection DB delete persistence.
Note that the execution of this hook is either immediate or delayed
depending on the error:
- **immediate** on `$app.delete()` failure
- **delayed** on transaction rollback
`// fires for every collection
onCollectionAfterDeleteError((e) => {
// e.app
// e.collection
// e.error
e.next()
})
// fires only for "users" and "articles" collections
onCollectionAfterDeleteError((e) => {
// e.app
// e.collection
// e.error
e.next()
}, "users", "articles")`
### [Request hooks](https://pocketbase.io/docs/js-event-hooks/\#request-hooks)
The request hooks are triggered only when the corresponding API request endpoint is accessed.
###### [Record CRUD request hooks](https://pocketbase.io/docs/js-event-hooks/\#record-crud-request-hooks)
**[onRecordsListRequest](https://pocketbase.io/docs/js-event-hooks/#onrecordslistrequest)**
`onRecordsListRequest` hook is triggered on each API Records list request.
Could be used to validate or modify the response before returning it to the client.
Note that if you want to hide existing or add new computed Record fields prefer using the
[`onRecordEnrich`](https://pocketbase.io/docs/js-event-hooks/#onrecordenrich)
hook because it is less error-prone and it is triggered
by all builtin Record responses (including when sending realtime Record events).
`// fires for every collection
onRecordsListRequest((e) => {
// e.app
// e.collection
// e.records
// e.result
// and all RequestEvent fields...
e.next()
})
// fires only for "users" and "articles" collections
onRecordsListRequest((e) => {
// e.app
// e.collection
// e.records
// e.result
// and all RequestEvent fields...
e.next()
}, "users", "articles")`
**[onRecordViewRequest](https://pocketbase.io/docs/js-event-hooks/#onrecordviewrequest)**
`onRecordViewRequest` hook is triggered on each API Record view request.
Could be used to validate or modify the response before returning it to the client.
Note that if you want to hide existing or add new computed Record fields prefer using the
[`onRecordEnrich`](https://pocketbase.io/docs/js-event-hooks/#onrecordenrich)
hook because it is less error-prone and it is triggered
by all builtin Record responses (including when sending realtime Record events).
`// fires for every collection
onRecordViewRequest((e) => {
// e.app
// e.collection
// e.record
// and all RequestEvent fields...
e.next()
})
// fires only for "users" and "articles" collections
onRecordViewRequest((e) => {
// e.app
// e.collection
// e.record
// and all RequestEvent fields...
e.next()
}, "users", "articles")`
**[onRecordCreateRequest](https://pocketbase.io/docs/js-event-hooks/#onrecordcreaterequest)**
`onRecordCreateRequest` hook is triggered on each API Record create request.
Could be used to additionally validate the request data or implement
completely different persistence behavior.
`// fires for every collection
onRecordCreateRequest((e) => {
// e.app
// e.collection
// e.record
// and all RequestEvent fields...
e.next()
})
// fires only for "users" and "articles" collections
onRecordCreateRequest((e) => {
// e.app
// e.collection
// e.record
// and all RequestEvent fields...
e.next()
}, "users", "articles")`
**[onRecordUpdateRequest](https://pocketbase.io/docs/js-event-hooks/#onrecordupdaterequest)**
`onRecordUpdateRequest` hook is triggered on each API Record update request.
Could be used to additionally validate the request data or implement
completely different persistence behavior.
`// fires for every collection
onRecordUpdateRequest((e) => {
// e.app
// e.collection
// e.record
// and all RequestEvent fields...
e.next()
})
// fires only for "users" and "articles" collections
onRecordUpdateRequest((e) => {
// e.app
// e.collection
// e.record
// and all RequestEvent fields...
e.next()
}, "users", "articles")`
**[onRecordDeleteRequest](https://pocketbase.io/docs/js-event-hooks/#onrecorddeleterequest)**
`onRecordDeleteRequest` hook is triggered on each API Record delete request.
Could be used to additionally validate the request data or implement
completely different delete behavior.
`// fires for every collection
onRecordDeleteRequest((e) => {
// e.app
// e.collection
// e.record
// and all RequestEvent fields...
e.next()
})
// fires only for "users" and "articles" collections
onRecordDeleteRequest((e) => {
// e.app
// e.collection
// e.record
// and all RequestEvent fields...
e.next()
}, "users", "articles")`
###### [Record auth request hooks](https://pocketbase.io/docs/js-event-hooks/\#record-auth-request-hooks)
**[onRecordAuthRequest](https://pocketbase.io/docs/js-event-hooks/#onrecordauthrequest)**
`onRecordAuthRequest` hook is triggered on each successful API
record authentication request (sign-in, token refresh, etc.).
Could be used to additionally validate or modify the authenticated
record data and token.
`// fires for every auth collection
onRecordAuthRequest((e) => {
// e.app
// e.record
// e.token
// e.meta
// e.authMethod
// and all RequestEvent fields...
e.next()
})
// fires only for "users" and "managers" auth collections
onRecordAuthRequest((e) => {
// e.app
// e.record
// e.token
// e.meta
// e.authMethod
// and all RequestEvent fields...
e.next()
}, "users", "managers")`
**[onRecordAuthRefreshRequest](https://pocketbase.io/docs/js-event-hooks/#onrecordauthrefreshrequest)**
`onRecordAuthRefreshRequest` hook is triggered on each Record
auth refresh API request (right before generating a new auth token).
Could be used to additionally validate the request data or implement
completely different auth refresh behavior.
`// fires for every auth collection
onRecordAuthRefreshRequest((e) => {
// e.app
// e.collection
// e.record
// and all RequestEvent fields...
e.next()
})
// fires only for "users" and "managers" auth collections
onRecordAuthRefreshRequest((e) => {
// e.app
// e.collection
// e.record
// and all RequestEvent fields...
e.next()
}, "users", "managers")`
**[onRecordAuthWithPasswordRequest](https://pocketbase.io/docs/js-event-hooks/#onrecordauthwithpasswordrequest)**
`onRecordAuthWithPasswordRequest` hook is triggered on each
Record auth with password API request.
`e.record` could be `nil` if no matching identity is found, allowing
you to manually locate a different Record model (by reassigning `e.record`).
`// fires for every auth collection
onRecordAuthWithPasswordRequest((e) => {
// e.app
// e.collection
// e.record (could be null)
// e.identity
// e.identityField
// e.password
// and all RequestEvent fields...
e.next()
})
// fires only for "users" and "managers" auth collections
onRecordAuthWithPasswordRequest((e) => {
// e.app
// e.collection
// e.record (could be null)
// e.identity
// e.identityField
// e.password
// and all RequestEvent fields...
e.next()
}, "users", "managers")`
**[onRecordAuthWithOAuth2Request](https://pocketbase.io/docs/js-event-hooks/#onrecordauthwithoauth2request)**
`onRecordAuthWithOAuth2Request` hook is triggered on each Record
OAuth2 sign-in/sign-up API request (after token exchange and before external provider linking).
If `e.record` is not set, then the OAuth2
request will try to create a new auth record.
To assign or link a different existing record model you can
change the `e.record` field.
`// fires for every auth collection
onRecordAuthWithOAuth2Request((e) => {
// e.app
// e.collection
// e.providerName
// e.providerClient
// e.record (could be null)
// e.oauth2User
// e.createData
// e.isNewRecord
// and all RequestEvent fields...
e.next()
})
// fires only for "users" and "managers" auth collections
onRecordAuthWithOAuth2Request((e) => {
// e.app
// e.collection
// e.providerName
// e.providerClient
// e.record (could be null)
// e.oauth2User
// e.createData
// e.isNewRecord
// and all RequestEvent fields...
e.next()
}, "users", "managers")`
**[onRecordRequestPasswordResetRequest](https://pocketbase.io/docs/js-event-hooks/#onrecordrequestpasswordresetrequest)**
`onRecordRequestPasswordResetRequest` hook is triggered on
each Record request password reset API request.
Could be used to additionally validate the request data or implement
completely different password reset behavior.
`// fires for every auth collection
onRecordRequestPasswordResetRequest((e) => {
// e.app
// e.collection
// e.record
// and all RequestEvent fields...
e.next()
})
// fires only for "users" and "managers" auth collections
onRecordRequestPasswordResetRequest((e) => {
// e.app
// e.collection
// e.record
// and all RequestEvent fields...
e.next()
}, "users", "managers")`
**[onRecordConfirmPasswordResetRequest](https://pocketbase.io/docs/js-event-hooks/#onrecordconfirmpasswordresetrequest)**
`onRecordConfirmPasswordResetRequest` hook is triggered on
each Record confirm password reset API request.
Could be used to additionally validate the request data or implement
completely different persistence behavior.
`// fires for every auth collection
onRecordConfirmPasswordResetRequest((e) => {
// e.app
// e.collection
// e.record
// and all RequestEvent fields...
e.next()
})
// fires only for "users" and "managers" auth collections
onRecordConfirmPasswordResetRequest((e) => {
// e.app
// e.collection
// e.record
// and all RequestEvent fields...
e.next()
}, "users", "managers")`
**[onRecordRequestVerificationRequest](https://pocketbase.io/docs/js-event-hooks/#onrecordrequestverificationrequest)**
`onRecordRequestVerificationRequest` hook is triggered on
each Record request verification API request.
Could be used to additionally validate the loaded request data or implement
completely different verification behavior.
`// fires for every auth collection
onRecordRequestVerificationRequest((e) => {
// e.app
// e.collection
// e.record
// and all RequestEvent fields...
e.next()
})
// fires only for "users" and "managers" auth collections
onRecordRequestVerificationRequest((e) => {
// e.app
// e.collection
// e.record
// and all RequestEvent fields...
e.next()
}, "users", "managers")`
**[onRecordConfirmVerificationRequest](https://pocketbase.io/docs/js-event-hooks/#onrecordconfirmverificationrequest)**
`onRecordConfirmVerificationRequest` hook is triggered on each
Record confirm verification API request.
Could be used to additionally validate the request data or implement
completely different persistence behavior.
`// fires for every auth collection
onRecordConfirmVerificationRequest((e) => {
// e.app
// e.collection
// e.record
// and all RequestEvent fields...
e.next()
})
// fires only for "users" and "managers" auth collections
onRecordConfirmVerificationRequest((e) => {
// e.app
// e.collection
// e.record
// and all RequestEvent fields...
e.next()
}, "users", "managers")`
**[onRecordRequestEmailChangeRequest](https://pocketbase.io/docs/js-event-hooks/#onrecordrequestemailchangerequest)**
`onRecordRequestEmailChangeRequest` hook is triggered on each
Record request email change API request.
Could be used to additionally validate the request data or implement
completely different request email change behavior.
`// fires for every auth collection
onRecordRequestEmailChangeRequest((e) => {
// e.app
// e.collection
// e.record
// e.newEmail
// and all RequestEvent fields...
e.next()
})
// fires only for "users" and "managers" auth collections
onRecordRequestEmailChangeRequest((e) => {
// e.app
// e.collection
// e.record
// e.newEmail
// and all RequestEvent fields...
e.next()
}, "users", "managers")`
**[onRecordConfirmEmailChangeRequest](https://pocketbase.io/docs/js-event-hooks/#onrecordconfirmemailchangerequest)**
`onRecordConfirmEmailChangeRequest` hook is triggered on each
Record confirm email change API request.
Could be used to additionally validate the request data or implement
completely different persistence behavior.
`// fires for every auth collection
onRecordConfirmEmailChangeRequest((e) => {
// e.app
// e.collection
// e.record
// e.newEmail
// and all RequestEvent fields...
e.next()
})
// fires only for "users" and "managers" auth collections
onRecordConfirmEmailChangeRequest((e) => {
// e.app
// e.collection
// e.record
// e.newEmail
// and all RequestEvent fields...
e.next()
}, "users", "managers")`
**[onRecordRequestOTPRequest](https://pocketbase.io/docs/js-event-hooks/#onrecordrequestotprequest)**
`onRecordRequestOTPRequest` hook is triggered on each Record
request OTP API request.
`e.record` could be `nil` if no user with the requested email is found, allowing
you to manually create a new Record or locate a different Record model (by reassigning `e.record`).
`// fires for every auth collection
onRecordRequestOTPRequest((e) => {
// e.app
// e.collection
// e.record (could be null)
// e.password
// and all RequestEvent fields...
e.next()
})
// fires only for "users" and "managers" auth collections
onRecordRequestOTPRequest((e) => {
// e.app
// e.collection
// e.record (could be null)
// e.password
// and all RequestEvent fields...
e.next()
}, "users", "managers")`
**[onRecordAuthWithOTPRequest](https://pocketbase.io/docs/js-event-hooks/#onrecordauthwithotprequest)**
`onRecordAuthWithOTPRequest` hook is triggered on each Record
auth with OTP API request.
`// fires for every auth collection
onRecordAuthWithOTPRequest((e) => {
// e.app
// e.collection
// e.record
// e.otp
// and all RequestEvent fields...
e.next()
})
// fires only for "users" and "managers" auth collections
onRecordAuthWithOTPRequest((e) => {
// e.app
// e.collection
// e.record
// e.otp
// and all RequestEvent fields...
e.next()
}, "users", "managers")`
###### [Batch request hooks](https://pocketbase.io/docs/js-event-hooks/\#batch-request-hooks)
**[onBatchRequest](https://pocketbase.io/docs/js-event-hooks/#onbatchrequest)**
`onBatchRequest` hook is triggered on each API batch request.
Could be used to additionally validate or modify the submitted batch requests.
This hook will also fire the corresponding `onRecordCreateRequest`, `onRecordUpdateRequest`, `onRecordDeleteRequest` hooks, where `e.app` is the batch transactional app.
`onBatchRequest((e) => {
// e.app
// e.batch
// and all RequestEvent fields...
e.next()
})`
###### [File request hooks](https://pocketbase.io/docs/js-event-hooks/\#file-request-hooks)
**[onFileDownloadRequest](https://pocketbase.io/docs/js-event-hooks/#onfiledownloadrequest)**
`onFileDownloadRequest` hook is triggered before each API File download request.
Could be used to validate or modify the file response before returning it to the client.
`onFileDownloadRequest((e) => {
// e.app
// e.collection
// e.record
// e.fileField
// e.servedPath
// e.servedName
// and all RequestEvent fields...
e.next()
})`
**[onFileTokenRequest](https://pocketbase.io/docs/js-event-hooks/#onfiletokenrequest)**
`onFileTokenRequest` hook is triggered on each auth file token API request.
`// fires for every auth model
onFileTokenRequest((e) => {
// e.app
// e.record
// e.token
// and all RequestEvent fields...
e.next();
})
// fires only for "users"
onFileTokenRequest((e) => {
// e.app
// e.record
// e.token
// and all RequestEvent fields...
e.next();
}, "users")`
###### [Collection request hooks](https://pocketbase.io/docs/js-event-hooks/\#collection-request-hooks)
**[onCollectionsListRequest](https://pocketbase.io/docs/js-event-hooks/#oncollectionslistrequest)**
`onCollectionsListRequest` hook is triggered on each API Collections list request.
Could be used to validate or modify the response before returning it to the client.
`onCollectionsListRequest((e) => {
// e.app
// e.collections
// e.result
// and all RequestEvent fields...
e.next()
})`
**[onCollectionViewRequest](https://pocketbase.io/docs/js-event-hooks/#oncollectionviewrequest)**
`onCollectionViewRequest` hook is triggered on each API Collection view request.
Could be used to validate or modify the response before returning it to the client.
`onCollectionViewRequest((e) => {
// e.app
// e.collection
// and all RequestEvent fields...
e.next()
})`
**[onCollectionCreateRequest](https://pocketbase.io/docs/js-event-hooks/#oncollectioncreaterequest)**
`onCollectionCreateRequest` hook is triggered on each API Collection create request.
Could be used to additionally validate the request data or implement
completely different persistence behavior.
`onCollectionCreateRequest((e) => {
// e.app
// e.collection
// and all RequestEvent fields...
e.next()
})`
**[onCollectionUpdateRequest](https://pocketbase.io/docs/js-event-hooks/#oncollectionupdaterequest)**
`onCollectionUpdateRequest` hook is triggered on each API Collection update request.
Could be used to additionally validate the request data or implement
completely different persistence behavior.
`onCollectionUpdateRequest((e) => {
// e.app
// e.collection
// and all RequestEvent fields...
e.next()
})`
**[onCollectionDeleteRequest](https://pocketbase.io/docs/js-event-hooks/#oncollectiondeleterequest)**
`onCollectionDeleteRequest` hook is triggered on each API Collection delete request.
Could be used to additionally validate the request data or implement
completely different delete behavior.
`onCollectionDeleteRequest((e) => {
// e.app
// e.collection
// and all RequestEvent fields...
e.next()
})`
**[onCollectionsImportRequest](https://pocketbase.io/docs/js-event-hooks/#oncollectionsimportrequest)**
`onCollectionsImportRequest` hook is triggered on each API
collections import request.
Could be used to additionally validate the imported collections or
to implement completely different import behavior.
`onCollectionsImportRequest((e) => {
// e.app
// e.collectionsData
// e.deleteMissing
e.next()
})`
###### [Settings request hooks](https://pocketbase.io/docs/js-event-hooks/\#settings-request-hooks)
**[onSettingsListRequest](https://pocketbase.io/docs/js-event-hooks/#onsettingslistrequest)**
`onSettingsListRequest` hook is triggered on each API Settings list request.
Could be used to validate or modify the response before returning it to the client.
`onSettingsListRequest((e) => {
// e.app
// e.settings
// and all RequestEvent fields...
e.next()
})`
**[onSettingsUpdateRequest](https://pocketbase.io/docs/js-event-hooks/#onsettingsupdaterequest)**
`onSettingsUpdateRequest` hook is triggered on each API Settings update request.
Could be used to additionally validate the request data or
implement completely different persistence behavior.
`onSettingsUpdateRequest((e) => {
// e.app
// e.oldSettings
// e.newSettings
// and all RequestEvent fields...
e.next()
})`
### [Base model hooks](https://pocketbase.io/docs/js-event-hooks/\#base-model-hooks)
The Model hooks are fired for all PocketBase structs that implements the Model DB interface - Record, Collection, Log, etc.
For convenience, if you want to listen to only the Record or Collection DB model
events without doing manual type assertion, you can use the
[`onRecord*`](https://pocketbase.io/docs/js-event-hooks/#record-model-hooks)
and
[`onCollection*`](https://pocketbase.io/docs/js-event-hooks/#collection-model-hooks)
proxy hooks above.
**[onModelValidate](https://pocketbase.io/docs/js-event-hooks/#onmodelvalidate)**
`onModelValidate` is called every time when a Model is being validated,
e.g. triggered by `$app.validate()` or `$app.save()`.
For convenience, if you want to listen to only the Record or Collection models
events without doing manual type assertion, you can use the equivalent `onRecord*` and `onCollection*` proxy hooks.
`// fires for every model
onModelValidate((e) => {
// e.app
// e.model
e.next()
})
// fires only for "users" and "articles" models
onModelValidate((e) => {
// e.app
// e.model
e.next()
}, "users", "articles")`
###### [Base model create hooks](https://pocketbase.io/docs/js-event-hooks/\#base-model-create-hooks)
**[onModelCreate](https://pocketbase.io/docs/js-event-hooks/#onmodelcreate)**
`onModelCreate` is triggered every time when a new Model is being created,
e.g. triggered by `$app.save()`.
Operations BEFORE the `e.next()` execute before the Model validation
and the INSERT DB statement.
Operations AFTER the `e.next()` execute after the Model validation
and the INSERT DB statement.
Note that successful execution doesn't guarantee that the Model
is persisted in the database since its wrapping transaction may
not have been committed yet.
If you want to listen to only the actual persisted events, you can
bind to `onModelAfterCreateSuccess` or `onModelAfterCreateError` hooks.
For convenience, if you want to listen to only the Record or Collection models
events without doing manual type assertion, you can use the equivalent `onRecord*` and `onCollection*` proxy hooks.
`// fires for every model
onModelCreate((e) => {
// e.app
// e.model
e.next()
})
// fires only for "users" and "articles" models
onModelCreate((e) => {
// e.app
// e.model
e.next()
}, "users", "articles")`
**[onModelCreateExecute](https://pocketbase.io/docs/js-event-hooks/#onmodelcreateexecute)**
`onModelCreateExecute` is triggered after successful Model validation
and right before the model INSERT DB statement execution.
Usually it is triggered as part of the `$app.save()` in the following firing order:
`onModelCreate`
-\> `onModelValidate` (skipped with `$app.saveNoValidate()`)
-\> `onModelCreateExecute`
Note that successful execution doesn't guarantee that the Model
is persisted in the database since its wrapping transaction may
not have been committed yet.
If you want to listen to only the actual persisted events, you can
bind to `onModelAfterCreateSuccess` or `onModelAfterCreateError` hooks.
For convenience, if you want to listen to only the Record or Collection models
events without doing manual type assertion, you can use the equivalent `onRecord*` and `onCollection*` proxy hooks.
`// fires for every model
onModelCreateExecute((e) => {
// e.app
// e.model
e.next()
})
// fires only for "users" and "articles" models
onModelCreateExecute((e) => {
// e.app
// e.model
e.next()
}, "users", "articles")`
**[onModelAfterCreateSuccess](https://pocketbase.io/docs/js-event-hooks/#onmodelaftercreatesuccess)**
`onModelAfterCreateSuccess` is triggered after each successful
Model DB create persistence.
Note that when a Model is persisted as part of a transaction,
this hook is delayed and executed only AFTER the transaction has been committed.
This hook is NOT triggered in case the transaction fails/rollbacks.
For convenience, if you want to listen to only the Record or Collection models
events without doing manual type assertion, you can use the equivalent `onRecord*` and `onCollection*` proxy hooks.
`// fires for every model
onModelAfterCreateSuccess((e) => {
// e.app
// e.model
e.next()
})
// fires only for "users" and "articles" models
onModelAfterCreateSuccess((e) => {
// e.app
// e.model
e.next()
}, "users", "articles")`
**[onModelAfterCreateError](https://pocketbase.io/docs/js-event-hooks/#onmodelaftercreateerror)**
`onModelAfterCreateError` is triggered after each failed
Model DB create persistence.
Note that the execution of this hook is either immediate or delayed
depending on the error:
- **immediate** on `$app.save()` failure
- **delayed** on transaction rollback
For convenience, if you want to listen to only the Record or Collection models
events without doing manual type assertion, you can use the equivalent `onRecord*` and `onCollection*` proxy hooks.
`// fires for every model
onModelAfterCreateError((e) => {
// e.app
// e.model
// e.error
e.next()
})
// fires only for "users" and "articles" models
onModelAfterCreateError((e) => {
// e.app
// e.model
// e.error
e.next()
}, "users", "articles")`
###### [Base model update hooks](https://pocketbase.io/docs/js-event-hooks/\#base-model-update-hooks)
**[onModelUpdate](https://pocketbase.io/docs/js-event-hooks/#onmodelupdate)**
`onModelUpdate` is triggered every time when a new Model is being updated,
e.g. triggered by `$app.save()`.
Operations BEFORE the `e.next()` execute before the Model validation
and the UPDATE DB statement.
Operations AFTER the `e.next()` execute after the Model validation
and the UPDATE DB statement.
Note that successful execution doesn't guarantee that the Model
is persisted in the database since its wrapping transaction may
not have been committed yet.
If you want to listen to only the actual persisted events, you can
bind to `onModelAfterUpdateSuccess` or `onModelAfterUpdateError` hooks.
For convenience, if you want to listen to only the Record or Collection models
events without doing manual type assertion, you can use the equivalent `onRecord*` and `onCollection*` proxy hooks.
`// fires for every model
onModelUpdate((e) => {
// e.app
// e.model
e.next()
})
// fires only for "users" and "articles" models
onModelUpdate((e) => {
// e.app
// e.model
e.next()
}, "users", "articles")`
**[onModelUpdateExecute](https://pocketbase.io/docs/js-event-hooks/#onmodelupdateexecute)**
`onModelUpdateExecute` is triggered after successful Model validation
and right before the model UPDATE DB statement execution.
Usually it is triggered as part of the `$app.save()` in the following firing order:
`onModelUpdate`
-\> `onModelValidate` (skipped with `$app.saveNoValidate()`)
-\> `onModelUpdateExecute`
Note that successful execution doesn't guarantee that the Model
is persisted in the database since its wrapping transaction may
not have been committed yet.
If you want to listen to only the actual persisted events, you can
bind to `onModelAfterUpdateSuccess` or `onModelAfterUpdateError` hooks.
For convenience, if you want to listen to only the Record or Collection models
events without doing manual type assertion, you can use the equivalent `onRecord*` and `onCollection*` proxy hooks.
`// fires for every model
onModelUpdateExecute((e) => {
// e.app
// e.model
e.next()
})
// fires only for "users" and "articles" models
onModelUpdateExecute((e) => {
// e.app
// e.model
e.next()
}, "users", "articles")`
**[onModelAfterUpdateSuccess](https://pocketbase.io/docs/js-event-hooks/#onmodelafterupdatesuccess)**
`onModelAfterUpdateSuccess` is triggered after each successful
Model DB update persistence.
Note that when a Model is persisted as part of a transaction,
this hook is delayed and executed only AFTER the transaction has been committed.
This hook is NOT triggered in case the transaction fails/rollbacks.
For convenience, if you want to listen to only the Record or Collection models
events without doing manual type assertion, you can use the equivalent `onRecord*` and `onCollection*` proxy hooks.
`// fires for every model
onModelAfterUpdateSuccess((e) => {
// e.app
// e.model
e.next()
})
// fires only for "users" and "articles" models
onModelAfterUpdateSuccess((e) => {
// e.app
// e.model
e.next()
}, "users", "articles")`
**[onModelAfterUpdateError](https://pocketbase.io/docs/js-event-hooks/#onmodelafterupdateerror)**
`onModelAfterUpdateError` is triggered after each failed
Model DB update persistence.
Note that the execution of this hook is either immediate or delayed
depending on the error:
- **immediate** on `$app.save()` failure
- **delayed** on transaction rollback
For convenience, if you want to listen to only the Record or Collection models
events without doing manual type assertion, you can use the equivalent `onRecord*` and `onCollection*` proxy hooks.
`// fires for every model
onModelAfterUpdateError((e) => {
// e.app
// e.model
// e.error
e.next()
})
// fires only for "users" and "articles" models
onModelAfterUpdateError((e) => {
// e.app
// e.model
// e.error
e.next()
}, "users", "articles")`
###### [Base model delete hooks](https://pocketbase.io/docs/js-event-hooks/\#base-model-delete-hooks)
**[onModelDelete](https://pocketbase.io/docs/js-event-hooks/#onmodeldelete)**
`onModelDelete` is triggered every time when a new Model is being deleted,
e.g. triggered by `$app.delete()`.
Operations BEFORE the `e.next()` execute before the Model validation
and the UPDATE DB statement.
Operations AFTER the `e.next()` execute after the Model validation
and the UPDATE DB statement.
Note that successful execution doesn't guarantee that the Model
is deleted from the database since its wrapping transaction may
not have been committed yet.
If you want to listen to only the actual persisted deleted events, you can
bind to `onModelAfterDeleteSuccess` or `onModelAfterDeleteError` hooks.
For convenience, if you want to listen to only the Record or Collection models
events without doing manual type assertion, you can use the equivalent `onRecord*` and `onCollection*` proxy hooks.
`// fires for every model
onModelDelete((e) => {
// e.app
// e.model
e.next()
})
// fires only for "users" and "articles" models
onModelDelete((e) => {
// e.app
// e.model
e.next()
}, "users", "articles")`
**[onModelDeleteExecute](https://pocketbase.io/docs/js-event-hooks/#onmodeldeleteexecute)**
`onModelDeleteExecute` is triggered after the internal delete checks and
right before the Model the model DELETE DB statement execution.
Usually it is triggered as part of the `$app.delete()` in the following firing order:
`onModelDelete`
-\>
internal delete checks
-\> `onModelDeleteExecute`
Note that successful execution doesn't guarantee that the Model
is deleted from the database since its wrapping transaction may
not have been committed yet.
If you want to listen to only the actual persisted events, you can
bind to `onModelAfterDeleteSuccess` or `onModelAfterDeleteError` hooks.
For convenience, if you want to listen to only the Record or Collection models
events without doing manual type assertion, you can use the equivalent `onRecord*` and `onCollection*` proxy hooks.
`// fires for every model
onModelDeleteExecute((e) => {
// e.app
// e.model
e.next()
})
// fires only for "users" and "articles" models
onModelDeleteExecute((e) => {
// e.app
// e.model
e.next()
}, "users", "articles")`
**[onModelAfterDeleteSuccess](https://pocketbase.io/docs/js-event-hooks/#onmodelafterdeletesuccess)**
`onModelAfterDeleteSuccess` is triggered after each successful
Model DB delete persistence.
Note that when a Model is deleted as part of a transaction,
this hook is delayed and executed only AFTER the transaction has been committed.
This hook is NOT triggered in case the transaction fails/rollbacks.
For convenience, if you want to listen to only the Record or Collection models
events without doing manual type assertion, you can use the equivalent `onRecord*` and `onCollection*` proxy hooks.
`// fires for every model
onModelAfterDeleteSuccess((e) => {
// e.app
// e.model
e.next()
})
// fires only for "users" and "articles" models
onModelAfterDeleteSuccess((e) => {
// e.app
// e.model
e.next()
}, "users", "articles")`
**[onModelAfterDeleteError](https://pocketbase.io/docs/js-event-hooks/#onmodelafterdeleteerror)**
`onModelAfterDeleteError` is triggered after each failed
Model DB delete persistence.
Note that the execution of this hook is either immediate or delayed
depending on the error:
- **immediate** on `$app.delete()` failure
- **delayed** on transaction rollback
For convenience, if you want to listen to only the Record or Collection models
events without doing manual type assertion, you can use the equivalent `onRecord*` and `onCollection*` proxy hooks.
`// fires for every model
onModelAfterDeleteError((e) => {
// e.app
// e.model
// e.error
e.next()
})
// fires only for "users" and "articles" models
onModelAfterDeleteError((e) => {
// e.app
// e.model
// e.error
e.next()
}, "users", "articles")`
* * *
[Prev: Overview](https://pocketbase.io/docs/js-overview) [Next: Routing](https://pocketbase.io/docs/js-routing)
## Extending PocketBase
Extending PocketBase
One of the main feature of PocketBase is that
**it can be used as a framework** which enables you to write your own custom app business
logic in
[Go](https://pocketbase.io/docs/go-overview) or [JavaScript](https://pocketbase.io/docs/js-overview) and still have a portable
backend at the end.
**Choose [Extend with Go](https://pocketbase.io/docs/go-overview) if you are already familiar**
**with the language or have the time to learn it.**
As the primary PocketBase language, the Go APIs are better documented and you'll be able to integrate with
any 3rd party Go library since you'll have more control over the application flow. The only drawback is that
the Go APIs are slightly more verbose and it may require some time to get used to, especially if this is your
first time working with Go.
**Choose [Extend with JavaScript](https://pocketbase.io/docs/js-overview)**
**if you don't intend to write too much custom code and want a quick way to explore the PocketBase capabilities.**
The embedded JavaScript engine is a pluggable wrapper around the existing Go APIs, so most of the time the
slight performance penalty will be negligible because it'll invoke the Go functions under the hood.
As a bonus, because the JS VM mirrors the Go APIs, you would be able migrate gradually without much code changes
from JS -> Go at later stage in case you hit a bottleneck or want more control over the execution flow.
With both Go and JavaScript, you can:
- **Register custom routes:**
Go
JavaScript
`app.OnServe().BindFunc(func(se *core.ServeEvent) error {
se.Router.GET("/hello", func(e *core.RequestEvent) error {
return e.String(http.StatusOK, "Hello world!")
})
return se.Next()
})`
`routerAdd("GET", "/hello", (e) => {
return e.string(200, "Hello world!")
})`
- **Bind to event hooks and intercept responses:**
Go
JavaScript
`app.OnRecordCreateRequest("posts").BindFunc(func(e *core.RecordRequestEvent) error {
// if not superuser, overwrite the newly submitted "posts" record status to pending
if !e.HasSuperuserAuth() {
e.Record.Set("status", "pending")
}
return e.Next()
})`
`onRecordCreateRequest((e) => {
// if not superuser, overwrite the newly submitted "posts" record status to pending
if (!e.hasSuperuserAuth()) {
e.record.set("status", "pending")
}
e.next()
}, "posts")`
- **Register custom console commands:**
Go
JavaScript
`app.RootCmd.AddCommand(&cobra.Command{
Use: "hello",
Run: func(cmd *cobra.Command, args []string) {
print("Hello world!")
},
})`
`$app.rootCmd.addCommand(new Command({
use: "hello",
run: (cmd, args) => {
console.log("Hello world!")
},
}))`
- and many more...
For further info, please check the related [Extend with Go](https://pocketbase.io/docs/go-overview) or
[Extend with JavaScript](https://pocketbase.io/docs/js-overview) guides.
* * *
[Prev: Working with relations](https://pocketbase.io/docs/working-with-relations)
## PocketBase Production Guide
Going to production
- [Deployment strategies](https://pocketbase.io/docs/going-to-production/#deployment-strategies)
- [Minimal setup](https://pocketbase.io/docs/going-to-production/#minimal-setup)
- [Using reverse proxy](https://pocketbase.io/docs/going-to-production/#using-reverse-proxy)
- [Using Docker](https://pocketbase.io/docs/going-to-production/#using-docker)
- [Backup and Restore](https://pocketbase.io/docs/going-to-production/#backup-and-restore)
- [Recommendations](https://pocketbase.io/docs/going-to-production/#recommendations)
- [Use SMTP mail server](https://pocketbase.io/docs/going-to-production/#use-smtp-mail-server)
- [Enable MFA for superusers](https://pocketbase.io/docs/going-to-production/#enable-mfa-for-superusers)
- [Enable rate limiter](https://pocketbase.io/docs/going-to-production/#enable-rate-limiter)
- [Increase the open file descriptors limit](https://pocketbase.io/docs/going-to-production/#increase-the-open-file-descriptors-limit)
- [Set GOMEMLIMIT](https://pocketbase.io/docs/going-to-production/#set-gomemlimit)
- [Enable settings encryption](https://pocketbase.io/docs/going-to-production/#enable-settings-encryption)
### [Deployment strategies](https://pocketbase.io/docs/going-to-production/\#deployment-strategies)
##### [Minimal setup](https://pocketbase.io/docs/going-to-production/\#minimal-setup)
One of the best PocketBase features is that it's completely portable. This mean that it doesn't require
any external dependency and
**could be deployed by just uploading the executable on your server**.
Here is an example for starting a production HTTPS server (auto managed TLS with Let's Encrypt) on clean
Ubuntu 22.04 installation.
1. Consider the following app directory structure:
`myapp/
pb_migrations/
pb_hooks/
pocketbase`
2. Upload the binary and anything else required by your application to your remote server, for
example using
**rsync**:
`rsync -avz -e ssh /local/path/to/myapp root@YOUR_SERVER_IP:/root/pb`
3. Start a SSH session with your server:
`ssh root@YOUR_SERVER_IP`
4. Start the executable (specifying a domain name will issue a Let's encrypt certificate for it)
`[root@dev ~]$ /root/pb/pocketbase serve yourdomain.com`
> Notice that in the above example we are logged in as **root** which allow us to
> bind to the
> **privileged 80 and 443 ports**.
>
>
> For **non-root** users usually you'll need special privileges to be able to do
> that. You have several options depending on your OS - `authbind`,
> `setcap`,
> `iptables`, `sysctl`, etc. Here is an example using `setcap`:
>
> `[myuser@dev ~]$ sudo setcap 'cap_net_bind_service=+ep' /root/pb/pocketbase`
5. (Optional) Systemd service
You can skip step 3 and create a **Systemd service**
to allow your application to start/restart on its own.
Here is an example service file (usually created in
`/lib/systemd/system/pocketbase.service`):
`[Unit]
Description = pocketbase
[Service]
Type = simple
User = root
Group = root
LimitNOFILE = 4096
Restart = always
RestartSec = 5s
StandardOutput = append:/root/pb/std.log
StandardError = append:/root/pb/std.log
WorkingDirectory = /root/pb
ExecStart = /root/pb/pocketbase serve yourdomain.com
[Install]
WantedBy = multi-user.target`
After that we just have to enable it and start the service using `systemctl`:
`[root@dev ~]$ systemctl enable pocketbase.service
[root@dev ~]$ systemctl start pocketbase`
> You can find a link to the Web UI installer in the `/root/pb/std.log`, but
> alternatively you can also create the first superuser explicitly via the
> `superuser` PocketBase command:
>
> `[root@dev ~]$ /root/pb/pocketbase superuser create EMAIL PASS`
##### [Using reverse proxy](https://pocketbase.io/docs/going-to-production/\#using-reverse-proxy)
If you plan hosting multiple applications on a single server or need finer network controls, you can
always put PocketBase behind a reverse proxy such as
_NGINX_, _Apache_, _Caddy_, etc.
_Just note that when using a reverse proxy you may need to setup the "User IP proxy headers" in the_
_PocketBase settings so that the application can extract and log the actual visitor/client IP (the_
_headers are usually `X-Real-IP`, `X-Forwarded-For`)._
Here is a minimal _NGINX_ example configuration:
`server {
listen 80;
server_name example.com;
client_max_body_size 10M;
location / {
# check http://nginx.org/en/docs/http/ngx_http_upstream_module.html#keepalive
proxy_set_header Connection '';
proxy_http_version 1.1;
proxy_read_timeout 360s;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# enable if you are serving under a subpath location
# rewrite /yourSubpath/(.*) /$1 break;
proxy_pass http://127.0.0.1:8090;
}
}`
Corresponding _Caddy_ configuration is:
`example.com {
request_body {
max_size 10MB
}
reverse_proxy 127.0.0.1:8090 {
transport http {
read_timeout 360s
}
}
}`
##### [Using Docker](https://pocketbase.io/docs/going-to-production/\#using-docker)
Some hosts (e.g. [fly.io](https://fly.io/)) use Docker
for deployments. PocketBase doesn't have an official Docker image, but you could use the below minimal
Dockerfile as an example:
`FROM alpine:latest
ARG PB_VERSION=0.26.6
RUN apk add --no-cache \
unzip \
ca-certificates
# download and unzip PocketBase
ADD https://github.com/pocketbase/pocketbase/releases/download/v${PB_VERSION}/pocketbase_${PB_VERSION}_linux_amd64.zip /tmp/pb.zip
RUN unzip /tmp/pb.zip -d /pb/
# uncomment to copy the local pb_migrations dir into the image
# COPY ./pb_migrations /pb/pb_migrations
# uncomment to copy the local pb_hooks dir into the image
# COPY ./pb_hooks /pb/pb_hooks
EXPOSE 8080
# start PocketBase
CMD ["/pb/pocketbase", "serve", "--http=0.0.0.0:8080"]`
To persist your data you need to mount a volume at `/pb/pb_data`.
_For a full example you could check the_
_["Host for free on Fly.io"](https://github.com/pocketbase/pocketbase/discussions/537)_
_guide._
### [Backup and Restore](https://pocketbase.io/docs/going-to-production/\#backup-and-restore)
To backup/restore your application it is enough to manually copy/replace your `pb_data`
directory
_(for transactional safety make sure that the application is not running)_.
To make things slightly easier, PocketBase v0.16+ comes with built-in backups and restore APIs that could
be accessed from the Dashboard (
_Settings_ \> _Backups_
):

Backups can be stored locally (default) or in a S3 compatible storage ( _it is recommended to use a separate bucket only for the backups_). The generated backup represents a full snapshot as ZIP archive of your `pb_data` directory (including
the locally stored uploaded files but excluding any local backups or files uploaded to S3).
During the backup's ZIP generation the application will be temporary set in read-only mode.
Depending on the size of your `pb_data` this could be a very slow operation and it is
advised in case of large `pb_data` (e.g. 2GB+) to consider a different backup strategy
_(see an example_
_[backup.sh script](https://github.com/pocketbase/pocketbase/discussions/4254#backups)_
_that combines `sqlite3 .backup` \+ `rsync`)_.
### [Recommendations](https://pocketbase.io/docs/going-to-production/\#recommendations)
By default, PocketBase uses the internal Unix `sendmail` command for sending emails.
While it's OK for development, it's not very useful for production, because your emails most likely will get
marked as spam or even fail to deliver.
To avoid deliverability issues, consider using a local SMTP server or an external mail service like
[MailerSend](https://www.mailersend.com/),
[Brevo](https://www.brevo.com/),
[SendGrid](https://sendgrid.com/),
[Mailgun](https://www.mailgun.com/),
[AWS SES](https://aws.amazon.com/ses/), etc.
Once you've decided on a mail service, you could configure the PocketBase SMTP settings from the
_Dashboard > Settings > Mail settings_:

As an additional layer of security you can enable the MFA and OTP options for the `_superusers`
collection, which will enforce an additional one-time password (email code) requirement when authenticating
as superuser.
In case of email deliverability issues, you can also generate an OTP manually using the
`./pocketbase superuser otp yoursuperuser@example.com` command.

To minimize the risk of API abuse (e.g. excessive auth or record create requests) it is recommended to
setup a rate limiter.
PocketBase v0.23.0+ comes with a simple builtin rate limiter that should cover most of the cases but you
are also free to use any external one via reverse proxy if you need more advanced options.
You can configure the builtin rate limiter from the
_Dashboard > Settings > Application:_

The below instructions are for Linux but other operating systems have similar mechanism.
Unix uses _"file descriptors"_ also for network connections and most systems have a default limit
of ~ 1024.
If your application has a lot of concurrent realtime connections, it is possible that at some point you would
get an error such as: `Too many open files`.
One way to mitigate this is to check your current account resource limits by running
`ulimit -a` and find the parameter you want to change. For example, if you want to increase the
open files limit ( _-n_), you could run
`ulimit -n 4096` before starting PocketBase.
If you are running in a memory constrained environment, defining the
[`GOMEMLIMIT`](https://pkg.go.dev/runtime#hdr-Environment_Variables)
environment variable could help preventing out-of-memory (OOM) termination of your process. It is a "soft limit"
meaning that the memory usage could still exceed it in some situations, but it instructs the GC to be more
"aggressive" and run more often if needed. For example: `GOMEMLIMIT=512MiB`.
If after `GOMEMLIMIT` you are still experiencing OOM errors, you can try to enable swap
partitioning (if not already) or open a
[Q&A discussion](https://github.com/pocketbase/pocketbase/discussions)
with some steps to reproduce the error in case it is something that we can improve in PocketBase.
It is fine to ignore the below if you are not sure whether you need it.
By default, PocketBase stores the applications settings in the database as plain JSON text, including the
SMTP password and S3 storage credentials.
While this is not a security issue on its own (PocketBase applications live entirely on a single server
and its expected only authorized users to have access to your server and application data), in some
situations it may be a good idea to store the settings encrypted in case someone get their hands on your
database file (e.g. from an external stored backup).
To store your PocketBase settings encrypted:
1. Create a new environment variable and **set a random 32 characters** string as its value.
e.g. add
`export PB_ENCRYPTION_KEY="3kh4kb9G2CESdRvCnAJx1z5LcpbAoaAl"`
in your shell profile file
2. Start the application with `--encryptionEnv=YOUR_ENV_VAR` flag.
e.g. `pocketbase serve --encryptionEnv=PB_ENCRYPTION_KEY`
## Go HTML Templates
Rendering templates
- [Overview](https://pocketbase.io/docs/go-rendering-templates/#overview)
- [Example HTML page with layout](https://pocketbase.io/docs/go-rendering-templates/#example-html-page-with-layout)
### [Overview](https://pocketbase.io/docs/go-rendering-templates/\#overview)
A common task when creating custom routes or emails is the need of generating HTML output.
There are plenty of Go template-engines available that you can use for this, but often for simple cases
the Go standard library `html/template` package should work just fine.
To make it slightly easier to load template files concurrently and on the fly, PocketBase also provides a
thin wrapper around the standard library in the
[`github.com/pocketbase/pocketbase/tools/template`](https://pkg.go.dev/github.com/pocketbase/pocketbase/tools/template)
utility package.
`import "github.com/pocketbase/pocketbase/tools/template"
data := map[string]any{"name": "John"}
html, err := template.NewRegistry().LoadFiles(
"views/base.html",
"views/partial1.html",
"views/partial2.html",
).Render(data)`
The general flow when working with composed and nested templates is that you create "base" template(s)
that defines various placeholders using the
`{{template "placeholderName" .}}` or
`{{block "placeholderName" .}}default...{{end}}` actions.
Then in the partials, you define the content for those placeholders using the
`{{define "placeholderName"}}custom...{{end}}` action.
The dot object ( `.`) in the above represents the data passed to the templates
via the `Render(data)` method.
By default the templates apply contextual (HTML, JS, CSS, URI) auto escaping so the generated template
content should be injection-safe. To render raw/verbatim trusted content in the templates you can use the
builtin `raw` function (e.g. `{{.content|raw}}`).
For more information about the template syntax please refer to the
[_html/template_](https://pkg.go.dev/html/template#hdr-A_fuller_picture)
and
[_text/template_](https://pkg.go.dev/text/template)
package godocs.
**Another great resource is also the Hashicorp's**
**[Learn Go Template Syntax](https://developer.hashicorp.com/nomad/tutorials/templates/go-template-syntax)**
**tutorial.**
### [Example HTML page with layout](https://pocketbase.io/docs/go-rendering-templates/\#example-html-page-with-layout)
Consider the following app directory structure:
`myapp/
views/
layout.html
hello.html
main.go`
We define the content for `layout.html` as:
`
{{block "title" .}}Default app title{{end}}
Header...
{{block "body" .}}
Default app body...
{{end}}
Footer...
`
We define the content for `hello.html` as:
`{{define "title"}}
Page 1
{{end}}
{{define "body"}}
Hello from {{.name}}
{{end}}`
Then to output the final page, we'll register a custom `/hello/:name` route:
`// main.go
package main
import (
"log"
"net/http"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/core"
"github.com/pocketbase/pocketbase/tools/template"
)
func main() {
app := pocketbase.New()
app.OnServe().BindFunc(func(se *core.ServeEvent) error {
// this is safe to be used by multiple goroutines
// (it acts as store for the parsed templates)
registry := template.NewRegistry()
se.Router.GET("/hello/{name}", func(e *core.RequestEvent) error {
name := e.Request.PathValue("name")
html, err := registry.LoadFiles(
"views/layout.html",
"views/hello.html",
).Render(map[string]any{
"name": name,
})
if err != nil {
// or redirect to a dedicated 404 HTML page
return e.NotFoundError("", err)
}
return e.HTML(http.StatusOK, html)
})
return se.Next()
})
if err := app.Start(); err != nil {
log.Fatal(err)
}
}`
* * *
[Prev: Sending emails](https://pocketbase.io/docs/go-sending-emails) [Next: Console commands](https://pocketbase.io/docs/go-console-commands)
## Working with Relations
Working with relations
- [Overview](https://pocketbase.io/docs/working-with-relations/#overview)
- [Prepend/Append to multiple relation](https://pocketbase.io/docs/working-with-relations/#prependappend-to-multiple-relation)
- [Remove from multiple relation](https://pocketbase.io/docs/working-with-relations/#remove-from-multiple-relation)
- [Expanding relations](https://pocketbase.io/docs/working-with-relations/#expanding-relations)
- [Back-relations](https://pocketbase.io/docs/working-with-relations/#back-relations)
- [Back-relation caveats](https://pocketbase.io/docs/working-with-relations/#back-relation-caveats)
### [Overview](https://pocketbase.io/docs/working-with-relations/\#overview)
Let's assume that we have the following collections structure:
![Expand diagram]()
The `relation` fields follow the same rules as any other collection field and can be set/modified
by directly updating the field value - with a record id or array of ids, in case a multiple relation is used.
Below is an example that shows creating a new **posts** record with 2 assigned tags.
JavaScript
Dart
`import PocketBase from 'pocketbase';
const pb = new PocketBase('http://127.0.0.1:8090');
...
const post = await pb.collection('posts').create({
'title': 'Lorem ipsum...',
'tags': ['TAG_ID1', 'TAG_ID2'],
});`
`import 'package:pocketbase/pocketbase.dart';
final pb = PocketBase('http://127.0.0.1:8090');
...
final post = await pb.collection('posts').create(body: {
'title': 'Lorem ipsum...',
'tags': ['TAG_ID1', 'TAG_ID2'],
});`
### [Prepend/Append to multiple relation](https://pocketbase.io/docs/working-with-relations/\#prependappend-to-multiple-relation)
To prepend/append a single or multiple relation id(s) to an existing value you can use the
`+` field modifier:
JavaScript
Dart
`import PocketBase from 'pocketbase';
const pb = new PocketBase('http://127.0.0.1:8090');
...
const post = await pb.collection('posts').update('POST_ID', {
// prepend single tag
'+tags': 'TAG_ID1',
// append multiple tags at once
'tags+': ['TAG_ID1', 'TAG_ID2'],
})`
`import 'package:pocketbase/pocketbase.dart';
final pb = PocketBase('http://127.0.0.1:8090');
...
final post = await pb.collection('posts').update('POST_ID', body: {
// prepend single tag
'+tags': 'TAG_ID1',
// append multiple tags at once
'tags+': ['TAG_ID1', 'TAG_ID2'],
})`
### [Remove from multiple relation](https://pocketbase.io/docs/working-with-relations/\#remove-from-multiple-relation)
To remove a single or multiple relation id(s) from an existing value you can use the
`-` field modifier:
JavaScript
Dart
`import PocketBase from 'pocketbase';
const pb = new PocketBase('http://127.0.0.1:8090');
...
const post = await pb.collection('posts').update('POST_ID', {
// remove single tag
'tags-': 'TAG_ID1',
// remove multiple tags at once
'tags-': ['TAG_ID1', 'TAG_ID2'],
})`
`import 'package:pocketbase/pocketbase.dart';
final pb = PocketBase('http://127.0.0.1:8090');
...
final post = await pb.collection('posts').update('POST_ID', body: {
// remove single tag
'tags-': 'TAG_ID1',
// remove multiple tags at once
'tags-': ['TAG_ID1', 'TAG_ID2'],
})`
### [Expanding relations](https://pocketbase.io/docs/working-with-relations/\#expanding-relations)
You can also expand record relation fields directly in the returned response without making additional
requests by using the `expand` query parameter, e.g. `?expand=user,post.tags`
Only the relations that the request client can **View** (aka. satisfies the relation
collection's **View API Rule**) will be expanded.
Nested relation references in `expand`, `filter` or `sort` are supported
via dot-notation and up to 6-levels depth.
For example, to list all **comments** with their **user** relation expanded, we can
do the following:
JavaScript
Dart
`await pb.collection("comments").getList(1, 30, { expand: "user" })`
`await pb.collection("comments").getList(perPage: 30, expand: "user")`
`{
"page": 1,
"perPage": 30,
"totalPages": 1,
"totalItems": 20,
"items": [\
{\
"id": "lmPJt4Z9CkLW36z",\
"collectionId": "BHKW36mJl3ZPt6z",\
"collectionName": "comments",\
"created": "2022-01-01 01:00:00.456Z",\
"updated": "2022-01-01 02:15:00.456Z",\
"post": "WyAw4bDrvws6gGl",\
"user": "FtHAW9feB5rze7D",\
"message": "Example message...",\
"expand": {\
"user": {\
"id": "FtHAW9feB5rze7D",\
"collectionId": "srmAo0hLxEqYF7F",\
"collectionName": "users",\
"created": "2022-01-01 00:00:00.000Z",\
"updated": "2022-01-01 00:00:00.000Z",\
"username": "users54126",\
"verified": false,\
"emailVisibility": false,\
"name": "John Doe"\
}\
}\
},\
...\
]
}`
### [Back-relations](https://pocketbase.io/docs/working-with-relations/\#back-relations)
PocketBase supports also `filter`, `sort` and `expand` for
**back-relations**
\- relations where the associated `relation` field is not in the main collection.
The following notation is used: `referenceCollection_via_relField` (ex.
`comments_via_post`).
For example, lets list the **posts** that has at least one **comments** record
containing the word _"hello"_:
JavaScript
Dart
`await pb.collection("posts").getList(1, 30, {
filter: "comments_via_post.message ?~ 'hello'"
expand: "comments_via_post.user",
})`
`await pb.collection("posts").getList(
perPage: 30,
filter: "comments_via_post.message ?~ 'hello'"
expand: "comments_via_post.user",
)`
`{
"page": 1,
"perPage": 30,
"totalPages": 2,
"totalItems": 45,
"items": [\
{\
"id": "WyAw4bDrvws6gGl",\
"collectionId": "1rAwHJatkTNCUIN",\
"collectionName": "posts",\
"created": "2022-01-01 01:00:00.456Z",\
"updated": "2022-01-01 02:15:00.456Z",\
"title": "Lorem ipsum dolor sit...",\
"expand": {\
"comments_via_post": [\
{\
"id": "lmPJt4Z9CkLW36z",\
"collectionId": "BHKW36mJl3ZPt6z",\
"collectionName": "comments",\
"created": "2022-01-01 01:00:00.456Z",\
"updated": "2022-01-01 02:15:00.456Z",\
"post": "WyAw4bDrvws6gGl",\
"user": "FtHAW9feB5rze7D",\
"message": "lorem ipsum...",\
"expand": {\
"user": {\
"id": "FtHAW9feB5rze7D",\
"collectionId": "srmAo0hLxEqYF7F",\
"collectionName": "users",\
"created": "2022-01-01 00:00:00.000Z",\
"updated": "2022-01-01 00:00:00.000Z",\
"username": "users54126",\
"verified": false,\
"emailVisibility": false,\
"name": "John Doe"\
}\
}\
},\
{\
"id": "tu4Z9CkLW36mPJz",\
"collectionId": "BHKW36mJl3ZPt6z",\
"collectionName": "comments",\
"created": "2022-01-01 01:10:00.123Z",\
"updated": "2022-01-01 02:39:00.456Z",\
"post": "WyAw4bDrvws6gGl",\
"user": "FtHAW9feB5rze7D",\
"message": "hello...",\
"expand": {\
"user": {\
"id": "FtHAW9feB5rze7D",\
"collectionId": "srmAo0hLxEqYF7F",\
"collectionName": "users",\
"created": "2022-01-01 00:00:00.000Z",\
"updated": "2022-01-01 00:00:00.000Z",\
"username": "users54126",\
"verified": false,\
"emailVisibility": false,\
"name": "John Doe"\
}\
}\
},\
...\
]\
}\
},\
...\
]
}`
###### [Back-relation caveats](https://pocketbase.io/docs/working-with-relations/\#back-relation-caveats)
- By default the back-relation reference is resolved as a dynamic
_multiple_ relation field, even when the back-relation field itself is marked as
_single_.
This is because the main record could have more than one _single_
back-relation reference (see in the above example that the `comments_via_post`
expand is returned as array, although the original `comments.post` field is a
_single_ relation).
The only case where the back-relation will be treated as a _single_
relation field is when there is
`UNIQUE` index constraint defined on the relation field.
- Back-relation `expand` is limited to max 1000 records per relation field. If you
need to fetch larger number of back-related records a better approach could be to send a
separate paginated `getList()` request to the back-related collection to avoid transferring
large JSON payloads and to reduce the memory usage.
* * *
[Prev: Files upload and handling](https://pocketbase.io/docs/files-handling) [Next: Extending PocketBase](https://pocketbase.io/docs/use-as-framework)
## PocketBase Event Hooks
Event hooks
The standard way to modify PocketBase is through
**event hooks** in your Go code.
All hooks have 3 main methods:
- [`Bind(handler)`](https://pkg.go.dev/github.com/pocketbase/pocketbase/tools/hook#Hook.Bind)
adds a new handler to the specified event hook. A handler has 3 fields:
- `Id` _(optional)_ \- the name of the handler (could be used
as argument for `Unbind`)
- `Priority` _(optional)_ \- the execution order of the handler
(if empty fallbacks to the order of registration in the code).
- `Func` _(required)_ \- the handler function.
- [`BindFunc(func)`](https://pkg.go.dev/github.com/pocketbase/pocketbase/tools/hook#Hook.BindFunc)
is similar to `Bind` but registers a new handler from just the provided function.
The registered handler is added with a default 0 priority and the id is autogenerated (the returned string
value).
- [`Trigger(event, oneOffHandlerFuncs...)`](https://pkg.go.dev/github.com/pocketbase/pocketbase/tools/hook#Hook.Trigger)
triggers the event hook.
_This method rarely has to be called manually by users._
To remove an already registered hook handler, you can use the handler id and pass it to
`Unbind(id)` or remove all handlers with
`UnbindAll()` ( _!including system handlers_).
All hook handler functions share the same `func(e T) error` signature and expect
the user to call `e.Next()` if they want to proceed with the execution chain.
If you need to access the app instance from inside a hook handler, prefer using the
`e.App` field instead of reusing a parent scope app variable because the hook could be part
of a DB transaction and can cause deadlock.
Also avoid using global mutex locks inside a hook handler because it could be invoked recursively
(e.g. cascade delete) and can cause deadlock.
You can explore all available hooks below:
- [App hooks](https://pocketbase.io/docs/go-event-hooks/#app-hooks)
- [Mailer hooks](https://pocketbase.io/docs/go-event-hooks/#mailer-hooks)
- [Realtime hooks](https://pocketbase.io/docs/go-event-hooks/#realtime-hooks)
- [Record model hooks](https://pocketbase.io/docs/go-event-hooks/#record-model-hooks)
- [Collection model hooks](https://pocketbase.io/docs/go-event-hooks/#collection-model-hooks)
- [Request hooks](https://pocketbase.io/docs/go-event-hooks/#request-hooks)
- [Base model hooks](https://pocketbase.io/docs/go-event-hooks/#base-model-hooks)
### [App hooks](https://pocketbase.io/docs/go-event-hooks/\#app-hooks)
**[OnBootstrap](https://pocketbase.io/docs/go-event-hooks/#onbootstrap)**
`OnBootstrap` hook is triggered when initializing the main
application resources (db, app settings, etc).
Note that attempting to access the database before the `e.Next()` call will result in an error.
`package main
import (
"log"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/core"
)
func main() {
app := pocketbase.New()
app.OnBootstrap().BindFunc(func(e *core.BootstrapEvent) error {
if err := e.Next(); err != nil {
return err
}
// e.App
return nil
})
if err := app.Start(); err != nil {
log.Fatal(err)
}
}`
**[OnServe](https://pocketbase.io/docs/go-event-hooks/#onserve)**
`OnServe` hook is triggered when the app web server is started
(after starting the TCP listener but before initializing the blocking serve task),
allowing you to adjust its options and attach new routes or middlewares.
`package main
import (
"log"
"net/http"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/apis"
"github.com/pocketbase/pocketbase/core"
)
func main() {
app := pocketbase.New()
app.OnServe().BindFunc(func(e *core.ServeEvent) error {
// register new "GET /hello" route
e.Router.GET("/hello", func(e *core.RequestEvent) error {
return e.String(200, "Hello world!")
}).Bind(apis.RequireAuth())
return e.Next()
})
if err := app.Start(); err != nil {
log.Fatal(err)
}
}`
**[OnSettingsReload](https://pocketbase.io/docs/go-event-hooks/#onsettingsreload)**
`OnSettingsReload` hook is triggered every time when the `App.Settings()`
is being replaced with a new state.
Calling `e.App.Settings()` after `e.Next()` returns the new state.
`package main
import (
"log"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/core"
)
func main() {
app := pocketbase.New()
app.OnSettingsReload().BindFunc(func(e *core.SettingsReloadEvent) error {
if err := e.Next(); err != nil {
return err
}
// e.App.Settings()
return nil
})
if err := app.Start(); err != nil {
log.Fatal(err)
}
}`
**[OnBackupCreate](https://pocketbase.io/docs/go-event-hooks/#onbackupcreate)**
`OnBackupCreate` is triggered on each `App.CreateBackup` call.
`package main
import (
"log"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/core"
)
func main() {
app := pocketbase.New()
app.OnBackupCreate().BindFunc(func(e *core.BackupEvent) error {
// e.App
// e.Name - the name of the backup to create
// e.Exclude - list of pb_data dir entries to exclude from the backup
return e.Next()
})
if err := app.Start(); err != nil {
log.Fatal(err)
}
}`
**[OnBackupRestore](https://pocketbase.io/docs/go-event-hooks/#onbackuprestore)**
`OnBackupRestore` is triggered before app backup restore (aka. on `App.RestoreBackup` call).
`package main
import (
"log"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/core"
)
func main() {
app := pocketbase.New()
app.OnBackupRestore().BindFunc(func(e *core.BackupEvent) error {
// e.App
// e.Name - the name of the backup to restore
// e.Exclude - list of dir entries to exclude from the backup
return e.Next()
})
if err := app.Start(); err != nil {
log.Fatal(err)
}
}`
**[OnTerminate](https://pocketbase.io/docs/go-event-hooks/#onterminate)**
`OnTerminate` hook is triggered when the app is in the process
of being terminated (ex. on `SIGTERM` signal).
Note that the app could be terminated abruptly without awaiting the hook completion.
`package main
import (
"log"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/core"
)
func main() {
app := pocketbase.New()
app.OnTerminate().BindFunc(func(e *core.TerminateEvent) error {
// e.App
// e.IsRestart
return e.Next()
})
if err := app.Start(); err != nil {
log.Fatal(err)
}
}`
### [Mailer hooks](https://pocketbase.io/docs/go-event-hooks/\#mailer-hooks)
**[OnMailerSend](https://pocketbase.io/docs/go-event-hooks/#onmailersend)**
`OnMailerSend` hook is triggered every time when a new email is
being send using the `App.NewMailClient()` instance.
It allows intercepting the email message or to use a custom mailer client.
`package main
import (
"log"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/core"
)
func main() {
app := pocketbase.New()
app.OnMailerSend().BindFunc(func(e *core.MailerEvent) error {
// e.App
// e.Mailer
// e.Message
// ex. change the mail subject
e.Message.Subject = "new subject"
return e.Next()
})
if err := app.Start(); err != nil {
log.Fatal(err)
}
}`
**[OnMailerRecordAuthAlertSend](https://pocketbase.io/docs/go-event-hooks/#onmailerrecordauthalertsend)**
`OnMailerRecordAuthAlertSend` hook is triggered when
sending a new device login auth alert email, allowing you to
intercept and customize the email message that is being sent.
`package main
import (
"log"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/core"
)
func main() {
app := pocketbase.New()
app.OnMailerRecordAuthAlertSend().BindFunc(func(e *core.MailerRecordEvent) error {
// e.App
// e.Mailer
// e.Message
// e.Record
// e.Meta
// ex. change the mail subject
e.Message.Subject = "new subject"
return e.Next()
})
if err := app.Start(); err != nil {
log.Fatal(err)
}
}`
**[OnMailerRecordPasswordResetSend](https://pocketbase.io/docs/go-event-hooks/#onmailerrecordpasswordresetsend)**
`OnMailerRecordPasswordResetSend` hook is triggered when
sending a password reset email to an auth record, allowing
you to intercept and customize the email message that is being sent.
`package main
import (
"log"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/core"
)
func main() {
app := pocketbase.New()
app.OnMailerRecordPasswordResetSend().BindFunc(func(e *core.MailerRecordEvent) error {
// e.App
// e.Mailer
// e.Message
// e.Record
// e.Meta
// ex. change the mail subject
e.Message.Subject = "new subject"
return e.Next()
})
if err := app.Start(); err != nil {
log.Fatal(err)
}
}`
**[OnMailerRecordVerificationSend](https://pocketbase.io/docs/go-event-hooks/#onmailerrecordverificationsend)**
`OnMailerRecordVerificationSend` hook is triggered when
sending a verification email to an auth record, allowing
you to intercept and customize the email message that is being sent.
`package main
import (
"log"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/core"
)
func main() {
app := pocketbase.New()
app.OnMailerRecordVerificationSend().BindFunc(func(e *core.MailerRecordEvent) error {
// e.App
// e.Mailer
// e.Message
// e.Record
// e.Meta
// ex. change the mail subject
e.Message.Subject = "new subject"
return e.Next()
})
if err := app.Start(); err != nil {
log.Fatal(err)
}
}`
**[OnMailerRecordEmailChangeSend](https://pocketbase.io/docs/go-event-hooks/#onmailerrecordemailchangesend)**
`OnMailerRecordEmailChangeSend` hook is triggered when sending a
confirmation new address email to an auth record, allowing
you to intercept and customize the email message that is being sent.
`package main
import (
"log"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/core"
)
func main() {
app := pocketbase.New()
app.OnMailerRecordEmailChangeSend().BindFunc(func(e *core.MailerRecordEvent) error {
// e.App
// e.Mailer
// e.Message
// e.Record
// e.Meta
// ex. change the mail subject
e.Message.Subject = "new subject"
return e.Next()
})
if err := app.Start(); err != nil {
log.Fatal(err)
}
}`
**[OnMailerRecordOTPSend](https://pocketbase.io/docs/go-event-hooks/#onmailerrecordotpsend)**
`OnMailerRecordOTPSend` hook is triggered when sending an OTP email
to an auth record, allowing you to intercept and customize the
email message that is being sent.
`package main
import (
"log"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/core"
)
func main() {
app := pocketbase.New()
app.OnMailerRecordOTPSend().BindFunc(func(e *core.MailerRecordEvent) error {
// e.App
// e.Mailer
// e.Message
// e.Record
// e.Meta
// ex. change the mail subject
e.Message.Subject = "new subject"
return e.Next()
})
if err := app.Start(); err != nil {
log.Fatal(err)
}
}`
### [Realtime hooks](https://pocketbase.io/docs/go-event-hooks/\#realtime-hooks)
**[OnRealtimeConnectRequest](https://pocketbase.io/docs/go-event-hooks/#onrealtimeconnectrequest)**
`OnRealtimeConnectRequest` hook is triggered when establishing the SSE client connection.
Any execution after e.Next() of a hook handler happens after the client disconnects.
`package main
import (
"log"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/core"
)
func main() {
app := pocketbase.New()
app.OnRealtimeConnectRequest().BindFunc(func(e *core.RealtimeConnectRequestEvent) error {
// e.App
// e.Client
// e.IdleTimeout
// and all RequestEvent fields...
return e.Next()
})
if err := app.Start(); err != nil {
log.Fatal(err)
}
}`
**[OnRealtimeSubscribeRequest](https://pocketbase.io/docs/go-event-hooks/#onrealtimesubscriberequest)**
`OnRealtimeSubscribeRequest` hook is triggered when updating the
client subscriptions, allowing you to further validate and
modify the submitted change.
`package main
import (
"log"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/core"
)
func main() {
app := pocketbase.New()
app.OnRealtimeSubscribeRequest().BindFunc(func(e *core.RealtimeSubscribeRequestEvent) error {
// e.App
// e.Client
// e.Subscriptions
// and all RequestEvent fields...
return e.Next()
})
if err := app.Start(); err != nil {
log.Fatal(err)
}
}`
**[OnRealtimeMessageSend](https://pocketbase.io/docs/go-event-hooks/#onrealtimemessagesend)**
`OnRealtimeMessageSend` hook is triggered when sending an SSE message to a client.
`package main
import (
"log"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/core"
)
func main() {
app := pocketbase.New()
app.OnRealtimeMessageSend().BindFunc(func(e *core.RealtimeMessageEvent) error {
// e.App
// e.Client
// e.Message
// and all original connect RequestEvent fields...
return e.Next()
})
if err := app.Start(); err != nil {
log.Fatal(err)
}
}`
### [Record model hooks](https://pocketbase.io/docs/go-event-hooks/\#record-model-hooks)
These are lower level Record model hooks and could be triggered from anywhere (custom console command, scheduled cron job, when calling `e.Save(record)`, etc.) and therefore they have no access to the request context!
If you want to intercept the builtin Web APIs and to access their request body, query parameters, headers or the request auth state, then please use the designated
[Record `*Request` hooks](https://pocketbase.io/docs/go-event-hooks/#request-hooks)
.
**[OnRecordEnrich](https://pocketbase.io/docs/go-event-hooks/#onrecordenrich)**
`OnRecordEnrich` is triggered every time when a record is enriched
\- as part of the builtin Record responses, during realtime message serialization, or when `apis.EnrichRecord` is invoked.
It could be used for example to redact/hide or add computed temporary
Record model props only for the specific request info.
`package main
import (
"log"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/core"
)
func main() {
app := pocketbase.New()
app.OnRecordEnrich("posts").BindFunc(func(e *core.RecordEnrichEvent) error {
// hide one or more fields
e.Record.Hide("role")
// add new custom field for registered users
if e.RequestInfo.Auth != nil && e.RequestInfo.Auth.Collection().Name == "users" {
e.Record.WithCustomData(true) // for security requires explicitly allowing it
e.Record.Set("computedScore", e.Record.GetInt("score") * e.RequestInfo.Auth.GetInt("base"))
}
return e.Next()
})
if err := app.Start(); err != nil {
log.Fatal(err)
}
}`
**[OnRecordValidate](https://pocketbase.io/docs/go-event-hooks/#onrecordvalidate)**
`OnRecordValidate` is a Record proxy model hook of `OnModelValidate`.
`OnRecordValidate` is called every time when a Record is being validated,
e.g. triggered by `App.Validate()` or `App.Save()`.
`package main
import (
"log"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/core"
)
func main() {
app := pocketbase.New()
// fires for every record
app.OnRecordValidate().BindFunc(func(e *core.RecordEvent) error {
// e.App
// e.Record
return e.Next()
})
// fires only for "users" and "articles" records
app.OnRecordValidate("users", "articles").BindFunc(func(e *core.RecordEvent) error {
// e.App
// e.Record
return e.Next()
})
if err := app.Start(); err != nil {
log.Fatal(err)
}
}`
###### [Record model create hooks](https://pocketbase.io/docs/go-event-hooks/\#record-model-create-hooks)
**[OnRecordCreate](https://pocketbase.io/docs/go-event-hooks/#onrecordcreate)**
`OnRecordCreate` is a Record proxy model hook of `OnModelCreate`.
`OnRecordCreate` is triggered every time when a new Record is being created,
e.g. triggered by `App.Save()`.
Operations BEFORE the `e.Next()` execute before the Record validation
and the INSERT DB statement.
Operations AFTER the `e.Next()` execute after the Record validation
and the INSERT DB statement.
Note that successful execution doesn't guarantee that the Record
is persisted in the database since its wrapping transaction may
not have been committed yet.
If you want to listen to only the actual persisted events, you can
bind to `OnRecordAfterCreateSuccess` or `OnRecordAfterCreateError` hooks.
`package main
import (
"log"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/core"
)
func main() {
app := pocketbase.New()
// fires for every record
app.OnRecordCreate().BindFunc(func(e *core.RecordEvent) error {
// e.App
// e.Record
return e.Next()
})
// fires only for "users" and "articles" records
app.OnRecordCreate("users", "articles").BindFunc(func(e *core.RecordEvent) error {
// e.App
// e.Record
return e.Next()
})
if err := app.Start(); err != nil {
log.Fatal(err)
}
}`
**[OnRecordCreateExecute](https://pocketbase.io/docs/go-event-hooks/#onrecordcreateexecute)**
`OnRecordCreateExecute` is a Record proxy model hook of `OnModelCreateExecute`.
`OnRecordCreateExecute` is triggered after successful Record validation
and right before the model INSERT DB statement execution.
Usually it is triggered as part of the `App.Save()` in the following firing order:
`OnRecordCreate`
-\> `OnRecordValidate` (skipped with `App.SaveNoValidate()`)
-\> `OnRecordCreateExecute`
Note that successful execution doesn't guarantee that the Record
is persisted in the database since its wrapping transaction may
not have been committed yet.
If you want to listen to only the actual persisted events, you can
bind to `OnRecordAfterCreateSuccess` or `OnRecordAfterCreateError` hooks.
`package main
import (
"log"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/core"
)
func main() {
app := pocketbase.New()
// fires for every record
app.OnRecordCreateExecute().BindFunc(func(e *core.RecordEvent) error {
// e.App
// e.Record
return e.Next()
})
// fires only for "users" and "articles" records
app.OnRecordCreateExecute("users", "articles").BindFunc(func(e *core.RecordEvent) error {
// e.App
// e.Record
return e.Next()
})
if err := app.Start(); err != nil {
log.Fatal(err)
}
}`
**[OnRecordAfterCreateSuccess](https://pocketbase.io/docs/go-event-hooks/#onrecordaftercreatesuccess)**
`OnRecordAfterCreateSuccess` is a Record proxy model hook of `OnModelAfterCreateSuccess`.
`OnRecordAfterCreateSuccess` is triggered after each successful
Record DB create persistence.
Note that when a Record is persisted as part of a transaction,
this hook is delayed and executed only AFTER the transaction has been committed.
This hook is NOT triggered in case the transaction fails/rollbacks.
`package main
import (
"log"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/core"
)
func main() {
app := pocketbase.New()
// fires for every record
app.OnRecordAfterCreateSuccess().BindFunc(func(e *core.RecordEvent) error {
// e.App
// e.Record
return e.Next()
})
// fires only for "users" and "articles" records
app.OnRecordAfterCreateSuccess("users", "articles").BindFunc(func(e *core.RecordEvent) error {
// e.App
// e.Record
return e.Next()
})
if err := app.Start(); err != nil {
log.Fatal(err)
}
}`
**[OnRecordAfterCreateError](https://pocketbase.io/docs/go-event-hooks/#onrecordaftercreateerror)**
`OnRecordAfterCreateError` is a Record proxy model hook of `OnModelAfterCreateError`.
`OnRecordAfterCreateError` is triggered after each failed
Record DB create persistence.
Note that the execution of this hook is either immediate or delayed
depending on the error:
- **immediate** on `App.Save()` failure
- **delayed** on transaction rollback
`package main
import (
"log"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/core"
)
func main() {
app := pocketbase.New()
// fires for every record
app.OnRecordAfterCreateError().BindFunc(func(e *core.RecordErrorEvent) error {
// e.App
// e.Record
// e.Error
return e.Next()
})
// fires only for "users" and "articles" records
app.OnRecordAfterCreateError("users", "articles").BindFunc(func(e *core.RecordErrorEvent) error {
// e.App
// e.Record
// e.Error
return e.Next()
})
if err := app.Start(); err != nil {
log.Fatal(err)
}
}`
###### [Record model update hooks](https://pocketbase.io/docs/go-event-hooks/\#record-model-update-hooks)
**[OnRecordUpdate](https://pocketbase.io/docs/go-event-hooks/#onrecordupdate)**
`OnRecordUpdate` is a Record proxy model hook of `OnModelUpdate`.
`OnRecordUpdate` is triggered every time when a new Record is being updated,
e.g. triggered by `App.Save()`.
Operations BEFORE the `e.Next()` execute before the Record validation
and the UPDATE DB statement.
Operations AFTER the `e.Next()` execute after the Record validation
and the UPDATE DB statement.
Note that successful execution doesn't guarantee that the Record
is persisted in the database since its wrapping transaction may
not have been committed yet.
If you want to listen to only the actual persisted events, you can
bind to `OnRecordAfterUpdateSuccess` or `OnRecordAfterUpdateError` hooks.
`package main
import (
"log"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/core"
)
func main() {
app := pocketbase.New()
// fires for every record
app.OnRecordUpdate().BindFunc(func(e *core.RecordEvent) error {
// e.App
// e.Record
return e.Next()
})
// fires only for "users" and "articles" records
app.OnRecordUpdate("users", "articles").BindFunc(func(e *core.RecordEvent) error {
// e.App
// e.Record
return e.Next()
})
if err := app.Start(); err != nil {
log.Fatal(err)
}
}`
**[OnRecordUpdateExecute](https://pocketbase.io/docs/go-event-hooks/#onrecordupdateexecute)**
`OnRecordUpdateExecute` is a Record proxy model hook of `OnModelUpdateExecute`.
`OnRecordUpdateExecute` is triggered after successful Record validation
and right before the model UPDATE DB statement execution.
Usually it is triggered as part of the `App.Save()` in the following firing order:
`OnRecordUpdate`
-\> `OnRecordValidate` (skipped with `App.SaveNoValidate()`)
-\> `OnRecordUpdateExecute`
Note that successful execution doesn't guarantee that the Record
is persisted in the database since its wrapping transaction may
not have been committed yet.
If you want to listen to only the actual persisted events, you can
bind to `OnRecordAfterUpdateSuccess` or `OnRecordAfterUpdateError` hooks.
`package main
import (
"log"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/core"
)
func main() {
app := pocketbase.New()
// fires for every record
app.OnRecordUpdateExecute().BindFunc(func(e *core.RecordEvent) error {
// e.App
// e.Record
return e.Next()
})
// fires only for "users" and "articles" records
app.OnRecordUpdateExecute("users", "articles").BindFunc(func(e *core.RecordEvent) error {
// e.App
// e.Record
return e.Next()
})
if err := app.Start(); err != nil {
log.Fatal(err)
}
}`
**[OnRecordAfterUpdateSuccess](https://pocketbase.io/docs/go-event-hooks/#onrecordafterupdatesuccess)**
`OnRecordAfterUpdateSuccess` is a Record proxy model hook of `OnModelAfterUpdateSuccess`.
`OnRecordAfterUpdateSuccess` is triggered after each successful
Record DB update persistence.
Note that when a Record is persisted as part of a transaction,
this hook is delayed and executed only AFTER the transaction has been committed.
This hook is NOT triggered in case the transaction fails/rollbacks.
`package main
import (
"log"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/core"
)
func main() {
app := pocketbase.New()
// fires for every record
app.OnRecordAfterUpdateSuccess().BindFunc(func(e *core.RecordEvent) error {
// e.App
// e.Record
return e.Next()
})
// fires only for "users" and "articles" records
app.OnRecordAfterUpdateSuccess("users", "articles").BindFunc(func(e *core.RecordEvent) error {
// e.App
// e.Record
return e.Next()
})
if err := app.Start(); err != nil {
log.Fatal(err)
}
}`
**[OnRecordAfterUpdateError](https://pocketbase.io/docs/go-event-hooks/#onrecordafterupdateerror)**
`OnRecordAfterUpdateError` is a Record proxy model hook of `OnModelAfterUpdateError`.
`OnRecordAfterUpdateError` is triggered after each failed
Record DB update persistence.
Note that the execution of this hook is either immediate or delayed
depending on the error:
- **immediate** on `App.Save()` failure
- **delayed** on transaction rollback
`package main
import (
"log"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/core"
)
func main() {
app := pocketbase.New()
// fires for every record
app.OnRecordAfterUpdateError().BindFunc(func(e *core.RecordErrorEvent) error {
// e.App
// e.Record
// e.Error
return e.Next()
})
// fires only for "users" and "articles" records
app.OnRecordAfterUpdateError("users", "articles").BindFunc(func(e *core.RecordErrorEvent) error {
// e.App
// e.Record
// e.Error
return e.Next()
})
if err := app.Start(); err != nil {
log.Fatal(err)
}
}`
###### [Record model delete hooks](https://pocketbase.io/docs/go-event-hooks/\#record-model-delete-hooks)
**[OnRecordDelete](https://pocketbase.io/docs/go-event-hooks/#onrecorddelete)**
`OnRecordDelete` is a Record proxy model hook of `OnModelDelete`.
`OnRecordDelete` is triggered every time when a new Record is being deleted,
e.g. triggered by `App.Delete()`.
Operations BEFORE the `e.Next()` execute before the Record validation
and the UPDATE DB statement.
Operations AFTER the `e.Next()` execute after the Record validation
and the UPDATE DB statement.
Note that successful execution doesn't guarantee that the Record
is deleted from the database since its wrapping transaction may
not have been committed yet.
If you want to listen to only the actual persisted deleted events, you can
bind to `OnRecordAfterDeleteSuccess` or `OnRecordAfterDeleteError` hooks.
`package main
import (
"log"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/core"
)
func main() {
app := pocketbase.New()
// fires for every record
app.OnRecordDelete().BindFunc(func(e *core.RecordEvent) error {
// e.App
// e.Record
return e.Next()
})
// fires only for "users" and "articles" records
app.OnRecordDelete("users", "articles").BindFunc(func(e *core.RecordEvent) error {
// e.App
// e.Record
return e.Next()
})
if err := app.Start(); err != nil {
log.Fatal(err)
}
}`
**[OnRecordDeleteExecute](https://pocketbase.io/docs/go-event-hooks/#onrecorddeleteexecute)**
`OnRecordDeleteExecute` is a Record proxy model hook of `OnModelDeleteExecute`.
`OnRecordDeleteExecute` is triggered after the internal delete checks and
right before the Record the model DELETE DB statement execution.
Usually it is triggered as part of the `App.Delete()` in the following firing order:
`OnRecordDelete`
-\>
internal delete checks
-\> `OnRecordDeleteExecute`
Note that successful execution doesn't guarantee that the Record
is deleted from the database since its wrapping transaction may
not have been committed yet.
If you want to listen to only the actual persisted events, you can
bind to `OnRecordAfterDeleteSuccess` or `OnRecordAfterDeleteError` hooks.
`package main
import (
"log"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/core"
)
func main() {
app := pocketbase.New()
// fires for every record
app.OnRecordDeleteExecute().BindFunc(func(e *core.RecordEvent) error {
// e.App
// e.Record
return e.Next()
})
// fires only for "users" and "articles" records
app.OnRecordDeleteExecute("users", "articles").BindFunc(func(e *core.RecordEvent) error {
// e.App
// e.Record
return e.Next()
})
if err := app.Start(); err != nil {
log.Fatal(err)
}
}`
**[OnRecordAfterDeleteSuccess](https://pocketbase.io/docs/go-event-hooks/#onrecordafterdeletesuccess)**
`OnRecordAfterDeleteSuccess` is a Record proxy model hook of `OnModelAfterDeleteSuccess`.
`OnRecordAfterDeleteSuccess` is triggered after each successful
Record DB delete persistence.
Note that when a Record is deleted as part of a transaction,
this hook is delayed and executed only AFTER the transaction has been committed.
This hook is NOT triggered in case the transaction fails/rollbacks.
`package main
import (
"log"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/core"
)
func main() {
app := pocketbase.New()
// fires for every record
app.OnRecordAfterDeleteSuccess().BindFunc(func(e *core.RecordEvent) error {
// e.App
// e.Record
return e.Next()
})
// fires only for "users" and "articles" records
app.OnRecordAfterDeleteSuccess("users", "articles").BindFunc(func(e *core.RecordEvent) error {
// e.App
// e.Record
return e.Next()
})
if err := app.Start(); err != nil {
log.Fatal(err)
}
}`
**[OnRecordAfterDeleteError](https://pocketbase.io/docs/go-event-hooks/#onrecordafterdeleteerror)**
`OnRecordAfterDeleteError` is a Record proxy model hook of `OnModelAfterDeleteError`.
`OnRecordAfterDeleteError` is triggered after each failed
Record DB delete persistence.
Note that the execution of this hook is either immediate or delayed
depending on the error:
- **immediate** on `App.Delete()` failure
- **delayed** on transaction rollback
`package main
import (
"log"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/core"
)
func main() {
app := pocketbase.New()
// fires for every record
app.OnRecordAfterDeleteError().BindFunc(func(e *core.RecordErrorEvent) error {
// e.App
// e.Record
// e.Error
return e.Next()
})
// fires only for "users" and "articles" records
app.OnRecordAfterDeleteError("users", "articles").BindFunc(func(e *core.RecordErrorEvent) error {
// e.App
// e.Record
// e.Error
return e.Next()
})
if err := app.Start(); err != nil {
log.Fatal(err)
}
}`
### [Collection model hooks](https://pocketbase.io/docs/go-event-hooks/\#collection-model-hooks)
These are lower level Collection model hooks and could be triggered from anywhere (custom console command, scheduled cron job, when calling `e.Save(collection)`, etc.) and therefore they have no access to the request context!
If you want to intercept the builtin Web APIs and to access their request body, query parameters, headers or the request auth state, then please use the designated
[Collection `*Request` hooks](https://pocketbase.io/docs/go-event-hooks/#collection-request-hooks)
.
**[OnCollectionValidate](https://pocketbase.io/docs/go-event-hooks/#oncollectionvalidate)**
`OnCollectionValidate` is a Collection proxy model hook of `OnModelValidate`.
`OnCollectionValidate` is called every time when a Collection is being validated,
e.g. triggered by `App.Validate()` or `App.Save()`.
`package main
import (
"log"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/core"
)
func main() {
app := pocketbase.New()
// fires for every collection
app.OnCollectionValidate().BindFunc(func(e *core.CollectionEvent) error {
// e.App
// e.Collection
return e.Next()
})
// fires only for "users" and "articles" collections
app.OnCollectionValidate("users", "articles").BindFunc(func(e *core.CollectionEvent) error {
// e.App
// e.Collection
return e.Next()
})
if err := app.Start(); err != nil {
log.Fatal(err)
}
}`
###### [Collection mode create hooks](https://pocketbase.io/docs/go-event-hooks/\#collection-mode-create-hooks)
**[OnCollectionCreate](https://pocketbase.io/docs/go-event-hooks/#oncollectioncreate)**
`OnCollectionCreate` is a Collection proxy model hook of `OnModelCreate`.
`OnCollectionCreate` is triggered every time when a new Collection is being created,
e.g. triggered by `App.Save()`.
Operations BEFORE the `e.Next()` execute before the Collection validation
and the INSERT DB statement.
Operations AFTER the `e.Next()` execute after the Collection validation
and the INSERT DB statement.
Note that successful execution doesn't guarantee that the Collection
is persisted in the database since its wrapping transaction may
not have been committed yet.
If you want to listen to only the actual persisted events, you can
bind to `OnCollectionAfterCreateSuccess` or `OnCollectionAfterCreateError` hooks.
`package main
import (
"log"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/core"
)
func main() {
app := pocketbase.New()
// fires for every collection
app.OnCollectionCreate().BindFunc(func(e *core.CollectionEvent) error {
// e.App
// e.Collection
return e.Next()
})
// fires only for "users" and "articles" collections
app.OnCollectionCreate("users", "articles").BindFunc(func(e *core.CollectionEvent) error {
// e.App
// e.Collection
return e.Next()
})
if err := app.Start(); err != nil {
log.Fatal(err)
}
}`
**[OnCollectionCreateExecute](https://pocketbase.io/docs/go-event-hooks/#oncollectioncreateexecute)**
`OnCollectionCreateExecute` is a Collection proxy model hook of `OnModelCreateExecute`.
`OnCollectionCreateExecute` is triggered after successful Collection validation
and right before the model INSERT DB statement execution.
Usually it is triggered as part of the `App.Save()` in the following firing order:
`OnCollectionCreate`
-\> `OnCollectionValidate` (skipped with `App.SaveNoValidate()`)
-\> `OnCollectionCreateExecute`
Note that successful execution doesn't guarantee that the Collection
is persisted in the database since its wrapping transaction may
not have been committed yet.
If you want to listen to only the actual persisted events, you can
bind to `OnCollectionAfterCreateSuccess` or `OnCollectionAfterCreateError` hooks.
`package main
import (
"log"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/core"
)
func main() {
app := pocketbase.New()
// fires for every collection
app.OnCollectionCreateExecute().BindFunc(func(e *core.CollectionEvent) error {
// e.App
// e.Collection
return e.Next()
})
// fires only for "users" and "articles" collections
app.OnCollectionCreateExecute("users", "articles").BindFunc(func(e *core.CollectionEvent) error {
// e.App
// e.Collection
return e.Next()
})
if err := app.Start(); err != nil {
log.Fatal(err)
}
}`
**[OnCollectionAfterCreateSuccess](https://pocketbase.io/docs/go-event-hooks/#oncollectionaftercreatesuccess)**
`OnCollectionAfterCreateSuccess` is a Collection proxy model hook of `OnModelAfterCreateSuccess`.
`OnCollectionAfterCreateSuccess` is triggered after each successful
Collection DB create persistence.
Note that when a Collection is persisted as part of a transaction,
this hook is delayed and executed only AFTER the transaction has been committed.
This hook is NOT triggered in case the transaction fails/rollbacks.
`package main
import (
"log"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/core"
)
func main() {
app := pocketbase.New()
// fires for every collection
app.OnCollectionAfterCreateSuccess().BindFunc(func(e *core.CollectionEvent) error {
// e.App
// e.Collection
return e.Next()
})
// fires only for "users" and "articles" collections
app.OnCollectionAfterCreateSuccess("users", "articles").BindFunc(func(e *core.CollectionEvent) error {
// e.App
// e.Collection
return e.Next()
})
if err := app.Start(); err != nil {
log.Fatal(err)
}
}`
**[OnCollectionAfterCreateError](https://pocketbase.io/docs/go-event-hooks/#oncollectionaftercreateerror)**
`OnCollectionAfterCreateError` is a Collection proxy model hook of `OnModelAfterCreateError`.
`OnCollectionAfterCreateError` is triggered after each failed
Collection DB create persistence.
Note that the execution of this hook is either immediate or delayed
depending on the error:
- **immediate** on `App.Save()` failure
- **delayed** on transaction rollback
`package main
import (
"log"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/core"
)
func main() {
app := pocketbase.New()
// fires for every collection
app.OnCollectionAfterCreateError().BindFunc(func(e *core.CollectionErrorEvent) error {
// e.App
// e.Collection
// e.Error
return e.Next()
})
// fires only for "users" and "articles" collections
app.OnCollectionAfterCreateError("users", "articles").BindFunc(func(e *core.CollectionErrorEvent) error {
// e.App
// e.Collection
// e.Error
return e.Next()
})
if err := app.Start(); err != nil {
log.Fatal(err)
}
}`
###### [Collection mode update hooks](https://pocketbase.io/docs/go-event-hooks/\#collection-mode-update-hooks)
**[OnCollectionUpdate](https://pocketbase.io/docs/go-event-hooks/#oncollectionupdate)**
`OnCollectionUpdate` is a Collection proxy model hook of `OnModelUpdate`.
`OnCollectionUpdate` is triggered every time when a new Collection is being updated,
e.g. triggered by `App.Save()`.
Operations BEFORE the `e.Next()` execute before the Collection validation
and the UPDATE DB statement.
Operations AFTER the `e.Next()` execute after the Collection validation
and the UPDATE DB statement.
Note that successful execution doesn't guarantee that the Collection
is persisted in the database since its wrapping transaction may
not have been committed yet.
If you want to listen to only the actual persisted events, you can
bind to `OnCollectionAfterUpdateSuccess` or `OnCollectionAfterUpdateError` hooks.
`package main
import (
"log"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/core"
)
func main() {
app := pocketbase.New()
// fires for every collection
app.OnCollectionUpdate().BindFunc(func(e *core.CollectionEvent) error {
// e.App
// e.Collection
return e.Next()
})
// fires only for "users" and "articles" collections
app.OnCollectionUpdate("users", "articles").BindFunc(func(e *core.CollectionEvent) error {
// e.App
// e.Collection
return e.Next()
})
if err := app.Start(); err != nil {
log.Fatal(err)
}
}`
**[OnCollectionUpdateExecute](https://pocketbase.io/docs/go-event-hooks/#oncollectionupdateexecute)**
`OnCollectionUpdateExecute` is a Collection proxy model hook of `OnModelUpdateExecute`.
`OnCollectionUpdateExecute` is triggered after successful Collection validation
and right before the model UPDATE DB statement execution.
Usually it is triggered as part of the `App.Save()` in the following firing order:
`OnCollectionUpdate`
-\> `OnCollectionValidate` (skipped with `App.SaveNoValidate()`)
-\> `OnCollectionUpdateExecute`
Note that successful execution doesn't guarantee that the Collection
is persisted in the database since its wrapping transaction may
not have been committed yet.
If you want to listen to only the actual persisted events, you can
bind to `OnCollectionAfterUpdateSuccess` or `OnCollectionAfterUpdateError` hooks.
`package main
import (
"log"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/core"
)
func main() {
app := pocketbase.New()
// fires for every collection
app.OnCollectionUpdateExecute().BindFunc(func(e *core.CollectionEvent) error {
// e.App
// e.Collection
return e.Next()
})
// fires only for "users" and "articles" collections
app.OnCollectionUpdateExecute("users", "articles").BindFunc(func(e *core.CollectionEvent) error {
// e.App
// e.Collection
return e.Next()
})
if err := app.Start(); err != nil {
log.Fatal(err)
}
}`
**[OnCollectionAfterUpdateSuccess](https://pocketbase.io/docs/go-event-hooks/#oncollectionafterupdatesuccess)**
`OnCollectionAfterUpdateSuccess` is a Collection proxy model hook of `OnModelAfterUpdateSuccess`.
`OnCollectionAfterUpdateSuccess` is triggered after each successful
Collection DB update persistence.
Note that when a Collection is persisted as part of a transaction,
this hook is delayed and executed only AFTER the transaction has been committed.
This hook is NOT triggered in case the transaction fails/rollbacks.
`package main
import (
"log"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/core"
)
func main() {
app := pocketbase.New()
// fires for every collection
app.OnCollectionAfterUpdateSuccess().BindFunc(func(e *core.CollectionEvent) error {
// e.App
// e.Collection
return e.Next()
})
// fires only for "users" and "articles" collections
app.OnCollectionAfterUpdateSuccess("users", "articles").BindFunc(func(e *core.CollectionEvent) error {
// e.App
// e.Collection
return e.Next()
})
if err := app.Start(); err != nil {
log.Fatal(err)
}
}`
**[OnCollectionAfterUpdateError](https://pocketbase.io/docs/go-event-hooks/#oncollectionafterupdateerror)**
`OnCollectionAfterUpdateError` is a Collection proxy model hook of `OnModelAfterUpdateError`.
`OnCollectionAfterUpdateError` is triggered after each failed
Collection DB update persistence.
Note that the execution of this hook is either immediate or delayed
depending on the error:
- **immediate** on `App.Save()` failure
- **delayed** on transaction rollback
`package main
import (
"log"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/core"
)
func main() {
app := pocketbase.New()
// fires for every collection
app.OnCollectionAfterUpdateError().BindFunc(func(e *core.CollectionErrorEvent) error {
// e.App
// e.Collection
// e.Error
return e.Next()
})
// fires only for "users" and "articles" collections
app.OnCollectionAfterUpdateError("users", "articles").BindFunc(func(e *core.CollectionErrorEvent) error {
// e.App
// e.Collection
// e.Error
return e.Next()
})
if err := app.Start(); err != nil {
log.Fatal(err)
}
}`
###### [Collection mode delete hooks](https://pocketbase.io/docs/go-event-hooks/\#collection-mode-delete-hooks)
**[OnCollectionDelete](https://pocketbase.io/docs/go-event-hooks/#oncollectiondelete)**
`OnCollectionDelete` is a Collection proxy model hook of `OnModelDelete`.
`OnCollectionDelete` is triggered every time when a new Collection is being deleted,
e.g. triggered by `App.Delete()`.
Operations BEFORE the `e.Next()` execute before the Collection validation
and the UPDATE DB statement.
Operations AFTER the `e.Next()` execute after the Collection validation
and the UPDATE DB statement.
Note that successful execution doesn't guarantee that the Collection
is deleted from the database since its wrapping transaction may
not have been committed yet.
If you want to listen to only the actual persisted deleted events, you can
bind to `OnCollectionAfterDeleteSuccess` or `OnCollectionAfterDeleteError` hooks.
`package main
import (
"log"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/core"
)
func main() {
app := pocketbase.New()
// fires for every collection
app.OnCollectionDelete().BindFunc(func(e *core.CollectionEvent) error {
// e.App
// e.Collection
return e.Next()
})
// fires only for "users" and "articles" collections
app.OnCollectionDelete("users", "articles").BindFunc(func(e *core.CollectionEvent) error {
// e.App
// e.Collection
return e.Next()
})
if err := app.Start(); err != nil {
log.Fatal(err)
}
}`
**[OnCollectionDeleteExecute](https://pocketbase.io/docs/go-event-hooks/#oncollectiondeleteexecute)**
`OnCollectionDeleteExecute` is a Collection proxy model hook of `OnModelDeleteExecute`.
`OnCollectionDeleteExecute` is triggered after the internal delete checks and
right before the Collection the model DELETE DB statement execution.
Usually it is triggered as part of the `App.Delete()` in the following firing order:
`OnCollectionDelete`
-\>
internal delete checks
-\> `OnCollectionDeleteExecute`
Note that successful execution doesn't guarantee that the Collection
is deleted from the database since its wrapping transaction may
not have been committed yet.
If you want to listen to only the actual persisted events, you can
bind to `OnCollectionAfterDeleteSuccess` or `OnCollectionAfterDeleteError` hooks.
`package main
import (
"log"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/core"
)
func main() {
app := pocketbase.New()
// fires for every collection
app.OnCollectionDeleteExecute().BindFunc(func(e *core.CollectionEvent) error {
// e.App
// e.Collection
return e.Next()
})
// fires only for "users" and "articles" collections
app.OnCollectionDeleteExecute("users", "articles").BindFunc(func(e *core.CollectionEvent) error {
// e.App
// e.Collection
return e.Next()
})
if err := app.Start(); err != nil {
log.Fatal(err)
}
}`
**[OnCollectionAfterDeleteSuccess](https://pocketbase.io/docs/go-event-hooks/#oncollectionafterdeletesuccess)**
`OnCollectionAfterDeleteSuccess` is a Collection proxy model hook of `OnModelAfterDeleteSuccess`.
`OnCollectionAfterDeleteSuccess` is triggered after each successful
Collection DB delete persistence.
Note that when a Collection is deleted as part of a transaction,
this hook is delayed and executed only AFTER the transaction has been committed.
This hook is NOT triggered in case the transaction fails/rollbacks.
`package main
import (
"log"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/core"
)
func main() {
app := pocketbase.New()
// fires for every collection
app.OnCollectionAfterDeleteSuccess().BindFunc(func(e *core.CollectionEvent) error {
// e.App
// e.Collection
return e.Next()
})
// fires only for "users" and "articles" collections
app.OnCollectionAfterDeleteSuccess("users", "articles").BindFunc(func(e *core.CollectionEvent) error {
// e.App
// e.Collection
return e.Next()
})
if err := app.Start(); err != nil {
log.Fatal(err)
}
}`
**[OnCollectionAfterDeleteError](https://pocketbase.io/docs/go-event-hooks/#oncollectionafterdeleteerror)**
`OnCollectionAfterDeleteError` is a Collection proxy model hook of `OnModelAfterDeleteError`.
`OnCollectionAfterDeleteError` is triggered after each failed
Collection DB delete persistence.
Note that the execution of this hook is either immediate or delayed
depending on the error:
- **immediate** on `App.Delete()` failure
- **delayed** on transaction rollback
`package main
import (
"log"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/core"
)
func main() {
app := pocketbase.New()
// fires for every collection
app.OnCollectionAfterDeleteError().BindFunc(func(e *core.CollectionErrorEvent) error {
// e.App
// e.Collection
// e.Error
return e.Next()
})
// fires only for "users" and "articles" collections
app.OnCollectionAfterDeleteError("users", "articles").BindFunc(func(e *core.CollectionErrorEvent) error {
// e.App
// e.Collection
// e.Error
return e.Next()
})
if err := app.Start(); err != nil {
log.Fatal(err)
}
}`
### [Request hooks](https://pocketbase.io/docs/go-event-hooks/\#request-hooks)
The request hooks are triggered only when the corresponding API request endpoint is accessed.
###### [Record CRUD request hooks](https://pocketbase.io/docs/go-event-hooks/\#record-crud-request-hooks)
**[OnRecordsListRequest](https://pocketbase.io/docs/go-event-hooks/#onrecordslistrequest)**
`OnRecordsListRequest` hook is triggered on each API Records list request.
Could be used to validate or modify the response before returning it to the client.
Note that if you want to hide existing or add new computed Record fields prefer using the
[`OnRecordEnrich`](https://pocketbase.io/docs/go-event-hooks/#onrecordenrich)
hook because it is less error-prone and it is triggered
by all builtin Record responses (including when sending realtime Record events).
`package main
import (
"log"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/core"
)
func main() {
app := pocketbase.New()
// fires for every collection
app.OnRecordsListRequest().BindFunc(func(e *core.RecordsListRequestEvent) error {
// e.App
// e.Collection
// e.Records
// e.Result
// and all RequestEvent fields...
return e.Next()
})
// fires only for "users" and "articles" collections
app.OnRecordsListRequest("users", "articles").BindFunc(func(e *core.RecordsListRequestEvent) error {
// e.App
// e.Collection
// e.Records
// e.Result
// and all RequestEvent fields...
return e.Next()
})
if err := app.Start(); err != nil {
log.Fatal(err)
}
}`
**[OnRecordViewRequest](https://pocketbase.io/docs/go-event-hooks/#onrecordviewrequest)**
`OnRecordViewRequest` hook is triggered on each API Record view request.
Could be used to validate or modify the response before returning it to the client.
Note that if you want to hide existing or add new computed Record fields prefer using the
[`OnRecordEnrich`](https://pocketbase.io/docs/go-event-hooks/#onrecordenrich)
hook because it is less error-prone and it is triggered
by all builtin Record responses (including when sending realtime Record events).
`package main
import (
"log"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/core"
)
func main() {
app := pocketbase.New()
// fires for every collection
app.OnRecordViewRequest().BindFunc(func(e *core.RecordRequestEvent) error {
// e.App
// e.Collection
// e.Record
// and all RequestEvent fields...
return e.Next()
})
// fires only for "users" and "articles" collections
app.OnRecordViewRequest("users", "articles").BindFunc(func(e *core.RecordRequestEvent) error {
log.Println(e.HttpContext)
log.Println(e.Record)
return nil
})
if err := app.Start(); err != nil {
log.Fatal(err)
}
}`
**[OnRecordCreateRequest](https://pocketbase.io/docs/go-event-hooks/#onrecordcreaterequest)**
`OnRecordCreateRequest` hook is triggered on each API Record create request.
Could be used to additionally validate the request data or implement
completely different persistence behavior.
`package main
import (
"log"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/core"
)
func main() {
app := pocketbase.New()
// fires for every collection
app.OnRecordCreateRequest().BindFunc(func(e *core.RecordRequestEvent) error {
// e.App
// e.Collection
// e.Record
// and all RequestEvent fields...
return e.Next()
})
// fires only for "users" and "articles" collections
app.OnRecordCreateRequest("users", "articles").BindFunc(func(e *core.RecordRequestEvent) error {
// e.App
// e.Collection
// e.Record
// and all RequestEvent fields...
return e.Next()
})
if err := app.Start(); err != nil {
log.Fatal(err)
}
}`
**[OnRecordUpdateRequest](https://pocketbase.io/docs/go-event-hooks/#onrecordupdaterequest)**
`OnRecordUpdateRequest` hook is triggered on each API Record update request.
Could be used to additionally validate the request data or implement
completely different persistence behavior.
`package main
import (
"log"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/core"
)
func main() {
app := pocketbase.New()
// fires for every collection
app.OnRecordUpdateRequest().BindFunc(func(e *core.RecordRequestEvent) error {
// e.App
// e.Collection
// e.Record
// and all RequestEvent fields...
return e.Next()
})
// fires only for "users" and "articles" collections
app.OnRecordUpdateRequest("users", "articles").BindFunc(func(e *core.RecordRequestEvent) error {
// e.App
// e.Collection
// e.Record
// and all RequestEvent fields...
return e.Next()
})
if err := app.Start(); err != nil {
log.Fatal(err)
}
}`
**[OnRecordDeleteRequest](https://pocketbase.io/docs/go-event-hooks/#onrecorddeleterequest)**
`OnRecordDeleteRequest` hook is triggered on each API Record delete request.
Could be used to additionally validate the request data or implement
completely different delete behavior.
`package main
import (
"log"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/core"
)
func main() {
app := pocketbase.New()
// fires for every collection
app.OnRecordDeleteRequest().BindFunc(func(e *core.RecordRequestEvent) error {
// e.App
// e.Collection
// e.Record
// and all RequestEvent fields...
return e.Next()
})
// fires only for "users" and "articles" collections
app.OnRecordDeleteRequest("users", "articles").BindFunc(func(e *core.RecordRequestEvent) error {
// e.App
// e.Collection
// e.Record
// and all RequestEvent fields...
return e.Next()
})
if err := app.Start(); err != nil {
log.Fatal(err)
}
}`
###### [Record auth request hooks](https://pocketbase.io/docs/go-event-hooks/\#record-auth-request-hooks)
**[OnRecordAuthRequest](https://pocketbase.io/docs/go-event-hooks/#onrecordauthrequest)**
`OnRecordAuthRequest` hook is triggered on each successful API
record authentication request (sign-in, token refresh, etc.).
Could be used to additionally validate or modify the authenticated
record data and token.
`package main
import (
"log"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/core"
)
func main() {
app := pocketbase.New()
// fires for every auth collection
app.OnRecordAuthRequest().BindFunc(func(e *core.RecordAuthRequestEvent) error {
// e.App
// e.Record
// e.Token
// e.Meta
// e.AuthMethod
// and all RequestEvent fields...
return e.Next()
})
// fires only for "users" and "managers" auth collections
app.OnRecordAuthRequest("users", "managers").BindFunc(func(e *core.RecordAuthRequestEvent) error {
// e.App
// e.Record
// e.Token
// e.Meta
// e.AuthMethod
// and all RequestEvent fields...
return e.Next()
})
if err := app.Start(); err != nil {
log.Fatal(err)
}
}`
**[OnRecordAuthRefreshRequest](https://pocketbase.io/docs/go-event-hooks/#onrecordauthrefreshrequest)**
`OnRecordAuthRefreshRequest` hook is triggered on each Record
auth refresh API request (right before generating a new auth token).
Could be used to additionally validate the request data or implement
completely different auth refresh behavior.
`package main
import (
"log"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/core"
)
func main() {
app := pocketbase.New()
// fires for every auth collection
app.OnRecordAuthRefreshRequest().BindFunc(func(e *core.RecordAuthWithOAuth2RequestEvent) error {
// e.App
// e.Collection
// e.Record
// and all RequestEvent fields...
return e.Next()
})
// fires only for "users" and "managers" auth collections
app.OnRecordAuthRefreshRequest("users", "managers").BindFunc(func(e *core.RecordAuthWithOAuth2RequestEvent) error {
// e.App
// e.Collection
// e.Record
// and all RequestEvent fields...
return e.Next()
})
if err := app.Start(); err != nil {
log.Fatal(err)
}
}`
**[OnRecordAuthWithPasswordRequest](https://pocketbase.io/docs/go-event-hooks/#onrecordauthwithpasswordrequest)**
`OnRecordAuthWithPasswordRequest` hook is triggered on each
Record auth with password API request.
`e.Record` could be `nil` if no matching identity is found, allowing
you to manually locate a different Record model (by reassigning `e.Record`).
`package main
import (
"log"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/core"
)
func main() {
app := pocketbase.New()
// fires for every auth collection
app.OnRecordAuthWithPasswordRequest().BindFunc(func(e *core.RecordAuthWithPasswordRequestEvent) error {
// e.App
// e.Collection
// e.Record (could be nil)
// e.Identity
// e.IdentityField
// e.Password
// and all RequestEvent fields...
return e.Next()
})
// fires only for "users" and "managers" auth collections
app.OnRecordAuthWithPasswordRequest("users", "managers").BindFunc(func(e *core.RecordAuthWithPasswordRequestEvent) error {
// e.App
// e.Collection
// e.Record (could be nil)
// e.Identity
// e.IdentityField
// e.Password
// and all RequestEvent fields...
return e.Next()
})
if err := app.Start(); err != nil {
log.Fatal(err)
}
}`
**[OnRecordAuthWithOAuth2Request](https://pocketbase.io/docs/go-event-hooks/#onrecordauthwithoauth2request)**
`OnRecordAuthWithOAuth2Request` hook is triggered on each Record
OAuth2 sign-in/sign-up API request (after token exchange and before external provider linking).
If `e.Record` is not set, then the OAuth2
request will try to create a new auth record.
To assign or link a different existing record model you can
change the `e.Record` field.
`package main
import (
"log"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/core"
)
func main() {
app := pocketbase.New()
// fires for every auth collection
app.OnRecordAuthWithOAuth2Request().BindFunc(func(e *core.RecordAuthWithOAuth2RequestEvent) error {
// e.App
// e.Collection
// e.ProviderName
// e.ProviderClient
// e.Record (could be nil)
// e.OAuth2User
// e.CreateData
// e.IsNewRecord
// and all RequestEvent fields...
return e.Next()
})
// fires only for "users" and "managers" auth collections
app.OnRecordAuthWithOAuth2Request("users", "managers").BindFunc(func(e *core.RecordAuthWithOAuth2RequestEvent) error {
// e.App
// e.Collection
// e.ProviderName
// e.ProviderClient
// e.Record (could be nil)
// e.OAuth2User
// e.CreateData
// e.IsNewRecord
// and all RequestEvent fields...
return e.Next()
})
if err := app.Start(); err != nil {
log.Fatal(err)
}
}`
**[OnRecordRequestPasswordResetRequest](https://pocketbase.io/docs/go-event-hooks/#onrecordrequestpasswordresetrequest)**
`OnRecordRequestPasswordResetRequest` hook is triggered on
each Record request password reset API request.
Could be used to additionally validate the request data or implement
completely different password reset behavior.
`package main
import (
"log"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/core"
)
func main() {
app := pocketbase.New()
// fires for every auth collection
app.OnRecordRequestPasswordResetRequest().BindFunc(func(e *core.RecordRequestPasswordResetRequestEvent) error {
// e.App
// e.Collection
// e.Record
// and all RequestEvent fields...
return e.Next()
})
// fires only for "users" and "managers" auth collections
app.OnRecordRequestPasswordResetRequest("users", "managers").BindFunc(func(e *core.RecordRequestPasswordResetRequestEvent) error {
// e.App
// e.Collection
// e.Record
// and all RequestEvent fields...
return e.Next()
})
if err := app.Start(); err != nil {
log.Fatal(err)
}
}`
**[OnRecordConfirmPasswordResetRequest](https://pocketbase.io/docs/go-event-hooks/#onrecordconfirmpasswordresetrequest)**
`OnRecordConfirmPasswordResetRequest` hook is triggered on
each Record confirm password reset API request.
Could be used to additionally validate the request data or implement
completely different persistence behavior.
`package main
import (
"log"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/core"
)
func main() {
app := pocketbase.New()
// fires for every auth collection
app.OnRecordConfirmPasswordResetRequest().BindFunc(func(e *core.RecordConfirmPasswordResetRequestEvent) error {
// e.App
// e.Collection
// e.Record
// and all RequestEvent fields...
return e.Next()
})
// fires only for "users" and "managers" auth collections
app.OnRecordConfirmPasswordResetRequest("users", "managers").BindFunc(func(e *core.RecordConfirmPasswordResetRequestEvent) error {
// e.App
// e.Collection
// e.Record
// and all RequestEvent fields...
return e.Next()
})
if err := app.Start(); err != nil {
log.Fatal(err)
}
}`
**[OnRecordRequestVerificationRequest](https://pocketbase.io/docs/go-event-hooks/#onrecordrequestverificationrequest)**
`OnRecordRequestVerificationRequest` hook is triggered on
each Record request verification API request.
Could be used to additionally validate the loaded request data or implement
completely different verification behavior.
`package main
import (
"log"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/core"
)
func main() {
app := pocketbase.New()
// fires for every auth collection
app.OnRecordRequestVerificationRequest().BindFunc(func(e *core.RecordRequestVerificationRequestEvent) error {
// e.App
// e.Collection
// e.Record
// and all RequestEvent fields...
return e.Next()
})
// fires only for "users" and "managers" auth collections
app.OnRecordRequestVerificationRequest("users", "managers").BindFunc(func(e *core.RecordRequestVerificationRequestEvent) error {
// e.App
// e.Collection
// e.Record
// and all RequestEvent fields...
return e.Next()
})
if err := app.Start(); err != nil {
log.Fatal(err)
}
}`
**[OnRecordConfirmVerificationRequest](https://pocketbase.io/docs/go-event-hooks/#onrecordconfirmverificationrequest)**
`OnRecordConfirmVerificationRequest` hook is triggered on each
Record confirm verification API request.
Could be used to additionally validate the request data or implement
completely different persistence behavior.
`package main
import (
"log"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/core"
)
func main() {
app := pocketbase.New()
// fires for every auth collection
app.OnRecordConfirmVerificationRequest().BindFunc(func(e *core.RecordConfirmVerificationRequestEvent) error {
// e.App
// e.Collection
// e.Record
// and all RequestEvent fields...
return e.Next()
})
// fires only for "users" and "managers" auth collections
app.OnRecordConfirmVerificationRequest("users", "managers").BindFunc(func(e *core.RecordConfirmVerificationRequestEvent) error {
// e.App
// e.Collection
// e.Record
// and all RequestEvent fields...
return e.Next()
})
if err := app.Start(); err != nil {
log.Fatal(err)
}
}`
**[OnRecordRequestEmailChangeRequest](https://pocketbase.io/docs/go-event-hooks/#onrecordrequestemailchangerequest)**
`OnRecordRequestEmailChangeRequest` hook is triggered on each
Record request email change API request.
Could be used to additionally validate the request data or implement
completely different request email change behavior.
`package main
import (
"log"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/core"
)
func main() {
app := pocketbase.New()
// fires for every auth collection
app.OnRecordRequestEmailChangeRequest().BindFunc(func(e *core.RecordRequestEmailChangeRequestEvent) error {
// e.App
// e.Collection
// e.Record
// e.NewEmail
// and all RequestEvent fields...
return e.Next()
})
// fires only for "users" and "managers" auth collections
app.OnRecordRequestEmailChangeRequest("users", "managers").BindFunc(func(e *core.RecordRequestEmailChangeRequestEvent) error {
// e.App
// e.Collection
// e.Record
// e.NewEmail
// and all RequestEvent fields...
return e.Next()
})
if err := app.Start(); err != nil {
log.Fatal(err)
}
}`
**[OnRecordConfirmEmailChangeRequest](https://pocketbase.io/docs/go-event-hooks/#onrecordconfirmemailchangerequest)**
`OnRecordConfirmEmailChangeRequest` hook is triggered on each
Record confirm email change API request.
Could be used to additionally validate the request data or implement
completely different persistence behavior.
`package main
import (
"log"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/core"
)
func main() {
app := pocketbase.New()
// fires for every auth collection
app.OnRecordConfirmEmailChangeRequest().BindFunc(func(e *core.RecordConfirmEmailChangeRequestEvent) error {
// e.App
// e.Collection
// e.Record
// e.NewEmail
// and all RequestEvent fields...
return e.Next()
})
// fires only for "users" and "managers" auth collections
app.OnRecordConfirmEmailChangeRequest("users", "managers").BindFunc(func(e *core.RecordConfirmEmailChangeRequestEvent) error {
// e.App
// e.Collection
// e.Record
// e.NewEmail
// and all RequestEvent fields...
return e.Next()
})
if err := app.Start(); err != nil {
log.Fatal(err)
}
}`
**[OnRecordRequestOTPRequest](https://pocketbase.io/docs/go-event-hooks/#onrecordrequestotprequest)**
`OnRecordRequestOTPRequest` hook is triggered on each Record
request OTP API request.
`e.Record` could be `nil` if no user with the requested email is found, allowing
you to manually create a new Record or locate a different Record model (by reassigning `e.Record`).
`package main
import (
"log"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/core"
)
func main() {
app := pocketbase.New()
// fires for every auth collection
app.OnRecordRequestOTPRequest().BindFunc(func(e *core.RecordCreateOTPRequestEvent) error {
// e.App
// e.Collection
// e.Record (could be nil)
// e.Password
// and all RequestEvent fields...
return e.Next()
})
// fires only for "users" and "managers" auth collections
app.OnRecordRequestOTPRequest("users", "managers").BindFunc(func(e *core.RecordCreateOTPRequestEvent) error {
// e.App
// e.Collection
// e.Record (could be nil)
// e.Password
// and all RequestEvent fields...
return e.Next()
})
if err := app.Start(); err != nil {
log.Fatal(err)
}
}`
**[OnRecordAuthWithOTPRequest](https://pocketbase.io/docs/go-event-hooks/#onrecordauthwithotprequest)**
`OnRecordAuthWithOTPRequest` hook is triggered on each Record
auth with OTP API request.
`package main
import (
"log"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/core"
)
func main() {
app := pocketbase.New()
// fires for every auth collection
app.OnRecordAuthWithOTPRequest().BindFunc(func(e *core.RecordAuthWithOTPRequestEvent) error {
// e.App
// e.Collection
// e.Record
// e.OTP
// and all RequestEvent fields...
return e.Next()
})
// fires only for "users" and "managers" auth collections
app.OnRecordAuthWithOTPRequest("users", "managers").BindFunc(func(e *core.RecordAuthWithOTPRequestEvent) error {
// e.App
// e.Collection
// e.Record
// e.OTP
// and all RequestEvent fields...
return e.Next()
})
if err := app.Start(); err != nil {
log.Fatal(err)
}
}`
###### [Batch request hooks](https://pocketbase.io/docs/go-event-hooks/\#batch-request-hooks)
**[OnBatchRequest](https://pocketbase.io/docs/go-event-hooks/#onbatchrequest)**
`OnBatchRequest` hook is triggered on each API batch request.
Could be used to additionally validate or modify the submitted batch requests.
This hook will also fire the corresponding `OnRecordCreateRequest`, `OnRecordUpdateRequest`, `OnRecordDeleteRequest` hooks, where `e.App` is the batch transactional app.
`package main
import (
"log"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/core"
)
func main() {
app := pocketbase.New()
app.OnBatchRequest().BindFunc(func(e *core.BatchRequestEvent) error {
// e.App
// e.Batch
// and all RequestEvent fields...
return e.Next()
})
if err := app.Start(); err != nil {
log.Fatal(err)
}
}`
###### [File request hooks](https://pocketbase.io/docs/go-event-hooks/\#file-request-hooks)
**[OnFileDownloadRequest](https://pocketbase.io/docs/go-event-hooks/#onfiledownloadrequest)**
`OnFileDownloadRequest` hook is triggered before each API File download request.
Could be used to validate or modify the file response before returning it to the client.
`package main
import (
"log"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/core"
)
func main() {
app := pocketbase.New()
app.OnFileDownloadRequest().BindFunc(func(e *core.FileDownloadRequestEvent) error {
// e.App
// e.Collection
// e.Record
// e.FileField
// e.ServedPath
// e.ServedName
// and all RequestEvent fields...
return e.Next()
})
if err := app.Start(); err != nil {
log.Fatal(err)
}
}`
**[OnFileTokenRequest](https://pocketbase.io/docs/go-event-hooks/#onfiletokenrequest)**
`OnFileTokenRequest` hook is triggered on each auth file token API request.
`package main
import (
"log"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/core"
)
func main() {
app := pocketbase.New()
// fires for every auth model
app.OnFileTokenRequest().BindFunc(func(e *core.FileTokenRequestEvent) error {
// e.App
// e.Record
// e.Token
// and all RequestEvent fields...
return e.Next();
})
// fires only for "users"
app.OnFileTokenRequest("users").BindFunc(func(e *core.FileTokenRequestEvent) error {
// e.App
// e.Record
// e.Token
// and all RequestEvent fields...
return e.Next();
})
if err := app.Start(); err != nil {
log.Fatal(err)
}
}`
###### [Collection request hooks](https://pocketbase.io/docs/go-event-hooks/\#collection-request-hooks)
**[OnCollectionsListRequest](https://pocketbase.io/docs/go-event-hooks/#oncollectionslistrequest)**
`OnCollectionsListRequest` hook is triggered on each API Collections list request.
Could be used to validate or modify the response before returning it to the client.
`package main
import (
"log"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/core"
)
func main() {
app := pocketbase.New()
app.OnCollectionsListRequest().BindFunc(func(e *core.CollectionsListRequestEvent) error {
// e.App
// e.Collections
// e.Result
// and all RequestEvent fields...
return e.Next()
})
if err := app.Start(); err != nil {
log.Fatal(err)
}
}`
**[OnCollectionViewRequest](https://pocketbase.io/docs/go-event-hooks/#oncollectionviewrequest)**
`OnCollectionViewRequest` hook is triggered on each API Collection view request.
Could be used to validate or modify the response before returning it to the client.
`package main
import (
"log"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/core"
)
func main() {
app := pocketbase.New()
app.OnCollectionViewRequest().BindFunc(func(e *core.CollectionRequestEvent) error {
// e.App
// e.Collection
// and all RequestEvent fields...
return e.Next()
})
if err := app.Start(); err != nil {
log.Fatal(err)
}
}`
**[OnCollectionCreateRequest](https://pocketbase.io/docs/go-event-hooks/#oncollectioncreaterequest)**
`OnCollectionCreateRequest` hook is triggered on each API Collection create request.
Could be used to additionally validate the request data or implement
completely different persistence behavior.
`package main
import (
"log"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/core"
)
func main() {
app := pocketbase.New()
app.OnCollectionCreateRequest().BindFunc(func(e *core.CollectionRequestEvent) error {
// e.App
// e.Collection
// and all RequestEvent fields...
return e.Next()
})
if err := app.Start(); err != nil {
log.Fatal(err)
}
}`
**[OnCollectionUpdateRequest](https://pocketbase.io/docs/go-event-hooks/#oncollectionupdaterequest)**
`OnCollectionUpdateRequest` hook is triggered on each API Collection update request.
Could be used to additionally validate the request data or implement
completely different persistence behavior.
`package main
import (
"log"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/core"
)
func main() {
app := pocketbase.New()
app.OnCollectionUpdateRequest().BindFunc(func(e *core.CollectionRequestEvent) error {
// e.App
// e.Collection
// and all RequestEvent fields...
return e.Next()
})
if err := app.Start(); err != nil {
log.Fatal(err)
}
}`
**[OnCollectionDeleteRequest](https://pocketbase.io/docs/go-event-hooks/#oncollectiondeleterequest)**
`OnCollectionDeleteRequest` hook is triggered on each API Collection delete request.
Could be used to additionally validate the request data or implement
completely different delete behavior.
`package main
import (
"log"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/core"
)
func main() {
app := pocketbase.New()
app.OnCollectionDeleteRequest().BindFunc(func(e *core.CollectionRequestEvent) error {
// e.App
// e.Collection
// and all RequestEvent fields...
return e.Next()
})
if err := app.Start(); err != nil {
log.Fatal(err)
}
}`
**[OnCollectionsImportRequest](https://pocketbase.io/docs/go-event-hooks/#oncollectionsimportrequest)**
`OnCollectionsImportRequest` hook is triggered on each API
collections import request.
Could be used to additionally validate the imported collections or
to implement completely different import behavior.
`package main
import (
"log"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/core"
)
func main() {
app := pocketbase.New()
app.OnCollectionsImportRequest().BindFunc(func(e *core.CollectionsImportRequestEvent) error {
// e.App
// e.CollectionsData
// e.DeleteMissing
return e.Next()
})
if err := app.Start(); err != nil {
log.Fatal(err)
}
}`
###### [Settings request hooks](https://pocketbase.io/docs/go-event-hooks/\#settings-request-hooks)
**[OnSettingsListRequest](https://pocketbase.io/docs/go-event-hooks/#onsettingslistrequest)**
`OnSettingsListRequest` hook is triggered on each API Settings list request.
Could be used to validate or modify the response before returning it to the client.
`package main
import (
"log"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/core"
)
func main() {
app := pocketbase.New()
app.OnSettingsListRequest().BindFunc(func(e *core.SettingsListRequestEvent) error {
// e.App
// e.Settings
// and all RequestEvent fields...
return e.Next()
})
if err := app.Start(); err != nil {
log.Fatal(err)
}
}`
**[OnSettingsUpdateRequest](https://pocketbase.io/docs/go-event-hooks/#onsettingsupdaterequest)**
`OnSettingsUpdateRequest` hook is triggered on each API Settings update request.
Could be used to additionally validate the request data or
implement completely different persistence behavior.
`package main
import (
"log"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/core"
)
func main() {
app := pocketbase.New()
app.OnSettingsUpdateRequest().BindFunc(func(e *core.SettingsUpdateRequestEvent) error {
// e.App
// e.OldSettings
// e.NewSettings
// and all RequestEvent fields...
return e.Next()
})
if err := app.Start(); err != nil {
log.Fatal(err)
}
}`
### [Base model hooks](https://pocketbase.io/docs/go-event-hooks/\#base-model-hooks)
The Model hooks are fired for all PocketBase structs that implements the Model DB interface - Record, Collection, Log, etc.
For convenience, if you want to listen to only the Record or Collection DB model
events without doing manual type assertion, you can use the
[`OnRecord*`](https://pocketbase.io/docs/go-event-hooks/#record-model-hooks)
and
[`OnCollection*`](https://pocketbase.io/docs/go-event-hooks/#collection-model-hooks)
proxy hooks above.
**[OnModelValidate](https://pocketbase.io/docs/go-event-hooks/#onmodelvalidate)**
`OnModelValidate` is called every time when a Model is being validated,
e.g. triggered by `App.Validate()` or `App.Save()`.
For convenience, if you want to listen to only the Record or Collection models
events without doing manual type assertion, you can use the equivalent `OnRecord*` and `OnCollection*` proxy hooks.
`package main
import (
"log"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/core"
)
func main() {
app := pocketbase.New()
// fires for every model
app.OnModelValidate().BindFunc(func(e *core.ModelEvent) error {
// e.App
// e.Model
return e.Next()
})
// fires only for "users" and "articles" models
app.OnModelValidate("users", "articles").BindFunc(func(e *core.ModelEvent) error {
// e.App
// e.Model
return e.Next()
})
if err := app.Start(); err != nil {
log.Fatal(err)
}
}`
###### [Base model create hooks](https://pocketbase.io/docs/go-event-hooks/\#base-model-create-hooks)
**[OnModelCreate](https://pocketbase.io/docs/go-event-hooks/#onmodelcreate)**
`OnModelCreate` is triggered every time when a new Model is being created,
e.g. triggered by `App.Save()`.
Operations BEFORE the `e.Next()` execute before the Model validation
and the INSERT DB statement.
Operations AFTER the `e.Next()` execute after the Model validation
and the INSERT DB statement.
Note that successful execution doesn't guarantee that the Model
is persisted in the database since its wrapping transaction may
not have been committed yet.
If you want to listen to only the actual persisted events, you can
bind to `OnModelAfterCreateSuccess` or `OnModelAfterCreateError` hooks.
For convenience, if you want to listen to only the Record or Collection models
events without doing manual type assertion, you can use the equivalent `OnRecord*` and `OnCollection*` proxy hooks.
`package main
import (
"log"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/core"
)
func main() {
app := pocketbase.New()
// fires for every model
app.OnModelCreate().BindFunc(func(e *core.ModelEvent) error {
// e.App
// e.Model
return e.Next()
})
// fires only for "users" and "articles" models
app.OnModelCreate("users", "articles").BindFunc(func(e *core.ModelEvent) error {
// e.App
// e.Model
return e.Next()
})
if err := app.Start(); err != nil {
log.Fatal(err)
}
}`
**[OnModelCreateExecute](https://pocketbase.io/docs/go-event-hooks/#onmodelcreateexecute)**
`OnModelCreateExecute` is triggered after successful Model validation
and right before the model INSERT DB statement execution.
Usually it is triggered as part of the `App.Save()` in the following firing order:
`OnModelCreate`
-\> `OnModelValidate` (skipped with `App.SaveNoValidate()`)
-\> `OnModelCreateExecute`
Note that successful execution doesn't guarantee that the Model
is persisted in the database since its wrapping transaction may
not have been committed yet.
If you want to listen to only the actual persisted events, you can
bind to `OnModelAfterCreateSuccess` or `OnModelAfterCreateError` hooks.
For convenience, if you want to listen to only the Record or Collection models
events without doing manual type assertion, you can use the equivalent `OnRecord*` and `OnCollection*` proxy hooks.
`package main
import (
"log"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/core"
)
func main() {
app := pocketbase.New()
// fires for every model
app.OnModelCreateExecute().BindFunc(func(e *core.ModelEvent) error {
// e.App
// e.Model
return e.Next()
})
// fires only for "users" and "articles" models
app.OnModelCreateExecute("users", "articles").BindFunc(func(e *core.ModelEvent) error {
// e.App
// e.Model
return e.Next()
})
if err := app.Start(); err != nil {
log.Fatal(err)
}
}`
**[OnModelAfterCreateSuccess](https://pocketbase.io/docs/go-event-hooks/#onmodelaftercreatesuccess)**
`OnModelAfterCreateSuccess` is triggered after each successful
Model DB create persistence.
Note that when a Model is persisted as part of a transaction,
this hook is delayed and executed only AFTER the transaction has been committed.
This hook is NOT triggered in case the transaction fails/rollbacks.
For convenience, if you want to listen to only the Record or Collection models
events without doing manual type assertion, you can use the equivalent `OnRecord*` and `OnCollection*` proxy hooks.
`package main
import (
"log"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/core"
)
func main() {
app := pocketbase.New()
// fires for every model
app.OnModelAfterCreateSuccess().BindFunc(func(e *core.ModelEvent) error {
// e.App
// e.Model
return e.Next()
})
// fires only for "users" and "articles" models
app.OnModelAfterCreateSuccess("users", "articles").BindFunc(func(e *core.ModelEvent) error {
// e.App
// e.Model
return e.Next()
})
if err := app.Start(); err != nil {
log.Fatal(err)
}
}`
**[OnModelAfterCreateError](https://pocketbase.io/docs/go-event-hooks/#onmodelaftercreateerror)**
`OnModelAfterCreateError` is triggered after each failed
Model DB create persistence.
Note that the execution of this hook is either immediate or delayed
depending on the error:
- **immediate** on `App.Save()` failure
- **delayed** on transaction rollback
For convenience, if you want to listen to only the Record or Collection models
events without doing manual type assertion, you can use the equivalent `OnRecord*` and `OnCollection*` proxy hooks.
`package main
import (
"log"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/core"
)
func main() {
app := pocketbase.New()
// fires for every model
app.OnModelAfterCreateError().BindFunc(func(e *core.ModelErrorEvent) error {
// e.App
// e.Model
// e.Error
return e.Next()
})
// fires only for "users" and "articles" models
app.OnModelAfterCreateError("users", "articles").BindFunc(func(e *core.ModelErrorEvent) error {
// e.App
// e.Model
// e.Error
return e.Next()
})
if err := app.Start(); err != nil {
log.Fatal(err)
}
}`
###### [Base model update hooks](https://pocketbase.io/docs/go-event-hooks/\#base-model-update-hooks)
**[OnModelUpdate](https://pocketbase.io/docs/go-event-hooks/#onmodelupdate)**
`OnModelUpdate` is triggered every time when a new Model is being updated,
e.g. triggered by `App.Save()`.
Operations BEFORE the `e.Next()` execute before the Model validation
and the UPDATE DB statement.
Operations AFTER the `e.Next()` execute after the Model validation
and the UPDATE DB statement.
Note that successful execution doesn't guarantee that the Model
is persisted in the database since its wrapping transaction may
not have been committed yet.
If you want to listen to only the actual persisted events, you can
bind to `OnModelAfterUpdateSuccess` or `OnModelAfterUpdateError` hooks.
For convenience, if you want to listen to only the Record or Collection models
events without doing manual type assertion, you can use the equivalent `OnRecord*` and `OnCollection*` proxy hooks.
`package main
import (
"log"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/core"
)
func main() {
app := pocketbase.New()
// fires for every model
app.OnModelUpdate().BindFunc(func(e *core.ModelEvent) error {
// e.App
// e.Model
return e.Next()
})
// fires only for "users" and "articles" models
app.OnModelUpdate("users", "articles").BindFunc(func(e *core.ModelEvent) error {
// e.App
// e.Model
return e.Next()
})
if err := app.Start(); err != nil {
log.Fatal(err)
}
}`
**[OnModelUpdateExecute](https://pocketbase.io/docs/go-event-hooks/#onmodelupdateexecute)**
`OnModelUpdateExecute` is triggered after successful Model validation
and right before the model UPDATE DB statement execution.
Usually it is triggered as part of the `App.Save()` in the following firing order:
`OnModelUpdate`
-\> `OnModelValidate` (skipped with `App.SaveNoValidate()`)
-\> `OnModelUpdateExecute`
Note that successful execution doesn't guarantee that the Model
is persisted in the database since its wrapping transaction may
not have been committed yet.
If you want to listen to only the actual persisted events, you can
bind to `OnModelAfterUpdateSuccess` or `OnModelAfterUpdateError` hooks.
For convenience, if you want to listen to only the Record or Collection models
events without doing manual type assertion, you can use the equivalent `OnRecord*` and `OnCollection*` proxy hooks.
`package main
import (
"log"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/core"
)
func main() {
app := pocketbase.New()
// fires for every model
app.OnModelUpdateExecute().BindFunc(func(e *core.ModelEvent) error {
// e.App
// e.Model
return e.Next()
})
// fires only for "users" and "articles" models
app.OnModelUpdateExecute("users", "articles").BindFunc(func(e *core.ModelEvent) error {
// e.App
// e.Model
return e.Next()
})
if err := app.Start(); err != nil {
log.Fatal(err)
}
}`
**[OnModelAfterUpdateSuccess](https://pocketbase.io/docs/go-event-hooks/#onmodelafterupdatesuccess)**
`OnModelAfterUpdateSuccess` is triggered after each successful
Model DB update persistence.
Note that when a Model is persisted as part of a transaction,
this hook is delayed and executed only AFTER the transaction has been committed.
This hook is NOT triggered in case the transaction fails/rollbacks.
For convenience, if you want to listen to only the Record or Collection models
events without doing manual type assertion, you can use the equivalent `OnRecord*` and `OnCollection*` proxy hooks.
`package main
import (
"log"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/core"
)
func main() {
app := pocketbase.New()
// fires for every model
app.OnModelAfterUpdateSuccess().BindFunc(func(e *core.ModelEvent) error {
// e.App
// e.Model
return e.Next()
})
// fires only for "users" and "articles" models
app.OnModelAfterUpdateSuccess("users", "articles").BindFunc(func(e *core.ModelEvent) error {
// e.App
// e.Model
return e.Next()
})
if err := app.Start(); err != nil {
log.Fatal(err)
}
}`
**[OnModelAfterUpdateError](https://pocketbase.io/docs/go-event-hooks/#onmodelafterupdateerror)**
`OnModelAfterUpdateError` is triggered after each failed
Model DB update persistence.
Note that the execution of this hook is either immediate or delayed
depending on the error:
- **immediate** on `App.Save()` failure
- **delayed** on transaction rollback
For convenience, if you want to listen to only the Record or Collection models
events without doing manual type assertion, you can use the equivalent `OnRecord*` and `OnCollection*` proxy hooks.
`package main
import (
"log"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/core"
)
func main() {
app := pocketbase.New()
// fires for every model
app.OnModelAfterUpdateError().BindFunc(func(e *core.ModelErrorEvent) error {
// e.App
// e.Model
// e.Error
return e.Next()
})
// fires only for "users" and "articles" models
app.OnModelAfterUpdateError("users", "articles").BindFunc(func(e *core.ModelErrorEvent) error {
// e.App
// e.Model
// e.Error
return e.Next()
})
if err := app.Start(); err != nil {
log.Fatal(err)
}
}`
###### [Base model delete hooks](https://pocketbase.io/docs/go-event-hooks/\#base-model-delete-hooks)
**[OnModelDelete](https://pocketbase.io/docs/go-event-hooks/#onmodeldelete)**
`OnModelDelete` is triggered every time when a new Model is being deleted,
e.g. triggered by `App.Delete()`.
Operations BEFORE the `e.Next()` execute before the Model validation
and the UPDATE DB statement.
Operations AFTER the `e.Next()` execute after the Model validation
and the UPDATE DB statement.
Note that successful execution doesn't guarantee that the Model
is deleted from the database since its wrapping transaction may
not have been committed yet.
If you want to listen to only the actual persisted deleted events, you can
bind to `OnModelAfterDeleteSuccess` or `OnModelAfterDeleteError` hooks.
For convenience, if you want to listen to only the Record or Collection models
events without doing manual type assertion, you can use the equivalent `OnRecord*` and `OnCollection*` proxy hooks.
`package main
import (
"log"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/core"
)
func main() {
app := pocketbase.New()
// fires for every model
app.OnModelDelete().BindFunc(func(e *core.ModelEvent) error {
// e.App
// e.Model
return e.Next()
})
// fires only for "users" and "articles" models
app.OnModelDelete("users", "articles").BindFunc(func(e *core.ModelEvent) error {
// e.App
// e.Model
return e.Next()
})
if err := app.Start(); err != nil {
log.Fatal(err)
}
}`
**[OnModelDeleteExecute](https://pocketbase.io/docs/go-event-hooks/#onmodeldeleteexecute)**
`OnModelDeleteExecute` is triggered after the internal delete checks and
right before the Model the model DELETE DB statement execution.
Usually it is triggered as part of the `App.Delete()` in the following firing order:
`OnModelDelete`
-\>
internal delete checks
-\> `OnModelDeleteExecute`
Note that successful execution doesn't guarantee that the Model
is deleted from the database since its wrapping transaction may
not have been committed yet.
If you want to listen to only the actual persisted events, you can
bind to `OnModelAfterDeleteSuccess` or `OnModelAfterDeleteError` hooks.
For convenience, if you want to listen to only the Record or Collection models
events without doing manual type assertion, you can use the equivalent `OnRecord*` and `OnCollection*` proxy hooks.
`package main
import (
"log"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/core"
)
func main() {
app := pocketbase.New()
// fires for every model
app.OnModelDeleteExecute().BindFunc(func(e *core.ModelEvent) error {
// e.App
// e.Model
return e.Next()
})
// fires only for "users" and "articles" models
app.OnModelDeleteExecute("users", "articles").BindFunc(func(e *core.ModelEvent) error {
// e.App
// e.Model
return e.Next()
})
if err := app.Start(); err != nil {
log.Fatal(err)
}
}`
**[OnModelAfterDeleteSuccess](https://pocketbase.io/docs/go-event-hooks/#onmodelafterdeletesuccess)**
`OnModelAfterDeleteSuccess` is triggered after each successful
Model DB delete persistence.
Note that when a Model is deleted as part of a transaction,
this hook is delayed and executed only AFTER the transaction has been committed.
This hook is NOT triggered in case the transaction fails/rollbacks.
For convenience, if you want to listen to only the Record or Collection models
events without doing manual type assertion, you can use the equivalent `OnRecord*` and `OnCollection*` proxy hooks.
`package main
import (
"log"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/core"
)
func main() {
app := pocketbase.New()
// fires for every model
app.OnModelAfterDeleteSuccess().BindFunc(func(e *core.ModelEvent) error {
// e.App
// e.Model
return e.Next()
})
// fires only for "users" and "articles" models
app.OnModelAfterDeleteSuccess("users", "articles").BindFunc(func(e *core.ModelEvent) error {
// e.App
// e.Model
return e.Next()
})
if err := app.Start(); err != nil {
log.Fatal(err)
}
}`
**[OnModelAfterDeleteError](https://pocketbase.io/docs/go-event-hooks/#onmodelafterdeleteerror)**
`OnModelAfterDeleteError` is triggered after each failed
Model DB delete persistence.
Note that the execution of this hook is either immediate or delayed
depending on the error:
- **immediate** on `App.Delete()` failure
- **delayed** on transaction rollback
For convenience, if you want to listen to only the Record or Collection models
events without doing manual type assertion, you can use the equivalent `OnRecord*` and `OnCollection*` proxy hooks.
`package main
import (
"log"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/core"
)
func main() {
app := pocketbase.New()
// fires for every model
app.OnModelAfterDeleteError().BindFunc(func(e *core.ModelErrorEvent) error {
// e.App
// e.Model
// e.Error
return e.Next()
})
// fires only for "users" and "articles" models
app.OnModelAfterDeleteError("users", "articles").BindFunc(func(e *core.ModelErrorEvent) error {
// e.App
// e.Model
// e.Error
return e.Next()
})
if err := app.Start(); err != nil {
log.Fatal(err)
}
}`
* * *
[Prev: Overview](https://pocketbase.io/docs/go-overview) [Next: Routing](https://pocketbase.io/docs/go-routing)
## Record Proxy Guide
Record proxy
The available [`core.Record` and its helpers](https://pocketbase.io/docs/go-records)
are usually the recommended way to interact with your data, but in case you want a typed access to your record
fields you can create a helper struct that embeds
[`core.BaseRecordProxy`](https://pkg.go.dev/github.com/pocketbase/pocketbase/core#BaseRecordProxy) _(which implements the `core.RecordProxy` interface)_ and define your collection fields as
getters and setters.
By implementing the `core.RecordProxy` interface you can use your custom struct as part of a
`RecordQuery` result like a regular record model. In addition, every DB change through the proxy
struct will trigger the corresponding record validations and hooks. This ensures that other parts of your app,
including 3rd party plugins, that don't know or use your custom struct will still work as expected.
Below is a sample `Article` record proxy implementation:
`// article.go
package main
import (
"github.com/pocketbase/pocketbase/core"
"github.com/pocketbase/pocketbase/tools/types"
)
// ensures that the Article struct satisfy the core.RecordProxy interface
var _ core.RecordProxy = (*Article)(nil)
type Article struct {
core.BaseRecordProxy
}
func (a *Article) Title() string {
return a.GetString("title")
}
func (a *Article) SetTitle(title string) {
a.Set("title", title)
}
func (a *Article) Slug() string {
return a.GetString("slug")
}
func (a *Article) SetSlug(slug string) {
a.Set("slug", slug)
}
func (a *Article) Created() types.DateTime {
return a.GetDateTime("created")
}
func (a *Article) Updated() types.DateTime {
return a.GetDateTime("updated")
}`
Accessing and modifying the proxy records is the same as for the regular records. Continuing with the
above `Article` example:
`func FindArticleBySlug(app core.App, slug string) (*Article, error) {
article := &Article{}
err := app.RecordQuery("articles").
AndWhere(dbx.NewExp("LOWER(slug)={:slug}", dbx.Params{
"slug": strings.ToLower(slug), // case insensitive match
})).
Limit(1).
One(article)
if err != nil {
return nil, err
}
return article, nil
}
...
article, err := FindArticleBySlug(app, "example")
if err != nil {
return err
}
// change the title
article.SetTitle("Lorem ipsum...")
// persist the change while also triggering the original record validations and hooks
err = app.Save(article)
if err != nil {
return err
}`
If you have an existing `*core.Record` value you can also load it into your proxy using the
`SetProxyRecord` method:
`// fetch regular record
record, err := app.FindRecordById("articles", "RECORD_ID")
if err != nil {
return err
}
// load into proxy
article := &Article{}
article.SetProxyRecord(record)`
* * *
[Prev: Miscellaneous](https://pocketbase.io/docs/go-miscellaneous)
## HTML Template Rendering
Rendering templates
- [Overview](https://pocketbase.io/docs/js-rendering-templates/#overview)
- [Example HTML page with layout](https://pocketbase.io/docs/js-rendering-templates/#example-html-page-with-layout)
### [Overview](https://pocketbase.io/docs/js-rendering-templates/\#overview)
A common task when creating custom routes or emails is the need of generating HTML output. To assist with
this, PocketBase provides the global `$template` helper for parsing and rendering HTML templates.
``const html = $template.loadFiles(
`${__hooks}/views/base.html`,
`${__hooks}/views/partial1.html`,
`${__hooks}/views/partial2.html`,
).render(data)``
The general flow when working with composed and nested templates is that you create "base" template(s)
that defines various placeholders using the
`{{template "placeholderName" .}}` or
`{{block "placeholderName" .}}default...{{end}}` actions.
Then in the partials, you define the content for those placeholders using the
`{{define "placeholderName"}}custom...{{end}}` action.
The dot object ( `.`) in the above represents the data passed to the templates
via the `render(data)` method.
By default the templates apply contextual (HTML, JS, CSS, URI) auto escaping so the generated template
content should be injection-safe. To render raw/verbatim trusted content in the templates you can use the
builtin `raw` function (e.g. `{{.content|raw}}`).
For more information about the template syntax please refer to the
[_html/template_](https://pkg.go.dev/html/template#hdr-A_fuller_picture)
and
[_text/template_](https://pkg.go.dev/text/template)
package godocs.
**Another great resource is also the Hashicorp's**
**[Learn Go Template Syntax](https://developer.hashicorp.com/nomad/tutorials/templates/go-template-syntax)**
**tutorial.**
### [Example HTML page with layout](https://pocketbase.io/docs/js-rendering-templates/\#example-html-page-with-layout)
Consider the following app directory structure:
`myapp/
pb_hooks/
views/
layout.html
hello.html
main.pb.js
pocketbase`
We define the content for `layout.html` as:
`
{{block "title" .}}Default app title{{end}}
Header...
{{block "body" .}}
Default app body...
{{end}}
Footer...
`
We define the content for `hello.html` as:
`{{define "title"}}
Page 1
{{end}}
{{define "body"}}
Hello from {{.name}}
{{end}}`
Then to output the final page, we'll register a custom `/hello/:name` route:
``routerAdd("get", "/hello/{name}", (e) => {
const name = e.request.pathValue("name")
const html = $template.loadFiles(
`${__hooks}/views/layout.html`,
`${__hooks}/views/hello.html`,
).render({
"name": name,
})
return e.html(200, html)
})``
* * *
[Prev: Sending emails](https://pocketbase.io/docs/js-sending-emails) [Next: Console commands](https://pocketbase.io/docs/js-console-commands)
## Sending Emails
Sending emails
PocketBase provides a simple abstraction for sending emails via the
`$app.newMailClient()` helper.
Depending on your configured mail settings ( _Dashboard > Settings > Mail settings_) it will use the
`sendmail` command or a SMTP client.
- [Send custom email](https://pocketbase.io/docs/js-sending-emails/#send-custom-email)
- [Overwrite system emails](https://pocketbase.io/docs/js-sending-emails/#overwrite-system-emails)
### [Send custom email](https://pocketbase.io/docs/js-sending-emails/\#send-custom-email)
You can send your own custom emails from everywhere within the app (hooks, middlewares, routes, etc.) by
using `$app.newMailClient().send(message)`. Here is an example of sending a custom email after
user registration:
`onRecordCreateRequest((e) => {
e.next()
const message = new MailerMessage({
from: {
address: e.app.settings().meta.senderAddress,
name: e.app.settings().meta.senderName,
},
to: [{address: e.record.email()}],
subject: "YOUR_SUBJECT...",
html: "YOUR_HTML_BODY...",
// bcc, cc and custom headers are also supported...
})
e.app.newMailClient().send(message)
}, "users")`
### [Overwrite system emails](https://pocketbase.io/docs/js-sending-emails/\#overwrite-system-emails)
If you want to overwrite the default system emails for forgotten password, verification, etc., you can
adjust the default templates available from the
_Dashboard > Collections > Edit collection > Options_
.
Alternatively, you can also apply individual changes by binding to one of the
[mailer hooks](https://pocketbase.io/docs/js-event-hooks/#mailer-hooks). Here is an example of appending a Record
field value to the subject using the `onMailerRecordPasswordResetSend` hook:
`onMailerRecordPasswordResetSend((e) => {
// modify the subject
e.message.subject += (" " + e.record.get("name"))
e.next()
})`
* * *
[Prev: Jobs scheduling](https://pocketbase.io/docs/js-jobs-scheduling) [Next: Rendering templates](https://pocketbase.io/docs/js-rendering-templates)
## Jobs Scheduling
Jobs scheduling
If you have tasks that need to be performed periodically, you could setup crontab-like jobs with
`cronAdd(id, expr, handler)`.
Each scheduled job runs in its own goroutine as part of the `serve` command process and must have:
- **id** \- identifier for the scheduled job; could be used to replace or remove an existing
job
- **cron expression** \- e.g. `0 0 * * *` (
_supports numeric list, steps, ranges or_
_macros_)
- **handler** \- the function that will be executed every time when the job runs
Here is an example:
`// prints "Hello!" every 2 minutes
cronAdd("hello", "*/2 * * * *", () => {
console.log("Hello!")
})`
To remove a single registered cron job you can call `cronRemove(id)`.
All registered app level cron jobs can be also previewed and triggered from the
_Dashboard > Settings > Crons_ section.
* * *
[Prev: Migrations](https://pocketbase.io/docs/js-migrations) [Next: Sending emails](https://pocketbase.io/docs/js-sending-emails)
## Jobs Scheduling
Jobs scheduling
If you have tasks that need to be performed periodically, you could setup crontab-like jobs with the
builtin `app.Cron()` _(it returns an app scoped_
_[`cron.Cron`](https://pkg.go.dev/github.com/pocketbase/pocketbase/tools/cron#Cron) value)_
.
The jobs scheduler is started automatically on app `serve`, so all you have to do is register a
handler with
[`app.Cron().Add(id, cronExpr, handler)`](https://pkg.go.dev/github.com/pocketbase/pocketbase/tools/cron#Cron.Add)
or
[`app.Cron().MustAdd(id, cronExpr, handler)`](https://pkg.go.dev/github.com/pocketbase/pocketbase/tools/cron#Cron.MustAdd)
( _the latter panic if the cron expression is not valid_).
Each scheduled job runs in its own goroutine and must have:
- **id** \- identifier for the scheduled job; could be used to replace or remove an existing
job
- **cron expression** \- e.g. `0 0 * * *` (
_supports numeric list, steps, ranges or_
_macros_)
- **handler** \- the function that will be executed everytime when the job runs
Here is one minimal example:
`// main.go
package main
import (
"log"
"github.com/pocketbase/pocketbase"
)
func main() {
app := pocketbase.New()
// prints "Hello!" every 2 minutes
app.Cron().MustAdd("hello", "*/2 * * * *", func() {
log.Println("Hello!")
})
if err := app.Start(); err != nil {
log.Fatal(err)
}
}`
To remove already registered cron job you can call
[`app.Cron().Remove(id)`](https://pkg.go.dev/github.com/pocketbase/pocketbase/tools/cron#Cron.Remove)
All registered app level cron jobs can be also previewed and triggered from the
_Dashboard > Settings > Crons_ section.
Keep in mind that the `app.Cron()` is also used for running the system scheduled jobs
like the logs cleanup or auto backups (the jobs id is in the format `__pb*__`) and
replacing these system jobs or calling `RemoveAll()`/ `Stop()` could have unintended
side-effects.
If you want more advanced control you can initialize your own cron instance independent from the
application via `cron.New()`.
* * *
[Prev: Migrations](https://pocketbase.io/docs/go-migrations) [Next: Sending emails](https://pocketbase.io/docs/go-sending-emails)
## Sending Emails in PocketBase
Sending emails
PocketBase provides a simple abstraction for sending emails via the
`app.NewMailClient()` factory.
Depending on your configured mail settings ( _Dashboard > Settings > Mail settings_) it will use the
`sendmail` command or a SMTP client.
- [Send custom email](https://pocketbase.io/docs/go-sending-emails/#send-custom-email)
- [Overwrite system emails](https://pocketbase.io/docs/go-sending-emails/#overwrite-system-emails)
### [Send custom email](https://pocketbase.io/docs/go-sending-emails/\#send-custom-email)
You can send your own custom email from everywhere within the app (hooks, middlewares, routes, etc.) by
using `app.NewMailClient().Send(message)`. Here is an example of sending a custom email after
user registration:
`// main.go
package main
import (
"log"
"net/mail"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/core"
"github.com/pocketbase/pocketbase/tools/mailer"
)
func main() {
app := pocketbase.New()
app.OnRecordCreateRequest("users").BindFunc(func(e *core.RecordRequestEvent) error {
if err := e.Next(); err != nil {
return err
}
message := &mailer.Message{
From: mail.Address{
Address: e.App.Settings().Meta.SenderAddress,
Name: e.App.Settings().Meta.SenderName,
},
To: []mail.Address{{Address: e.Record.Email()}},
Subject: "YOUR_SUBJECT...",
HTML: "YOUR_HTML_BODY...",
// bcc, cc, attachments and custom headers are also supported...
}
return e.App.NewMailClient().Send(message)
})
if err := app.Start(); err != nil {
log.Fatal(err)
}
}`
### [Overwrite system emails](https://pocketbase.io/docs/go-sending-emails/\#overwrite-system-emails)
If you want to overwrite the default system emails for forgotten password, verification, etc., you can
adjust the default templates available from the
_Dashboard > Collections > Edit collection > Options_
.
Alternatively, you can also apply individual changes by binding to one of the
[mailer hooks](https://pocketbase.io/docs/go-event-hooks/#mailer-hooks). Here is an example of appending a Record
field value to the subject using the `OnMailerRecordPasswordResetSend` hook:
`// main.go
package main
import (
"log"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/core"
)
func main() {
app := pocketbase.New()
app.OnMailerRecordPasswordResetSend("users").BindFunc(func(e *core.MailerRecordEvent) error {
// modify the subject
e.Message.Subject += (" " + e.Record.GetString("name"))
return e.Next()
})
if err := app.Start(); err != nil {
log.Fatal(err)
}
}`
* * *
[Prev: Jobs scheduling](https://pocketbase.io/docs/go-jobs-scheduling) [Next: Rendering templates](https://pocketbase.io/docs/go-rendering-templates)
## Custom Console Commands
Console commands
You can register custom console commands using
`app.RootCmd.AddCommand(cmd)`, where `cmd` is a
[cobra](https://pkg.go.dev/github.com/spf13/cobra) command.
Here is an example:
`package main
import (
"log"
"github.com/pocketbase/pocketbase"
"github.com/spf13/cobra"
)
func main() {
app := pocketbase.New()
app.RootCmd.AddCommand(&cobra.Command{
Use: "hello",
Run: func(cmd *cobra.Command, args []string) {
log.Println("Hello world!")
},
})
if err := app.Start(); err != nil {
log.Fatal(err)
}
}`
To run the command you can build your Go application and execute:
`# or "go run main.go hello"
./myapp hello`
Keep in mind that the console commands execute in their own separate app process and run
independently from the main `serve` command (aka. hook events between different processes
are not shared with one another).
* * *
[Prev: Rendering templates](https://pocketbase.io/docs/go-rendering-templates) [Next: Realtime messaging](https://pocketbase.io/docs/go-realtime)
## Custom Console Commands
Console commands
You can register custom console commands using
`app.rootCmd.addCommand(cmd)`, where `cmd` is a
[Command](https://pocketbase.io/jsvm/classes/Command.html) instance.
Here is an example:
`$app.rootCmd.addCommand(new Command({
use: "hello",
run: (cmd, args) => {
console.log("Hello world!")
},
}))`
To run the command you can execute:
`./pocketbase hello`
Keep in mind that the console commands execute in their own separate app process and run
independently from the main `serve` command (aka. hook events between different processes
are not shared with one another).
* * *
[Prev: Rendering templates](https://pocketbase.io/docs/js-rendering-templates) [Next: Sending HTTP requests](https://pocketbase.io/docs/js-sending-http-requests)
## API Rules and Filters
API rules and filters
- [API rules](https://pocketbase.io/docs/api-rules-and-filters/#api-rules)
- [Filters syntax](https://pocketbase.io/docs/api-rules-and-filters/#filters-syntax)
- [Special identifiers and modifiers](https://pocketbase.io/docs/api-rules-and-filters/#special-identifiers-and-modifiers)
- [@ macros](https://pocketbase.io/docs/api-rules-and-filters/#-macros)
- [:isset modifier](https://pocketbase.io/docs/api-rules-and-filters/#isset-modifier)
- [:length modifier](https://pocketbase.io/docs/api-rules-and-filters/#length-modifier)
- [:each modifier](https://pocketbase.io/docs/api-rules-and-filters/#each-modifier)
- [:lower modifier](https://pocketbase.io/docs/api-rules-and-filters/#lower-modifier)
- [Examples](https://pocketbase.io/docs/api-rules-and-filters/#examples)
### [API rules](https://pocketbase.io/docs/api-rules-and-filters/\#api-rules)
**API Rules** are your collection access controls and data filters.
Each collection has **5 rules**, corresponding to the specific API action:
- `listRule`
- `viewRule`
- `createRule`
- `updateRule`
- `deleteRule`
Auth collections has an additional `options.manageRule` used to allow one user (it could be even
from a different collection) to be able to fully manage the data of another user (ex. changing their email,
password, etc.).
Each rule could be set to:
- **"locked"** \- aka. `null`, which means that the action could be performed
only by an authorized superuser
( **this is the default**)
- **Empty string** \- anyone will be able to perform the action (superusers, authorized users
and guests)
- **Non-empty string** \- only users (authorized or not) that satisfy the rule filter expression
will be able to perform this action
**PocketBase API Rules act also as records filter!**
Or in other words, you could for example allow listing only the "active" records of your collection,
by using a simple filter expression such as:
`status = "active"`
(where "status" is a field defined in your Collection).
Because of the above, the API will return 200 empty items response in case a request doesn't
satisfy a `listRule`, 400 for unsatisfied `createRule` and 404 for
unsatisfied `viewRule`, `updateRule` and `deleteRule`.
All rules will return 403 in case they were "locked" (aka. superuser only) and the request client is
not a superuser.
The API Rules are ignored when the action is performed by an authorized superuser ( **superusers can access everything**)!
### [Filters syntax](https://pocketbase.io/docs/api-rules-and-filters/\#filters-syntax)
You can find information about the available fields in your collection API rules tab:

There is autocomplete to help you guide you while typing the rule filter expression, but in general you
have access to **3 groups of fields**:
- **Your Collection schema fields**
This includes all nested relation fields too, ex.
`someRelField.status != "pending"`
- `@request.*`
Used to access the current request data, such as query parameters, body/form fields, authorized user state,
etc.
- `@request.context` \- the context where the rule is used (ex.
`@request.context != "oauth2"`)
The currently supported context values are
`default`,
`oauth2`,
`otp`,
`password`,
`realtime`,
`protectedFile`.
- `@request.method` \- the HTTP request method (ex.
`@request.method = "GET"`)
- `@request.headers.*` \- the request headers as string values (ex.
`@request.headers.x_token = "test"`)
Note: All header keys are normalized to lowercase and "-" is replaced with "\_" (for
example "X-Token" is "x\_token").
- `@request.query.*` \- the request query parameters as string values (ex.
`@request.query.page = "1"`)
- `@request.auth.*` \- the current authenticated model (ex.
`@request.auth.id != ""`)
- `@request.body.*` \- the submitted body parameters (ex.
`@request.body.title != ""`)
Note: Uploaded files are not part of the `@request.body`
because they are evaluated separately ( _this behavior may change in the future_).
- `@collection.*`
This filter could be used to target other collections that are not directly related to the current
one (aka. there is no relation field pointing to it) but both shares a common field value, like
for example a category id:
`@collection.news.categoryId ?= categoryId && @collection.news.author ?= @request.auth.id`
In case you want to join the same collection multiple times but based on different criteria, you
can define an alias by appending `:alias` suffix to the collection name.
`// see https://github.com/pocketbase/pocketbase/discussions/3805#discussioncomment-7634791
@request.auth.id != "" &&
@collection.courseRegistrations.user ?= id &&
@collection.courseRegistrations:auth.user ?= @request.auth.id &&
@collection.courseRegistrations.courseGroup ?= @collection.courseRegistrations:auth.courseGroup`
The syntax basically follows the format
`OPERAND OPERATOR OPERAND`, where:
- `OPERAND` \- could be any field literal, string (single or double quoted),
number, null, true, false
- `OPERATOR` \- is one of:
- `=` Equal
- `!=` NOT equal
- `>` Greater than
- `>=` Greater than or equal
- `<` Less than
- `<=` Less than or equal
- `~` Like/Contains (if not specified auto wraps the right string OPERAND in a "%" for wildcard
match)
- `!~` NOT Like/Contains (if not specified auto wraps the right string OPERAND in a "%" for
wildcard match)
- `?=` _Any/At least one of_ Equal
- `?!=` _Any/At least one of_ NOT equal
- `?>` _Any/At least one of_ Greater than
- `?>=` _Any/At least one of_ Greater than or equal
- `?<` _Any/At least one of_ Less than
- `?<=` _Any/At least one of_ Less than or equal
- `?~` _Any/At least one of_ Like/Contains (if not specified auto wraps the right string OPERAND in a "%" for wildcard
match)
- `?!~` _Any/At least one of_ NOT Like/Contains (if not specified auto wraps the right string OPERAND in a "%" for
wildcard match)
To group and combine several expressions you can use parenthesis
`(...)`, `&&` (AND) and `||` (OR) tokens.
Single line comments are also supported: `// Example comment`.
### [Special identifiers and modifiers](https://pocketbase.io/docs/api-rules-and-filters/\#special-identifiers-and-modifiers)
##### [@ macros](https://pocketbase.io/docs/api-rules-and-filters/\#-macros)
The following datetime macros are available and can be used as part of the filter expression:
`// all macros are UTC based
@now - the current datetime as string
@second - @now second number (0-59)
@minute - @now minute number (0-59)
@hour - @now hour number (0-23)
@weekday - @now weekday number (0-6)
@day - @now day number
@month - @now month number
@year - @now year number
@yesterday - the yesterday datetime relative to @now as string
@tomorrow - the tomorrow datetime relative to @now as string
@todayStart - beginning of the current day as datetime string
@todayEnd - end of the current day as datetime string
@monthStart - beginning of the current month as datetime string
@monthEnd - end of the current month as datetime string
@yearStart - beginning of the current year as datetime string
@yearEnd - end of the current year as datetime string`
For example:
`@request.body.publicDate >= @now`
##### [:isset modifier](https://pocketbase.io/docs/api-rules-and-filters/\#isset-modifier)
The `:isset` field modifier is available only for the `@request.*` fields and can be
used to check whether the client submitted a specific data with the request. Here is for example a rule that
disallows changing a "role" field:
`@request.body.role:isset = false`
Note that `@request.body.*:isset` at the moment doesn't support checking for
new uploaded files because they are evaluated separately and cannot be serialized ( _this behavior may change in the future_).
##### [:length modifier](https://pocketbase.io/docs/api-rules-and-filters/\#length-modifier)
The `:length` field modifier could be used to check the number of items in an array field
(multiple `file`, `select`, `relation`).
Could be used with both the collection schema fields and the `@request.body.*` fields. For example:
`// check example submitted data: {"someSelectField": ["val1", "val2"]}
@request.body.someSelectField:length > 1
// check existing record field length
someRelationField:length = 2`
Note that `@request.body.*:length` at the moment doesn't support checking
for new uploaded files because they are evaluated separately and cannot be serialized ( _this behavior may change in the future_).
##### [:each modifier](https://pocketbase.io/docs/api-rules-and-filters/\#each-modifier)
The `:each` field modifier works only with multiple `select`, `file` and
`relation`
type fields. It could be used to apply a condition on each item from the field array. For example:
`// check if all submitted select options contain the "create" text
@request.body.someSelectField:each ~ "create"
// check if all existing someSelectField has "pb_" prefix
someSelectField:each ~ "pb_%"`
Note that `@request.body.*:each` at the moment doesn't support checking for
new uploaded files because they are evaluated separately and cannot be serialized ( _this behavior may change in the future_).
##### [:lower modifier](https://pocketbase.io/docs/api-rules-and-filters/\#lower-modifier)
The `:lower` field modifier could be used to perform lower-case string comparisons. For example:
`// check if the submitted lower-cased body "title" field is equal to "test" ("Test", "tEsT", etc.)
@request.body.title:lower = "test"
// match existing records with lower-cased "title" equal to "test" ("Test", "tEsT", etc.)
title:lower ~ "test"`
Under the hood it uses the
[SQLite `LOWER` scalar function](https://www.sqlite.org/lang_corefunc.html#lower)
and by default works only for ASCII characters, unless the ICU extension is loaded.
### [Examples](https://pocketbase.io/docs/api-rules-and-filters/\#examples)
- Allow only registered users:
`@request.auth.id != ""`
- Allow only registered users and return records that are either "active" or "pending":
`@request.auth.id != "" && (status = "active" || status = "pending")`
- Allow only registered users who are listed in an _allowed\_users_ multi-relation field value:
`@request.auth.id != "" && allowed_users.id ?= @request.auth.id`
- Allow access by anyone and return only the records where the _title_ field value starts with
"Lorem" (ex. "Lorem ipsum"):
`title ~ "Lorem%"`
* * *
[Prev: Collections](https://pocketbase.io/docs/collections) [Next: Authentication](https://pocketbase.io/docs/authentication)
## Sending HTTP Requests
Sending HTTP requests
- [Overview](https://pocketbase.io/docs/js-sending-http-requests/#overview)
- [multipart/form-data requests](https://pocketbase.io/docs/js-sending-http-requests/#multipartform-data-requests)
- [Limitations](https://pocketbase.io/docs/js-sending-http-requests/#limitations)
### [Overview](https://pocketbase.io/docs/js-sending-http-requests/\#overview)
You can use the global `$http.send(config)` helper to send HTTP requests to external services.
This could be used for example to retrieve data from external data sources, to make custom requests to a payment
provider API, etc.
Below is a list with all currently supported config options and their defaults.
`// throws on timeout or network connectivity error
const res = $http.send({
url: "",
method: "GET",
body: "", // ex. JSON.stringify({"test": 123}) or new FormData()
headers: {}, // ex. {"content-type": "application/json"}
timeout: 120, // in seconds
})
console.log(res.headers) // the response headers (ex. res.headers['X-Custom'][0])
console.log(res.cookies) // the response cookies (ex. res.cookies.sessionId.value)
console.log(res.statusCode) // the response HTTP status code
console.log(res.raw) // the response body as plain text
console.log(res.json) // the response body as parsed json array or map`
Here is an example that will enrich a single book record with some data based on its ISBN details from
openlibrary.org.
`onRecordCreateRequest((e) => {
let isbn = e.record.get("isbn");
// try to update with the published date from the openlibrary API
try {
const res = $http.send({
url: "https://openlibrary.org/isbn/" + isbn + ".json",
headers: {"content-type": "application/json"}
})
if (res.statusCode == 200) {
e.record.set("published", res.json.publish_date)
}
} catch (err) {
e.app.logger().error("Failed to retrieve book data", "error", err);
}
return e.next()
}, "books")`
##### [multipart/form-data requests](https://pocketbase.io/docs/js-sending-http-requests/\#multipartform-data-requests)
In order to send `multipart/form-data` requests (ex. uploading files) the request
`body` must be a `FormData` instance.
PocketBase JSVM's `FormData` has the same APIs as its
[browser equivalent](https://developer.mozilla.org/en-US/docs/Web/API/FormData)
with the main difference that for file values instead of `Blob` it accepts
[`$filesystem.File`](https://pocketbase.io/jsvm/modules/_filesystem.html).
`const formData = new FormData();
formData.append("title", "Hello world!")
formData.append("documents", $filesystem.fileFromBytes("doc1", "doc1.txt"))
formData.append("documents", $filesystem.fileFromBytes("doc2", "doc2.txt"))
const res = $http.send({
url: "https://...",
method: "POST",
body: formData,
})
console.log(res.statusCode)`
### [Limitations](https://pocketbase.io/docs/js-sending-http-requests/\#limitations)
As of now there is no support for streamed responses or server-sent events (SSE). The
`$http.send` call blocks and returns the entire response body at once.
For this and other more advanced use cases you'll have to
[extend PocketBase with Go](https://pocketbase.io/docs/go-overview/).
* * *
[Prev: Console commands](https://pocketbase.io/docs/js-console-commands) [Next: Realtime messaging](https://pocketbase.io/docs/js-realtime)
## PocketBase Realtime Messaging
Realtime messaging
By default PocketBase sends realtime events only for Record create/update/delete operations ( _and for the OAuth2 auth redirect_), but you are free to send custom realtime messages to the connected clients via the
[`$app.subscriptionsBroker()`](https://pocketbase.io/jsvm/functions/_app.subscriptionsBroker.html) instance.
[`$app.subscriptionsBroker().clients()`](https://pocketbase.io/jsvm/interfaces/subscriptions.Broker.html#clients)
returns all connected
[`subscriptions.Client`](https://pocketbase.io/jsvm/interfaces/subscriptions.Client.html)
indexed by their unique connection id.
The current auth record associated with a client could be accessed through
`client.get("auth")`
Note that a single authenticated user could have more than one active realtime connection (aka.
multiple clients). This could happen for example when opening the same app in different tabs,
browsers, devices, etc.
Below you can find a minimal code sample that sends a JSON payload to all clients subscribed to the
"example" topic:
`const message = new SubscriptionMessage({
name: "example",
data: JSON.stringify({ ... }),
});
// retrieve all clients (clients id indexed map)
const clients = $app.subscriptionsBroker().clients()
for (let clientId in clients) {
if (clients[clientId].hasSubscription("example")) {
clients[clientId].send(message)
}
}`
From the client-side, users can listen to the custom subscription topic by doing something like:
JavaScript
Dart
`import PocketBase from 'pocketbase';
const pb = new PocketBase('http://127.0.0.1:8090');
...
await pb.realtime.subscribe('example', (e) => {
console.log(e)
})`
`import 'package:pocketbase/pocketbase.dart';
final pb = PocketBase('http://127.0.0.1:8090');
...
await pb.realtime.subscribe('example', (e) {
print(e)
})`
* * *
[Prev: Sending HTTP requests](https://pocketbase.io/docs/js-sending-http-requests) [Next: Filesystem](https://pocketbase.io/docs/js-filesystem)
## Using PocketBase APIs
How to use PocketBase
The easiest way to use PocketBase is by interacting with its Web APIs directly from the client-side (e.g.
mobile app or browser SPA).
It was designed with this exact use case in mind and it is also the reason why there are general purpose
JSON APIs for listing, pagination, sorting, filtering, etc.
The access and filter controls for your data is usually done through the
[collection API rules](https://pocketbase.io/docs/api-rules-and-filters/)
.
For the cases when you need more specialized handling (sending emails, intercepting the default actions,
creating new routes, etc.) you can
[extend PocketBase with Go or JavaScript](https://pocketbase.io/docs/use-as-framework/)
.
For interacting with the
[Web APIs](https://pocketbase.io/docs/api-records/)
you can make use of the official SDK clients:
- [JavaScript SDK](https://github.com/pocketbase/js-sdk)(Browser, Node.js, React Native)
- [Dart SDK](https://github.com/pocketbase/dart-sdk)(Web, Mobile, Desktop, CLI)
When used on the client-side, it is safe to have a single/global SDK instance for the entire lifecycle of
your application.
**[Web apps recommendation](https://pocketbase.io/docs/how-to-use/#web-apps-recommendation)**
Not everyone will agree with this, but if you are building a web app with PocketBase I recommend
developing the frontend as a **traditional client-side SPA** and for the cases where additional
server-side handling is needed (e.g. for payment webhooks, extra data server validations, etc.) you
could try to:
- [Use PocketBase as Go/JS framework](https://pocketbase.io/docs/use-as-framework) to create new routes or
intercept existing.
- Create one-off Node.js/Bun/Deno/etc. server-side actions that will interact with
PocketBase only as superuser and as pure data store (similar to traditional database
interactions but over HTTP). In this case it is safe to have a global superuser client
such as:
`// src/superuser.js
import PocketBase from "pocketbase"
const superuserClient = new PocketBase('https://example.com');
// disable autocancellation so that we can handle async requests from multiple users
superuserClient.autoCancellation(false);
// option 1: authenticate as superuser using email/password (could be filled with ENV params)
await superuserClient.collection('_superusers').authWithPassword(SUPERUSER_EMAIL, SUPERUSER_PASS, {
// This will trigger auto refresh or auto reauthentication in case
// the token has expired or is going to expire in the next 30 minutes.
autoRefreshThreshold: 30 * 60
})
// option 2: OR authenticate as superuser via long-lived "API key"
// (see https://pocketbase.io/docs/authentication/#api-keys)
superuserClient.authStore.save('YOUR_GENERATED_SUPERUSER_TOKEN')
export default superuserClient;`
Then you can import directly the file in your server-side actions and use the client as
usual:
`import superuserClient from './src/superuser.js'
async function serverAction(req, resp) {
... do some extra data validations or handling ...
// send a create request as superuser
await superuserClient.collection('example').create({ ... })
}`
**[Why not JS SSR](https://pocketbase.io/docs/how-to-use/#why-not-js-ssr)**
Using PocketBase with meta framework such as SvelteKit, Nuxt, Next.js, etc.
**in a JS SSR mode**
is possible but it comes with many complications and you need to carefully evaluate whether the cost
of having another backend (PocketBase) along-side your existing one (the Node.js server) is worth it.
You can read more about the potential problems in
[JS SSR - issues and recommendations #5313](https://github.com/pocketbase/pocketbase/discussions/5313) but some of the common pitfalls are:
- Security issues caused by incorrectly initialized and shared JS SDK instance in a long-running
server-side context.
- OAuth2 integration difficulties related to the server-side only OAuth2 flow (or its mixed
"all-in-one" client-side handling and sharing a cookie with the server-side).
- Proxying realtime connections and essentially duplicating the same thing PocketBase already
does.
- Performance bottlenecks caused by the default single-threaded Node.js process and the
excessive resources utilization due to the server-side rendering and heavy back-and-forth
requests communication between the different layers (client<->Node.js<->PocketBase).
This doesn't mean that using PocketBase with JS SSR is always a "bad thing" but based on the
dozens reported issues so far I would recommend it only after careful evaluation and only to more
experienced developers that have in-depth understanding of the used tools and their trade-offs. If
you still want to use PocketBase to handle regular users authentication with a JS SSR meta
framework, then you can find some JS SDK examples in the repo's
[JS SSR integration section](https://github.com/pocketbase/js-sdk#ssr-integration).
**[Why not htmx, Hotwire/Turbo, Unpoly, etc.](https://pocketbase.io/docs/how-to-use/#why-not-htmx-hotwireturbo-unpoly-etc-)**
htmx, Hotwire/Turbo, Unpoly and other similar tools are commonly used for building server rendered
applications but unfortunately they don't play well with the JSON APIs and fully stateless nature
of PocketBase.
It is possible to use them with PocketBase but at the moment I don't recommend it because we lack
the necessary helpers and utilities for building SSR-first applications, which means that you
might have to create from scratch a lot of things on your own such as middlewares for handling
cookies ( _and eventually taking care also for CORS and CSRF_) or custom authentication
endpoints and access controls ( _the collection API rules apply only for the builtin JSON routes_).
In the future we could eventually provide official SSR support in terms of guides and middlewares
for this use case but again - PocketBase wasn't designed with this in mind and you may want to
reevaluate the tech stack of your application and switch to a traditional client-side SPA as
mentioned earlier or use a different backend solution that might fit better with your use case.
**[Mobile apps auth persistence](https://pocketbase.io/docs/how-to-use/#mobile-apps-auth-persistence)**
When building mobile apps with the JavaScript SDK or Dart SDK you'll have to specify a custom
persistence store if you want to preserve the authentication between the various app activities
and open/close state.
The SDKs comes with a helper async storage implementation that allows you to hook any custom
persistent layer (local file, SharedPreferences, key-value based database, etc.). Here is a
minimal PocketBase SDKs initialization for React Native (JavaScript) and Flutter (Dart):
JavaScript
Dart
`// Node.js and React Native doesn't have native EventSource implementation
// so in order to use the realtime subscriptions you'll need to load EventSource polyfill,
// for example: npm install react-native-sse --save
import eventsource from 'react-native-sse';
import AsyncStorage from '@react-native-async-storage/async-storage';
import PocketBase, { AsyncAuthStore } from 'pocketbase';
// load the polyfill
global.EventSource = eventsource;
// initialize the async store
const store = new AsyncAuthStore({
save: async (serialized) => AsyncStorage.setItem('pb_auth', serialized),
initial: AsyncStorage.getItem('pb_auth'),
});
// initialize the PocketBase client
// (it is OK to have a single/global instance for the duration of your application)
const pb = new PocketBase('http://127.0.0.1:8090', store);
...
await pb.collection('users').authWithPassword('test@example.com', '1234567890');
console.log(pb.authStore.record)`
`import 'package:pocketbase/pocketbase.dart';
import 'package:shared_preferences/shared_preferences.dart';
// for simplicity we are using a simple SharedPreferences instance
// but you can also replace it with its safer EncryptedSharedPreferences alternative
final prefs = await SharedPreferences.getInstance();
// initialize the async store
final store = AsyncAuthStore(
save: (String data) async => prefs.setString('pb_auth', data),
initial: prefs.getString('pb_auth'),
);
// initialize the PocketBase client
// (it is OK to have a single/global instance for the duration of your application)
final pb = PocketBase('http://127.0.0.1:8090', authStore: store);
...
await pb.collection('users').authWithPassword('test@example.com', '1234567890');
print(pb.authStore.record);`
**[React Native file upload on Android and iOS](https://pocketbase.io/docs/how-to-use/#react-native-file-upload-on-android-and-ios)**
At the time of writing, React Native on Android and iOS seems to have a non-standard
`FormData` implementation and for uploading files on these platforms it requires the following
special object syntax:
`{
uri: "...",
type: "...",
name: "..."
}`
Or in other words, you may have to apply a conditional handling similar to:
`const data = new FormData();
// result is the resolved promise of ImagePicker.launchImageLibraryAsync
let imageUri = result.assets[0].uri;
if (Platform.OS === 'web') {
const req = await fetch(imageUri);
const blob = await req.blob();
data.append('avatar', blob); // regular File/Blob value
} else {
// the below object format works only on Android and iOS
// (FormData.set() also doesn't seem to be supported so we use FormData.append())
data.append('avatar', {
uri: imageUri,
type: 'image/*',
name: imageUri.split('/').pop(),
});
}
...
await pb.collection('example').create(data)`
The next couple pages have a little bit more information about the basic PocketBase components like
collections, records, authentication, relations, files handling, etc.
* * *
[Prev: Introduction](https://pocketbase.io/docs) [Next: Collections](https://pocketbase.io/docs/collections)