#How do you run logic every time a new user signs up?

8 messages · Page 1 of 1 (latest)

plush prairie
#

I am trying to run some logic to insert default data about a user when they sign up. I am using convex-auth
import GitHub from "@auth/core/providers/github";
import Resend from "@auth/core/providers/resend";
import { convexAuth } from "@convex-dev/auth/server";
import { adventurerPaymentScheme } from "./utils/monetization";
import { email } from "zod";

export const { auth, signIn, signOut, store, isAuthenticated } = convexAuth({
providers: [
GitHub({
profile(githubProfile, _tokens) {
console.log(githubProfile);
return {
id: githubProfile.id.toString(),
name: githubProfile.name ?? githubProfile.login,
email: githubProfile.email,
image: githubProfile.avatar_url,
};
},
}),
Resend,
],
});

however, the problem with usConvexAuth({}) is that these callbacks run on EVERY sign in, not just the sign up. how do I run on sign up?

meager havenBOT
#

Hi @plush prairie. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave:

#

To run logic specifically when a new user signs up using convex-auth, you should use the createOrUpdateUser callback. However, as you noticed, both createOrUpdateUser and afterUserCreatedOrUpdated run on every sign-in and sign-up event, not just on initial sign-up.

The recommended approach is to check within your callback whether the user is new or existing. The createOrUpdateUser callback receives an args object that includes existingUserId if the user already exists. You can use this to distinguish between sign-up (new user) and sign-in (existing user):

import GitHub from "@auth/core/providers/github";
import Resend from "@auth/core/providers/resend";
import { convexAuth } from "@convex-dev/auth/server";
import { MutationCtx } from "./_generated/server";

export const { auth, signIn, signOut, store, isAuthenticated } = convexAuth({
  providers: [
    GitHub({
      profile(githubProfile, _tokens) {
        return {
          id: githubProfile.id.toString(),
          name: githubProfile.name ?? githubProfile.login,
          email: githubProfile.email,
          image: githubProfile.avatar_url,
        };
      },
    }),
    Resend,
  ],
  callbacks: {
    async createOrUpdateUser(ctx: MutationCtx, args) {
      if (args.existingUserId) {
        // Existing user: sign-in, not sign-up
        return args.existingUserId;
      }
      // New user: sign-up logic here
      const userId = await ctx.db.insert("users", {
        email: args.profile.email,
        name: args.profile.name,
        // ...other fields
      });
      // Insert default data for the new user
      await ctx.db.insert("defaultData", {
        userId,
        // ...default values
      });
      return userId;
    },
  },
});
#

This way, your logic for inserting default data will only run when a new user is created (i.e., on sign-up), not on every sign-in. This pattern is explicitly supported and recommended in the Convex Auth documentation for controlling user creation and account linking behavior see: Advanced: Details – Controlling user creation and account linking behavior.

If you use afterUserCreatedOrUpdated, you will not be able to distinguish between sign-up and sign-in, as it runs on both events see: Writing additional data during authentication.

Summary:
Use createOrUpdateUser, check for args.existingUserId, and only run your sign-up logic when it is undefined (i.e., on new user creation).

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:

#

@plush prairie Kapa.ai is still learning and improving, please let me know how I did by reacting below.

plush prairie
#

@meager haven what is "defaultData" issn't that a table argument?

meager havenBOT
#

Let me check the knowledge base for you again. One moment! :books:

#

You're correct—"defaultData" in the example is the name of a table in the Convex database. In Convex, you can insert data into any table you have defined in your schema (or, if you are not using a schema, any table you choose to create by inserting into it) see: Database > Tables.

For example, if you want to store default data for each new user, you would typically define a table (e.g., "defaultData" or any name you choose) in your schema:

import { defineSchema, defineTable } from "convex/server";
import { v } from "convex/values";

export default defineSchema({
  users: defineTable({
    email: v.string(),
    name: v.string(),
    // ...other fields
  }),
  defaultData: defineTable({
    userId: v.id("users"),
    // ...other default fields
  }),
});

Then, in your mutation or callback, you can insert into this table as shown in the previous example. The table name is just a string argument to the insert function, and you can name it whatever fits your application's needs see: Writing Data.

If you haven't defined a "defaultData" table, you should create it in your schema or choose another table name that matches your use case. If you are not using a schema, Convex will create the table the first time you insert into it see: Database > Tables.

If you need more details on how to define tables and schemas, see the schemas documentation.

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: