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.

auth.ts
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.

GET/preferences/{scope}/{preference}/get
const { data, error } = await authClient.preferences.user.theme.get({    scopeId,});
PropDescriptionType
scopeId?
The scope ID if the scope requires it
string

or

GET/preferences/get-preference
Notes

Types won't be inferred correctly for this endpoint.

const { data, error } = await authClient.preferences.getPreference({    scope, // required    key, // required    scopeId,});
PropDescriptionType
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.

POST/preferences/{scope}/{preference}/set
const { data, error } = await authClient.preferences.user.theme.set({    scopeId,    value, // required});
PropDescriptionType
scopeId?
The scope ID if the scope requires it
string
value
The value to set
"light" | "dark" | "system"

or

POST/preferences/set-preference
Notes

Types won't be inferred correctly for this endpoint.

const { data, error } = await authClient.preferences.setPreference({    scope, // required    key, // required    value, // required    scopeId,});
PropDescriptionType
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.

GET/preferences/{scope}/${group}/get
const { data, error } = await authClient.preferences.project.$ui.get({    scopeId,});
PropDescriptionType
scopeId?
The scope ID if the scope requires it
string

Set Group

Set multiple preferences at once using a defined group.

POST/preferences/{scope}/${group}/set
const { data, error } = await authClient.preferences.project.$ui.set({    scopeId,    values, // required});
PropDescriptionType
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.

auth.ts
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:

auth.ts
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 NameTypeKeyDescription
idstringUnique identifier
userIdstringUser owner (nullable when unbound)
scopeIdstringResource identifier (e.g., project)
scopestring-Scope key (e.g., user, project)
keystring-Preference key
valuestring-JSON-encoded value (may be encrypted)
updatedAtDate-Last update timestamp

Options

scopes: Record<string, PreferenceScopeAttributes> - Object mapping scope names to their configuration.

On this page

Preferences | better-auth-extended