Action context inside auth callback

I'm trying to run an action inside of the afterUserCreatedOrUpdated callback, but I'm getting the following error:
Uncaught TypeError: ctx.runAction is not a function
A lot of the documentation talks about runAction, but I could only find examples of using it with useAction on the client. I really want to add details to the users table with 3rd party information after create. What am I missing?
8 Replies
Convex Bot
Convex Bot3w ago
Thanks for posting in <#1088161997662724167>. Reminder: If you have a Convex Pro account, use the Convex Dashboard to file support tickets. - Provide context: What are you trying to achieve, what is the end-user interaction, what are you seeing? (full error message, command output, etc.) - Use search.convex.dev to search Docs, Stack, and Discord all at once. - Additionally, you can post your questions in the Convex Community's <#1228095053885476985> channel to receive a response from AI. - Avoid tagging staff unless specifically instructed. Thank you!
sshader
sshader3w ago
These callbacks run in a mutation, so ctx is a MutationCtx (https://labs.convex.dev/auth/api_reference/server#callbacksafterusercreatedorupdated) So it can't directly call actions, since mutations can't have side effects (so we can do things like safely retry them), but it can schedule actions (ctx.scheduler.runAfter(0, internal.your.func)) So probably what you want to do is schedule the action to populate your users table with the third party information
server - Convex Auth
Authentication library for your Convex backend
Skyblue (Matt)
Skyblue (Matt)OP3w ago
Oh that's kind of a bummer, I was expecting it to behave more like a trigger Oh...maybe i can just move that to a db trigger on the users table i guess?
sshader
sshader3w ago
It's a little like a trigger -- it'll happen in the same transaction as the user creation / user update as part of the auth flow. But because it's running in a mutation, you can't directly call actions. A db trigger on the users table also sounds like it would do the job, but you'd still be kicking off an action with the scheduler instead of directly using ctx.runAction (because the trigger would still be running in a mutation)
ballingt
ballingt3w ago
@Skyblue (Matt) What are you trying to do in your action? If it's a fetch() request, then you need an action to do that and we can't do that transactionally (we're not going to hold the transaction open until the fetch() returns). If you just want it to run right after the transaction commits, then that's what scheduling does; runAfter(0, internal.stuff.doSomething, {}) will run right after
I really want to add details to the users table with 3rd party information after create.
If you don't already have this information, you won't be able to add it to the users table in the same transaction. You'll either need to fetch it earlier in the flow or make it optional in the users table and populated it ~100ms+latency later.
Skyblue (Matt)
Skyblue (Matt)OP3w ago
Yah thats what I was thinking. I'm not overly concerned about trying to optimize for the perf of it because its a one time event, but i do want to be able to code the invocation of the action in a way that reads like a trigger. I was tracking with the need to use an action, but I couldn't figure out how to run that action in that auth callback, and had an equal amount of luck getting trying to kickoff a mutation in the callback and calling an action from there. I guess, as I'm reading both of your responses I'm tracking that it isn't the right context to call actions from. I guess I was just hoping for some way to group the functionality together in a way that I would typically do from a backend function. 1. listen for record inserts on the users table 2. invoke special trigger action that... 3. calls 3rd party to create customer and portal link 4. patches user record with those fields (optional in the schema of course) 5. the user query on the client updates (this is already happening beautifully of course) 6. I redirect user to the link to pick a subscription After the successful sign up, the app stays in a loading state waiting for the record to have those fields. Ideal situation for me is that 2-3 are action, 4 is internal mutation called by that action, and i can associate that action with the createUser event in a way that's highly readable and associated closely with the file I'm using for the action and internal mutation. I will take a look at the scheduler, I haven't yet given that a whirl. In my head I was thinking that wasn't for one time events, so I hadn't even considered it.
ballingt
ballingt3w ago
Cool, yeah it's ctx.scheduler.runAfter(0, internal.stuff.yourAction, {}) and yourAction, unlike traditional backend programming, has to be written in a separate function. What you're describing sounds good, but instead of listening for inserts on the users (by the logic that someday you might write to three different tables in there instead of a single users table) just do it right in that transaction. Or if you want what "trigger" means in some databases, sometimes it's not transactional, in which case you want to schedule this to happen after the transaction.
Skyblue (Matt)
Skyblue (Matt)OP3w ago
Appreciate the help from both of you

Did you find this page helpful?