Plugins

Anonymous

Create temporary users and upgrade them to real accounts later.

The anonymous plugin issues an account-less session so first-time visitors can save state before registering. When the visitor later signs in or signs up with a real method, Kernia runs your on_link callback to migrate their data, then deletes the anonymous user.

Installation

Add the plugin to your server

Pass anonymous() to your Kernia config. Supply an on_link callback to move domain data (carts, drafts, preferences) from the anonymous user onto the real user during conversion.

auth.py
import os
from kernia import KerniaOptions
from kernia.auth import init
from kernia.plugins.anonymous import anonymous

async def on_link(anonymous_user, new_user, ctx) -> None:
    await db.carts.reassign(
        from_user=anonymous_user["id"],
        to_user=new_user["id"],
    )

auth = init(KerniaOptions(
    database=adapter,
    secret=os.environ["KERNIA_SECRET"],
    base_url=os.environ["KERNIA_BASE_URL"],
    plugins=[anonymous(on_link=on_link)],
))

Add the client plugin

Kernia speaks the Better Auth wire protocol, so the official JavaScript client works unchanged. Add the anonymous client plugin:

auth-client.ts
import { createAuthClient } from "better-auth/client";
import { anonymousClient } from "better-auth/client/plugins";

export const authClient = createAuthClient({
  baseURL: "/api/auth",
  plugins: [anonymousClient()],
});

Usage

Start an anonymous session

await authClient.signIn.anonymous();

Kernia creates a user row flagged isAnonymous, opens a session, and sets the cookie. From here the visitor is authenticated like any other user.

Convert to a real account

There is no separate "link" call. When the anonymous user signs in or signs up with a real method, the plugin's hooks fire automatically:

// Same browser session, now graduating to a real account
await authClient.signUp.email({
  email: "user@example.com",
  name: "Ada",
  password: "the-password",
});

On success Kernia calls on_link(anonymous_user, new_user, ctx), then (unless disable_delete_anonymous_user is set) deletes the anonymous user and its sessions. Conversion hooks cover the email, username, magic-link, and passkey sign-in paths.

Options

Pass these as keyword arguments to anonymous(...):

OptionTypeDefaultDescription
on_linkasync (anonymous_user, new_user, ctx) -> NoneMigrates data before the anonymous user is deleted.
email_domain_namestrgeneratedDomain used for the placeholder email assigned to anonymous users.
generate_name(ctx) -> strCustom display-name generator for anonymous users.
generate_random_email() -> strCustom placeholder-email generator.
disable_delete_anonymous_userboolFalseKeep the anonymous row after conversion and disable /delete-anonymous-user.

Schema

The plugin extends the core user table with a single flag:

isAnonymous
booleanrequired
Marks an account-less user. Defaults to false.

No separate anonymous table is required.

Always do your data migration inside on_link — once conversion succeeds the anonymous user and its sessions are deleted, and the original id is gone.