Refine providers for integrating your frontend application with ease into your PocketBase backend. It implements the three common providers for user authentication, CRUD, and live updates. It's based on the PocketBase JS SDK and can also be used in conjunction with pocketbase-typegen for improved type safety.
npm install refine-pocketbaseimport PocketBase from "pocketbase";
import { authProvider, dataProvider, liveProvider } from "refine-pocketbase";
const pb = new PocketBase(POCKETBASE_URL);
<Refine
authProvider={authProvider(pb)}
dataProvider={dataProvider(pb)}
liveProvider={liveProvider(pb)}
...
>
...
</Refine>The code below uses useList to fetch a list of users. The resulting list contains user records with the id, name, avatar and the name of the organisation a user is assigned to. The meta properties fields and expand are used to customize the server response to the requirements of the user interface.
const users = useList({
resource: "users",
meta: {
fields: ["id", "name", "avatar", "expand.org.name"],
expand: ["org"],
},
});Here fields is an array of strings limiting the fields to return in the server's response. expand is an array with names of the related records that will be included in the response. Pocketbase supports up to 6-level depth nested relations expansion. See https://pocketbase.io/docs/api-records for more examples.
A couple of other refine hooks and components like useOne, useTable, <Show/>, <List/>, etc. will support the meta props fields and expand if used with the refine-pocketbase data provider.
The in or nin filters expect an array as value as show in the code fragment below.
{
field: "a",
operator: "in",
value: [1, 2, 3],
}This expression will be transformed to a pocketbase filter expression (a = 1 || a = 2 || a = 3).
A similar expression using nin filter will be transformed to b != 1 && b != 2 && b != 3.
Setting an empty array [] as a filter value will cause the in or nin filter to be excluded from the resulting filter expression.
The between or nbetween filters expect a tuple [min, max] as value as show in the code fragment below.
{
field: "a",
operator: "between",
value: [1, 2],
}This expression will be transformed to a pocketbase filter expression (a >= 1 && a <= 2).
The same expression but with nin as the operator will be transformed to (a < 1 || a > 2).
Partial tuples in form of [min, undefined/null] or [undefined/null, max] are possible as well and would omit either one side of the join operator.
An empty tuple [] will cause the filter to be excluded from the resulting filter.
The useCustom hook allows you to make custom API calls to your PocketBase backend. This is particularly useful when you need to interact with custom PocketBase endpoints.
Here's an example of how to use the useCustom hook:
const apiUrl = useApiUrl();
const { data, isLoading } = useCustom({
url: `${apiUrl}/api/custom-endpoint`,
method: "get",
});A number of configuration properties are supported by the auth provider, primarily for controlling redirection following specific auth events. Please take a look at the self-explanatory names in the AuthOptions typescript interface to find the available options.
import { authProvider, AuthOptions } from "refine-pocketbase";
const authOptions: AuthOptions = {
loginRedirectTo: "/dashboard",
};
<Refine
authProvider={authProvider(pb, authOptions)}
...
>
...
</Refine>users is the default auth collection in Pocketbase. Several auth collections can be supported in a single Pocketbase instance. You can use a different collection with the authProvider by using the collection property:
const authOptions: AuthOptions = {
collection: "superusers",
};The PocketBase OAuth2Config can be set either via the mutationVariables prop in AuthPage,
<AuthPage
type="login"
mutationVariables={{
scopes: ["user-read-private", "user-top-read"],
}}
providers={[
{
name: "spotify",
label: "Login with Spotify",
icon: <SpotifyIcon />,
},
]}
/>or imperatively via useLogin:
import { LoginWithProvider } from "refine-pocketbase";
const { mutate: login } = useLogin<LoginWithProvider>();
login({
scopes: ["user-read-private", "user-top-read"],
providerName: "spotify",
});For improved type safety, refine-pocketbase exports the LoginWithProvider type. With this, TypeScript can warn you if a property name or value type is incorrect and provide you with autocompletion for a better developer experience.
For password-based authentication, you can pass an optional options property that meets PocketBase's RecordOptions interface.
import { LoginWithPassword } from "refine-pocketbase";
const { mutate: login } = useLogin<LoginWithPassword>();
login({
email: "[email protected]",
password: "********",
options: {
expand: "orgs,permissions",
headers: { key: "value" },
... // more RecordOptions props
},
});
// similar scenario but using AuthPage:
<AuthPage
type="login"
mutationVariables={{
email: "[email protected]",
password: "********",
options: {
expand: "orgs,permissions",
headers: { key: "value" },
... // more RecordOptions props
},
}}
/>For passwordless authentication using a one-time password (OTP), the process consists of two steps.
In the first step, an OTP needs to be requested. To do this, make a login call without providing a password. This will send the OTP to the user via email. Currently, this only works if the user is already registered.
After that, the user needs to enter the OTP received in the email. This OTP must then be passed to the otpHandle.resolve method within the same session. Note that refreshing the page or navigating to another page will reset the state, requiring the process to be restarted from the beginning.
import { type LoginArgs, useOtp } from "refine-pocketbase";
const { mutate: login } = useLogin<LoginArgs>();
const otpHandler = useOtp();
// 1. login without password requests an otp
login({
email: "[email protected]",
otpHandler,
});
// 2. OTP request needs to be resolved in the same session
otpHandler.resolve("your-otp");A full example is available in demo/src/pages/LoginPage.tsx.
Note
To enable OTP functionality, configure PocketBase to send one-time passwords.
MFA with a password and OTP is similar to passwordless authentication. The only difference is that MFA requires a password. See the previous section for more details.
// 1. requests an otp
login({
email: "[email protected]",
password: "1234567890", //
otpHandler,
});
// 2. enter otp
otpHandler.resolve("your-otp");A full example is available in demo/src/pages/LoginPage.tsx.
Note
To enable MFA functionality, configure PocketBase to send one-time passwords and enable multi-factor authentication.
If you want to use translation, pass the translate function returned by useTranslate to the corresponding adapter calls (login in this case).
const { mutate: login } = useLogin<LoginArgs>();
const translate = useTranslate();
login({
email,
password,
translate, // <~ pass on the translate function here
});Please expand the following section to view the corresponding translation keys for your custom translation texts.
Register
key |
defaultMessage |
type |
|---|---|---|
authProvider.register.requestVerificationMessage |
Account verification | success |
authProvider.register.requestVerificationDescription |
Please verify your account by clicking the link we sent to your email address | success |
authProvider.register.completedMessage |
Registration completed | success |
authProvider.register.completedDescription |
Please sign in using your credentials | success |
authProvider.register.errorName |
Registration failed | error |
authProvider.register.errorMessage |
Something went wrong while creating your account. Please try again. | error |
Password Forgot
key |
defaultMessage |
type |
|---|---|---|
authProvider.forgotPassword.successMessage |
Password reset link sent | success |
authProvider.forgotPassword.successDescription |
Check your email for instructions to reset your password. | success |
authProvider.forgotPassword.errorMessage |
Password reset email not sent | error |
authProvider.forgotPassword.errorDescription |
Something went wrong while sending the reset link. Please check your email address and try again. | error |
Login
key |
defaultMessage |
type |
|---|---|---|
authProvider.login.successMessage |
Login successful | success |
authProvider.login.successDescription |
You're now signed in and ready to go. | success |
authProvider.login.errorName |
Something went wrong | error |
authProvider.login.errorMessage |
We couldn’t complete your request. Please refresh or try again later. | error |
authProvider.login.unsupportedLoginName |
Unsupported login | error |
authProvider.login.unsupportedLoginMessage |
This authentication method isn’t available. Try another way to sign in. | error |
authProvider.login.otpCanceled |
Verification canceled | error |
authProvider.login.otpCanceledMessage |
You stopped entering the code. Try again when you’re ready. | error |
authProvider.login.otpInvalid |
Invalid verification code | error |
authProvider.login.otpInvalidMessage |
The code you entered is invalid or has expired. Request a new one and try again. | error |
authProvider.login.mfaError |
Verification failed | error |
authProvider.login.mfaErrorMessage |
Multi-factor authentication was not completed successfully. Please try again. | error |
authProvider.login.credentialsError |
Invalid credentials | error |
authProvider.login.credentialsErrorMessage |
The email or password you entered is incorrect. Please try again. | error |
Update Password
key |
defaultMessage |
type |
|---|---|---|
authProvider.updatePassword.successMessage |
Password updated | success |
authProvider.updatePassword.successDescription |
Your password has been changed successfully. | success |
authProvider.updatePassword.errorMessage |
Password update failed | error |
authProvider.updatePassword.errorDescription |
Something went wrong while updating your password. Please try again later. | error |
The useGetIdentity hook returns a user object that includes an avatar thumbnail URL.
import { useGetIdentity } from "@refinedev/core";
const { data: identity } = useGetIdentity();
identity.avatar; // ~> http://127.0.0.1:8090/api/files/example/kfzjt5oy8r34hvn/test_52iWbGinWd.png?thumb=100x100By default, the avatar thumbnail size is 100x100 pixels.
You can customize this by setting the identityAvatarThumb option in authOptions when creating the authProvider:
const authOptions: AuthOptions = {
identityAvatarThumb: "128x128",
};
authProvider(pb, authOptions);Avatar URLs follow PocketBase’s file URL handling, since this library uses the PocketBase SDK internally.
- auth provider
- register
- register & verify
- login with password
- login with provider
- login with OTP (passwordless)
- login with MFA (password & OTP)
- forgot password
- update password
- data provider
- filters
- sorters
- pagination
- expand
- filter
- live provider
- subscribe
- unsubscribe
- audit log provider
-
auditLogProviderimplementation - happy path test specs
-
authProvider -
dataProvider(except fordeleteOne) -
liveProvider -
auditLogProvider
-
- test specs for
authProvidererror conditions-
register -
forgotPassword -
updatePassword -
login
-
- test specs for
dataProvidererror conditions-
getList -
create -
update -
getOne -
deleteOne
-
- test specs for
deleteOne - test specs with
expand-
getList -
getOne
-
- test specs with
fields-
getList -
getOne
-
- test specs for
auditLogProvidererrors - remove all requestKeys to prioritize sdk autoCancellation preference
- Setup Github Actions
- test environment
- build & publish
- leave a star ⭐
- report a bug 🐞
- open a pull request 🏗️
- help others ❤️
- buy me a coffee ☕
If you're tired of setting up another PocketBase instance, installing frontend dependencies, and building CI/CD pipelines, but would rather start shipping features or prototypes right away, I would be more than happy if you could try the headless refine-pocketbase-starter template. You can choose any UI library you prefer. refine-pocketbase-starter also includes a GitHub Actions workflow that builds a Docker image containing both PocketBase and your frontend. For easy self-hosting, the Docker image will be stored in your GitHub container registry (private or public) each time you push a new version tag. However, alternative container registries can also be used. Just follow the instructions in the documentation. If you have further questions, don't hesitate to ask in my Discord channel.

