Skip to main content

User Synchronization

Maintain a rebuildable application user projection without duplicating Better Auth session truth.

Most applications do not need a second user table. Add one only when Convex product data needs indexed display/search fields or application-owned profile fields.

Source-of-truth rule

Better Auth user
  → trigger
  → derived application user row
  → application queries

The arrow is one-way for copied auth fields. The projection must be rebuildable from Better Auth.

Define the projection

convex/schema.ts
users: defineTable({
  authUserId: v.string(),
  name: v.optional(v.string()),
  image: v.optional(v.string()),
  createdAt: v.number(),
  updatedAt: v.number(),
}).index('by_auth_user_id', ['authUserId'])

Create sync triggers

convex/auth.ts
import {
  createUserSyncTriggers,
  type BetterAuthUserDocLike,
} from 'better-convex-nuxt/server/createUserSyncTriggers'

const userProjection = createUserSyncTriggers<BetterAuthUserDocLike, Doc<'users'>>({
  table: 'users',
  index: 'by_auth_user_id',
  authIdField: 'authUserId',
  createDoc: ({ user, now }) => ({
    authUserId: user._id,
    name: user.name ?? undefined,
    image: user.image ?? undefined,
    createdAt: now,
    updatedAt: now,
  }),
  patchDoc: ({ user, now }) => ({
    name: user.name ?? undefined,
    image: user.image ?? undefined,
    updatedAt: now,
  }),
  rebuildDoc: ({ user, now }) => ({
    name: user.name ?? undefined,
    image: user.image ?? undefined,
    updatedAt: now,
  }),
})

Attach userProjection.user as the component client's user triggers.

Rebuild and repair

The helper exposes user.rebuild(ctx, users) for a bounded page of canonical Better Auth users. An operator-only internal workflow should:

  1. Page through Better Auth users.
  2. Call rebuild for each page.
  3. Repeat until the canonical source reports completion.
  4. Record inserted, patched, and skipped counts.

The helper removes duplicate projection rows found by the auth ID index. It does not schedule rebuilds or own operator authorization.

Failure behavior

An update trigger that arrives before its create row cannot queue itself. Later creation uses its current user snapshot, and periodic rebuild can reconcile drift.

Do not use the projection for authentication or as the sole source of revocable authorization.