Migrate to 0.8
Migrate to @convex-dev/better-auth@0.8
To use Local Install, complete this migration guide first and then follow the Local Install guide.
Upgrade
Update the component
Update Better Auth and the component.
npm install @convex-dev/better-auth@latest better-auth@1.3.8 --save-exact
Update component instance
Replace the component instance with createClient.
import {
AuthFunctions,
BetterAuth,
PublicAuthFunctions,
createClient,
} from "@convex-dev/better-auth";
import { DataModel } from "./_generated/dataModel";
import { components } from "./_generated/api";
const authFunctions: AuthFunctions = internal.auth;
const publicAuthFunctions: PublicAuthFunctions = api.auth;
export const betterAuthComponent = new BetterAuth(
export const authComponent = createClient<DataModel>(
components.betterAuth, {
authFunctions,
publicAuthFunctions,
});
// These will be used in the next step
export const { onCreate, onUpdate, onDelete } = authComponent.triggersApi()
Convert hooks from createAuthFunctions
to triggers
The previously supported onCreateUser
, onUpdateUser
, and onDeleteUser
hooks from
betterAuthComponent.createAuthFunctions()
have been replaced by triggers. See the
Triggers guide for more information.
export const {
createUser,
deleteUser,
updateUser,
} = betterAuthComponent.createAuthFunctions<DataModel>({
onCreateUser: async (ctx, user) => {
return ctx.db.insert("users", {
email: user.email,
});
},
onDeleteUser: async (ctx, userId) => {
await ctx.db.delete(userId as Id<"users">);
},
});
const authFunctions: AuthFunctions = internal.auth;
export const authComponent = createClient<DataModel>(
components.betterAuth,
{
authFunctions,
triggers: {
user: {
onCreate: async (ctx, authUser) => {
// Any `onCreateUser` logic should be moved here
const userId = await ctx.db.insert('users', {
email: authUser.email,
})
// Instead of returning the user id, we set it to the component
// user table manually. This is no longer required behavior, but
// is necessary when migrating from previous versions to avoid
// a required database migration.
// This helper method exists solely to facilitate this migration.
await authComponent.setUserId(ctx, authUser._id, userId)
},
onUpdate: async (ctx, oldUser, newUser) => {
// Any `onUpdateUser` logic should be moved here
},
onDelete: async (ctx, authUser) => {
await ctx.db.delete(authUser.userId as Id<'users'>)
},
},
},
},
)
export const { onCreate, onUpdate, onDelete } = authComponent.triggersApi()
Move and update Better Auth config
The createAuth
function should be moved to convex/auth.ts
. This isn't
required, but better represents where the code actually runs, colocates it
with other related server side auth code, and avoids potentially writing
Convex code outside of the Convex directory for some function based config
properties.
The Convex database adapter is now provided through a method on the auth
component, and a GenericCtx
type from the component library is now used to
type the createAuth
ctx arg.
There is also a new optionsOnly
parameter to the createAuth
function. This
is used to disable logging when the function is called just to generate options.
This is not required, but helpful for reducing noise in logs.
Be sure to update any imports of createAuth
to the new path.
import type { GenericCtx } from './_generated/server';
const {
convexAdapter,
type GenericCtx,
createClient,
} from "@convex-dev/better-auth";
import { DataModel } from "./_generated/dataModel";
export const authComponent = createClient<DataModel>(
components.betterAuth
// ...
)
export const createAuth = (ctx: GenericCtx) => {
export const createAuth = (
ctx: GenericCtx<DataModel>,
{ optionsOnly } = { optionsOnly: false },
) => {
return betterAuth({
// ...
database: authComponent.adapter(ctx),
database: convexAdapter(ctx, authComponent),
// When createAuth is called just to generate options, we don't want to
// log anything
logger: {
disabled: optionsOnly,
},
})
}
Update ctx.auth.getUserIdentity()
usage
The subject
property of the id token, as well as the user identity object returned from
ctx.auth.getUserIdentity()
, was formerly the user id from the application user
table. It is now the user id from the Better Auth user table. Any direct usage
of the subject
property should be replaced with
authComponent.getAuthUser(ctx)
, which returns the entire Better Auth user
object (formerly referred to as user metadata).
export const listMessages = query({
args: {},
handler: async (ctx) => {
const userId = (await ctx.auth.getUserIdentity())?.subject;
const userId = (await authComponent.getAuthUser(ctx))?.userId;
return ctx.db
.query("messages")
.withIndex("by_userId", (q) => q.eq("userId", userId))
.collect();
},
});
Update authComponent.getAuthUser()
usage
authComponent.getAuthUser()
now throws an error if the user is not found. Use
authComponent.safeGetAuthUser()
to match the previous behavior.
export const getCurrentUser = query({
args: {},
handler: async (ctx) => {
const authComponent.getAuthUser(ctx);
return authComponent.safeGetAuthUser(ctx);
},
});
export const getCurrentUser = query({
args: {},
handler: async (ctx) => {
const userMetadata = await betterAuthComponent.getAuthUser(ctx);
const userMetadata = await authComponent.safeGetAuthUser(ctx);
if (!userMetadata) {
return null;
}
const user = await ctx.db.get(userMetadata.userId as Id<"users">);
return {
...user,
...userMetadata,
};
},
});
Framework specific changes
If your project uses TanStack Start, follow the last few steps of the TanStack Start guide and make sure your code aligns.
This migration should not have framework specific impacts for any other framework.
That's it!
Please report any issues or inaccuracies from this guide via GitHub issues or in the #better-auth channel on Discord.
Other breaking changes
Convex plugin options
removed
The named options
parameter to the Convex plugin that accepted Better Auth
options has been removed. This was necessary because the Convex plugin
previously customized the session, breaking type inference for configuration
that affected the session, so the Better Auth options had to be separated to a
new function and passed in to the Convex plugin.
None of this is necessary anymore, if you were doing this you can go back to
using a single createAuth
function.
const createOptions = (ctx: GenericCtx) =>
({
baseURL: siteUrl,
database: convexAdapter(ctx, betterAuthComponent),
plugins: [
// ...plugins
],
}) satisfies BetterAuthOptions;
export const createAuth = (ctx: GenericCtx) => {
const options = createOptions(ctx);
return betterAuth({
...options,
plugins: [...options.plugins, convex({ options })],
});
};
export const createAuth = (ctx: GenericCtx<DataModel>) => {
return betterAuth({
baseURL: siteUrl,
database: authComponent.adapter(ctx),
plugins: [
// ...plugins
convex(),
],
});
};