Convex + Better Auth

App user id in component table

Configure Better Auth to continue tracking app user id in component table

This guide shows one of two recommended strategies for migrating away from maintaining a user.userId field in the Better Auth user table. Read the overview for more information.

The migration guide covers basic required changes, but your application may have additional or altered requirements depending on how the Better Auth component is implemented. Be sure to review any auth related code for potential impacts. Avoid type assertions and checking for type errors is encouraged.

This guide is for configuring Better Auth to continue the existing behavior of tracking the app user id in the component table.

Run convex dev

Keep the Convex dev server running while following the migration steps.

npx convex dev

Migrate to Local Install

If you haven't already, follow the guide to migrate to Local Install.

Add userId field to component schema

Use Better Auth's additionalFields option to add the userId field to the component schema.

convex/auth.ts
export const createAuth = (
  ctx: GenericCtx<DataModel>,
  { optionsOnly } = { optionsOnly: false }
) => {
  return betterAuth({
    user: {
      additionalFields: {
        userId: {
          type: "string",
          required: false,
        },
      },
    },
  });
};

Regenerate schema

Regenerate the Better Auth schema to include the new userId field.

cd convex/betterAuth && npx @better-auth/cli generate -y

Add setUserId mutation

The component has a deprecated setUserId method, you'll want to create your own to replace it.

convex/betterAuth/auth.ts
export const setUserId = mutation({
  args: {
    authId: v.id("user"),
    userId: v.string(),
  },
  handler: async (ctx, args) => {
    await ctx.db.patch(args.authId, {
      userId: args.userId,
    });
  },
});

Update user onCreate trigger

Update the user onCreate trigger to set the userId field on the Better Auth user.

convex/auth.ts
export const authComponent = createClient<DataModel, typeof betterAuthSchema>(
  components.betterAuth,
  {
    authFunctions,
    local: {
      schema: betterAuthSchema,
    },
    triggers: {
      user: {
        onCreate: async (ctx, authUser) => {
          const userId = await ctx.db.insert("users", {
            email: authUser.email,
          });
          await authComponent.setUserId(ctx, authUser._id, userId); 
          await ctx.runMutation(components.betterAuth.auth.setUserId, {
            authId: authUser._id,
            userId,
          });
        },
      },
    },
  }
);

All set 🎉

Previous behavior is now configured manually, no changes in behavior should result.