Preferences
Define and manage preferences with scoped settings.
The Preferences plugin allows you to define, read, and write preferences across multiple scopes (e.g., user, project, organization). It supports scoped preferences with optional scopeId
, group operations, default values, and encryption for sensitive values.
Installation
Install the plugin
npm install @better-auth-extended/preferences
Add the plugin to your auth config
To use the Preferences plugin, add it to your auth config.
import { betterAuth } from "better-auth";
import { preferences, createPreferenceScope } from "@better-auth-extended/preferences";
import { z } from "zod";
export const auth = betterAuth({
// ... other config options
plugins: [
preferences({
scopes: {
user: createPreferenceScope({
preferences: {
theme: { type: z.enum(["light", "dark", "system"]) },
notifications: { type: z.boolean() },
},
defaultValues: {
theme: "system",
notifications: false,
},
}),
project: createPreferenceScope({
preferences: {
buildChannel: { type: z.enum(["stable", "beta"]) },
envSecrets: { type: z.record(z.string(), z.string()), sensitive: true },
},
requireScopeId: true,
groups: {
ui: {
preferences: {
buildChannel: true,
envSecrets: true
},
operations: ["read", "write"],
},
},
}),
},
}),
],
});
Add the client plugin
Include the client plugin in your auth client instance.
import { createAuthClient } from "better-auth/client";
import { preferencesClient } from "@better-auth-extended/preferences/client";
const authClient = createAuthClient({
plugins: [preferencesClient()],
});
Run migrations
This plugin adds an additional table to the database. Click here to see the schema
npx @better-auth/cli migrate
or generate
npx @better-auth/cli generate
Basic Usage
The Preferences plugin provides several endpoints to manage preferences. You can get and set individual preferences or work with groups of preferences.
Get Preference
Retrieve a single preference value by scope and key.
const { data, error } = await authClient.preferences.user.theme.get({ scopeId,});
Prop | Description | Type |
---|---|---|
scopeId? | The scope ID if the scope requires it | string |
or
Types won't be inferred correctly for this endpoint.
const { data, error } = await authClient.preferences.getPreference({ scope, // required key, // required scopeId,});
Prop | Description | Type |
---|---|---|
scope | The scope of the preference | string |
key | The key of the preference | string |
scopeId? | The scope ID if the scope requires it | string |
Set Preference
Set or update a single preference value.
const { data, error } = await authClient.preferences.user.theme.set({ scopeId, value, // required});
Prop | Description | Type |
---|---|---|
scopeId? | The scope ID if the scope requires it | string |
value | The value to set | "light" | "dark" | "system" |
or
Types won't be inferred correctly for this endpoint.
const { data, error } = await authClient.preferences.setPreference({ scope, // required key, // required value, // required scopeId,});
Prop | Description | Type |
---|---|---|
scope | The scope of the preference | string |
key | The key of the preference | string |
value | The value to set | unknown |
scopeId? | The scope ID if the scope requires it | string |
Get Group
Retrieve multiple preferences at once using a defined group.
const { data, error } = await authClient.preferences.project.$ui.get({ scopeId,});
Prop | Description | Type |
---|---|---|
scopeId? | The scope ID if the scope requires it | string |
Set Group
Set multiple preferences at once using a defined group.
const { data, error } = await authClient.preferences.project.$ui.set({ scopeId, values, // required});
Prop | Description | Type |
---|---|---|
scopeId? | The scope ID if the scope requires it | string |
values | The values to set | Record<string, unknown> |
Creating Scopes
Define your scopes with their preferences, validation types, defaults, optional groups, and access rules. Each scope and preference automatically generates the corresponding endpoints above.
import { preferences, createPreferenceScope } from "@better-auth-extended/preferences";
import { z } from "zod";
preferences({
scopes: {
user: createPreferenceScope({
preferences: {
theme: { type: z.enum(["light", "dark", "system"]) },
notifications: { type: z.boolean() },
},
defaultValues: {
theme: "system",
notifications: false,
},
}),
project: preferences.createScope({
preferences: {
buildChannel: { type: z.enum(["stable", "beta"]) },
envSecrets: { type: z.record(z.string(), z.string()), sensitive: true },
},
requireScopeId: true,
groups: {
ui: {
preferences: {
buildChannel: true,
envSecrets: true
},
operations: ["read", "write"],
},
},
}),
},
});
Options
preferences: Record<string, { type: StandardSchemaV1; sensitive?: boolean; secret?: string; }>
- Object mapping preference keys to their type definitions.
groups: Record<string, { preferences: Partial<Record<keyof preferences, boolean>>; operations?: "read" | "write" | ("read" | "write")[]; }>
- Optional groups for batch operations.
defaultValues: Partial<Record<keyof preferences, any | (() => any)>>
- Default values for preferences. Can be static values or functions.
canRead: boolean | Permission
| (data, ctx) => boolean | Permission | Promise<boolean | Permission>
- Permission check for reading preferences.
canWrite: boolean | Permission
| (data, ctx) => boolean | Permission | Promise<boolean | Permission>
- Permission check for writing preferences.
Returning Permission
requires the admin plugin to be configured. If it's not set up, the request will fail.
Example:
canWrite: {
statement: "preferences",
permissions: ["write"],
};
requireScopeId: boolean
- Whether the scope requires a scopeId
for all operations.
disableUserBinding: boolean
- Whether to disable binding preferences to users.
mergeStrategy: "deep"
| "replace"
- How to merge default values with stored values.
sensitive: boolean
- Whether to encrypt values at rest for the entire scope.
secret: string
- Custom secret for encryption/decryption.
Schema
Table Name: preference
Field Name | Type | Key | Description |
---|---|---|---|
id | string | Unique identifier | |
userId | string | User owner (nullable when unbound) | |
scopeId | string | Resource identifier (e.g., project) | |
scope | string | - | Scope key (e.g., user, project) |
key | string | - | Preference key |
value | string | - | JSON-encoded value (may be encrypted) |
updatedAt | Date | - | Last update timestamp |
Options
scopes: Record<string, PreferenceScopeAttributes>
- Object mapping scope names to their configuration.
How is this guide?