get-convex / get-convex/better-auth

Honor BA core's `advanced.generateId({ model })` for branded IDs on component tables

Open
#372 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
TypeScript
Stars
764
Forks
126
PR merge metrics
No merged PRs in 30d

Description

Better Auth core exposes `options.advanced.generateId({ model, size })` as the hook for application-supplied primary IDs. On the Convex adapter that hook is effectively a no-op, which makes it difficult to use Stripe/Linear style prefixed identifiers (`ws_*` on organization, `mbr_*` on member, `inv_*` on invitation, `sub_*` on subscription) without standing up parallel app-side tables per model.

The Convex adapter sets `disableIdGeneration: true`(`src/client/adapter.ts:186`) because Convex's `_id` is platform-generated and immutable, so BA's id pipeline has to stay off on this adapter. The downstream side effect is that the `customIdGenerator` default-value branch in `@better-auth/core/db/adapter/get-id-field.mjs` (lines 13–22) is also skipped: when `disableIdGeneration` is true, `shouldGenerateId` is false, the `defaultValue` block that would route through `options.advanced.generateId` is never attached and the hook doesn't fire.

The current workaround is to keep a parallel app-side table **per BA model** with a `publicId` column and a `by_publicId` index, plus a resolver at every REST/CLI input boundary.

It works but duplicates a contract BA core already defines, and it has to be repeated per model the application wants to brand.

### Proposal

An opt-in, per-model column. When enabled, the adapter generates the BA component table with an extra `publicId: v.string()` field and a `by_publicId` index. On `create`, the adapter calls `options.advanced.generateId({ model })` and writes the result to `publicId`. Convex `_id` remains platform owned; the BA `id` ↔ Convex `_id` mapping is unchanged.

```ts
export const createAuth = createClient({
components: {
organization: { publicId: true },
member: { publicId: true },
invitation: { publicId: true },
subscription: { publicId: true },
},
advanced: {
generateId: ({ model }) => {
const prefix = {
organization: "ws",
member: "mbr",
invitation: "inv",
subscription: "sub",
}[model];
return prefix ? `${prefix}_${nanoid(14)}` : undefined;
},
},
});
```

The `publicId` is a secondary, externally-facing identifier. It is never used as a foreign key — those continue to reference `_id` / BA `id`. The shape mirrors the pattern apps already use on their own tables, applied to the BA-owned ones.

### Scope

This proposal does not change:

- Convex `_id` — still auto-generated, immutable, the platform primary key.
- The BA `id` ↔ Convex `_id` mapping (`mapKeysTransformInput/Output`).
- `forceAllowId` / `customIdGenerator` plumbing in BA core.
- Internal foreign keys such as `member.organizationId` and `subscription.referenceId`,...those continue to reference BA `id` /Convex `_id`.

### Current workaround

Parallel `workspaceMetadata` table keyed on `organization._id`, with `publicId` + `by_publicId` index, a resolver at the REST entry, a brand-typed projection helper, and an ESLint allowlist to enforce the boundary. Roughly 150 lines of glue that would no longer be necessary if the adapter populated `publicId` directly.

Happy to open a PR if the direction is welcome but won't if it's a non-starter or I am missing something critical. Thanks for reading. 🙏

Contributor guide

Open the contributing guide

Research direction

Start by reading src/client/adapter.ts around line 186, then inspect the Better Auth ID-generation path at @better-auth/core/db/adapter/get-id-field.mjs lines 13–22 and the adapter’s component-table creation and mapping paths. Confirm how an opt-in per-model publicId field and by_publicId index would interact with creation and existing foreign-key mappings. Done means the proposal is implemented without changing Convex _id, BA id mappings, or internal foreign keys, with coverage for enabled and disabled models.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
authentication, databases
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.