#create a user

9 messages · Page 1 of 1 (latest)

pliant finch
#

I am using convex-auth I want to create a new user inside of a mutation with a given phone number and get the created user's id. How to do that ?

distant terraceBOT
#

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

#

You can’t directly call signIn/signUp from a mutation to create users with Convex Auth, but you can programmatically create a user + auth account from server code using the createAccount helper from @convex-dev/auth/server. [createAccount; Discord: Sign up users from backend]

The pattern (shown in the Discord thread) is:

// convex/users.ts
import { createAccount } from "@convex-dev/auth/server";
import { mutation } from "./_generated/server";
import { v } from "convex/values";

export const createUserWithPhone = mutation({
  args: {
    phone: v.string(),
    // optionally any other profile fields your "users" schema allows
    // e.g. name: v.optional(v.string()),
  },
  handler: async (ctx, args) => {
    const providerId = "phone"; // or whatever ConvexCredentials / provider id you use

    // `account.id` is the unique external id — for phone flows this is usually the phone number
    const account = {
      id: args.phone,
      // If you’re storing a secret (e.g. OTP, password), put it here as `secret`
      // secret: "...",
    };

    // This must match your "users" table schema
    const profile = {
      phone: args.phone,
      // name: args.name ?? null,
    };

    const userId = await createAccount(ctx, {
      provider: providerId,
      account,
      profile,
      // Only set these to true if the phone is verified before sign‑in is allowed
      shouldLinkViaEmail: false,
      shouldLinkViaPhone: false,
    });

    // `userId` is the Id<"users"> of the created (or linked) user
    return userId;
  },
});

createAccount:

#
  • Inserts into "users" with profile, and
  • Inserts into authAccounts with the given provider and account.id,
  • Returns the created user’s ID. [createAccount]

If you also want automatic linking rules (e.g. by email/phone) or more control, you can alternatively implement a callbacks.createOrUpdateUser in your convex/auth.ts, but that callback is invoked as part of the normal sign‑in / verification flow, not arbitrarily from any mutation. [createOrUpdateUser]

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

#

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

pliant finch
#

@distant terrace import { createAccount } from "@convex-dev/auth/server"; this function requires an action context

distant terraceBOT
#

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

#

You’re right: createAccount currently requires an ActionCtx, not a MutationCtx. [createAccount]

From the docs:

ctx | GenericActionCtx<DataModel>

So you cannot call createAccount directly inside a mutation handler.

To create a user given a phone number and get its ID, you need to:

  1. Define an internal action that calls createAccount.
  2. Call that action from your mutation using the scheduler (since mutations can’t directly call actions).

1. Internal action that creates the user

// convex/authInternal.ts
import { internalAction } from "./_generated/server";
import { v } from "convex/values";
import { createAccount } from "@convex-dev/auth/server";

export const createUserWithPhoneInternal = internalAction({
  args: {
    phone: v.string(),
  },
  handler: async (ctx, { phone }) => {
    const account = {
      id: phone, // unique external ID for this provider, usually the phone number
      // secret: "...", // optional, if you store a secret
    };

    const profile = {
      phone,
      // add any other fields required by your `users` schema
    };

    const userId = await createAccount(ctx, {
      provider: "phone", // your provider id
      account,
      profile,
      shouldLinkViaPhone: false, // or true if you know it's verified
    });

    return userId;
  },
});

createAccount returns the user ID. [createAccount]

2. Mutation that schedules this action

You can then expose a mutation that schedules this internal action:

// convex/users.ts
import { mutation } from "./_generated/server";
import { internal } from "./_generated/api";
import { v } from "convex/values";
#

export const createUserWithPhone = mutation({
args: { phone: v.string() },
handler: async (ctx, { phone }) => {
// Run immediately (delay 0). Returns a job id; the action result itself
// is not returned to this mutation.
await ctx.scheduler.runAfter(
0,
internal.authInternal.createUserWithPhoneInternal,
{ phone }
);

// If you need the userId synchronously in the client, call the action
// directly from the client instead of via a mutation.
return null;

},
});


Mutations can only trigger actions via `ctx.scheduler.runAfter`, they cannot call them directly. [[Mutations & scheduler](<https://docs.convex.dev/functions/mutation-functions#the-mutation-constructor>); [Calling actions from clients](<https://docs.convex.dev/functions/actions#calling-actions-from-clients>)]

If you truly need the `userId` **immediately** on the client, call the **action** directly from the client (via `useAction` / HTTP) and return `userId` from that action.
----------------------
Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:
----------------------