#Infer the type of the arguments to a mutation

1 messages · Page 1 of 1 (latest)

strong prawnBOT
#

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

#

To infer the type of arguments to a Convex mutation in TypeScript, you have several options:

Using Validators with Automatic Type Inference

When you define a mutation with argument validation, TypeScript will automatically infer the argument types:

import { mutation } from "./_generated/server";
import { v } from "convex/values";

export const send = mutation({
  args: {
    body: v.string(),
    author: v.string(),
  },
  handler: async (ctx, args) => {
    // TypeScript knows that args is { body: string, author: string }
    const { body, author } = args;
    await ctx.db.insert("messages", { body, author });
  },
});

This approach provides both runtime validation and compile-time type checking Functions > Argument and Return Value Validation.

Using the Infer Type Helper

You can extract TypeScript types from validators using the Infer type helper:

import { mutation } from "./_generated/server";
import { Infer, v } from "convex/values";

const messageArgs = v.object({
  body: v.string(),
  author: v.string(),
});

// This resolves to { body: string, author: string }
export type MessageArgs = Infer<typeof messageArgs>;

export const send = mutation({
  args: messageArgs,
  handler: async (ctx, args) => {
    // args is typed as MessageArgs
    await ctx.db.insert("messages", args);
  },
});

This is useful when you want to reuse the same validator in multiple places Functions > Extracting TypeScript Types.

Using FunctionArgs Type

If you need to reference the argument type of an existing mutation elsewhere in your code, you can use the FunctionArgs type:

import { api } from "./_generated/api";
import { FunctionArgs } from "convex/server";
#

// This will be the type of arguments for the send mutation
type SendArgs = FunctionArgs<typeof api.messages.send>;


### Reusing Schema Validators

You can also define validators in your schema and reuse them for argument validation:

```typescript
// in convex/schema.ts
export const messageFields = {
  body: v.string(),
  author: v.string(),
};

// in convex/messages.ts
import { mutation } from "./_generated/server";
import { messageFields } from "./schema";

export const send = mutation({
  args: messageFields,
  handler: async (ctx, args) => {
    // args is typed according to messageFields
    return await ctx.db.insert("messages", args);
  },
});

This approach helps maintain consistency between your schema and function arguments Stack > Argument Validation without Repetition.

By using these techniques, you can ensure type safety throughout your Convex application while avoiding duplication of type definitions.

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

#

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

quasi iron
#

so i need to define the args outside of the mutation? @strong prawn

strong prawnBOT
#

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

#

No, you don't need to define the args outside of the mutation. You have two options:

Option 1: Define args directly in the mutation (inline)

You can define the args directly within the mutation definition, which is the most common approach:

import { mutation } from "./_generated/server";
import { v } from "convex/values";

export const send = mutation({
  args: {
    body: v.string(),
    author: v.string(),
  },
  handler: async (ctx, args) => {
    // TypeScript automatically infers args as { body: string, author: string }
    const { body, author } = args;
    await ctx.db.insert("messages", { body, author });
  },
});

With this approach, TypeScript will automatically infer the types of your arguments Functions > Argument and Return Value Validation.

Option 2: Define args separately (for reusability)

Defining args outside the mutation is optional but useful when you want to:

  • Reuse the same validators across multiple functions
  • Extract the type using Infer
  • Share validators between your schema and functions
import { mutation } from "./_generated/server";
import { Infer, v } from "convex/values";

// Optional: define args separately for reuse
const messageArgs = {
  body: v.string(),
  author: v.string(),
};

// Optional: extract the type if needed elsewhere
export type MessageArgs = Infer<typeof messageArgs>;

export const send = mutation({
  args: messageArgs,  // Use the predefined args
  handler: async (ctx, args) => {
    await ctx.db.insert("messages", args);
  },
});
#

Both approaches provide the same type safety and runtime validation. The choice depends on your specific needs for code organization and reusability.

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

#

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

quasi iron
#

in the first example: how can i get the inferred type of the send arguments? @strong prawn

strong prawnBOT
#

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

#

To get the inferred type of the send mutation arguments from the first example, you can use the FunctionArgs type from Convex. Here's how to do it:

import { api } from "./_generated/api";
import { FunctionArgs } from "convex/server";

// This extracts the argument type from the send mutation
type SendArgs = FunctionArgs<typeof api.messages.send>;

// Now you can use SendArgs elsewhere in your code
function processSendArgs(args: SendArgs) {
  // ...
}

Alternatively, if you want to extract the type directly from the validator without referencing the API, you can use the Infer type helper:

import { Infer, v } from "convex/values";

// Define your validators (same as in your mutation)
const sendArgsValidator = {
  body: v.string(),
  author: v.string(),
};

// Extract the type
type SendArgs = Infer<typeof sendArgsValidator>;

The Infer type helper is particularly useful when you want to extract TypeScript types from your validators for use elsewhere in your codebase Stack > Types and Validators in TypeScript: A Convex Cookbook.

Both approaches give you the same result: a TypeScript type that matches the structure defined by your validators.

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

#

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

quasi iron
#

how can i get the actual validator object? @strong prawn

strong prawnBOT
#

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

#

To get the actual validator object from a Convex function, you have a few approaches depending on your specific use case:

1. Export the validator directly

If you define your validators separately, you can simply export them:

import { v } from "convex/values";

// Define and export the validator
export const sendArgsValidator = {
  body: v.string(),
  author: v.string(),
};

// Use it in your mutation
export const send = mutation({
  args: sendArgsValidator,
  handler: async (ctx, args) => {
    // ...
  },
});

2. Extract validators from schema

If you're using a schema-based approach, you can export field validators from your schema:

// in convex/schema.ts
import { v } from "convex/values";

export const recipeFields = {
  name: v.string(),
  course: v.union(v.literal("appetizer"), v.literal("main"), v.literal("dessert")),
  ingredients: v.array(v.string()),
  steps: v.array(v.string()),
};

// Then import in your function file
import { recipeFields } from "./schema";

This approach is recommended in Stack > Types and Validators in TypeScript: A Convex Cookbook to avoid repetition.

3. Use the Table helper from convex-helpers

For a more structured approach, you can use the Table helper from the convex-helpers package:

// convex/schema.ts
import { defineSchema } from "convex/server";
import { v } from "convex/values";
import { Table } from "convex-helpers/server"; // npm i convex-helpers

export const Recipes = Table("recipes", {
  name: v.string(),
  course: v.union(v.literal("appetizer"), v.literal("main"), v.literal("dessert")),
  ingredients: v.array(v.string()),
  steps: v.array(v.string()),
});

export default defineSchema({
  recipes: Recipes.table.index("by_course", ["course"]),
});
#

Then you can access validators in different ways:

// Access validators with or without system fields
Recipes.withoutSystemFields; // user-defined field validators
Recipes.withSystemFields;    // those validators plus _id and _creationTime
Recipes.doc;                 // v.object() validator for the table's docs

This approach is detailed in Stack > Argument Validation without Repetition.

These methods give you access to the actual validator objects that you can reuse across your codebase for consistent validation and type safety.

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

#

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

quasi iron
#

it is currently a mutation @strong prawn

strong prawnBOT
#

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

#

I understand you're working with a Convex mutation. Based on our previous conversation, you're looking to extract the validator object from an existing mutation.

If your mutation is defined inline like this:

export const send = mutation({
  args: {
    body: v.string(),
    author: v.string(),
  },
  handler: async (ctx, args) => {
    // ...
  },
});

You have a few options to get the validator object:

1. Extract the args definition to a separate variable

The simplest approach is to refactor your code to define the args separately:

import { v } from "convex/values";

// Extract the validator object
export const sendArgsValidator = {
  body: v.string(),
  author: v.string(),
};

export const send = mutation({
  args: sendArgsValidator,  // Use the extracted validator
  handler: async (ctx, args) => {
    // ...
  },
});

2. Create a helper function to reuse validators

If you need to reuse validators across multiple functions, you can create helper functions:

import { v } from "convex/values";

// Define a helper function that returns the validator
export function myReadHelper(ctx, id) {
  // Use validators here
  // ...
}

export const send = mutation({
  args: {
    body: v.string(),
    author: v.string(),
  },
  handler: async (ctx, args) => {
    // ...
  },
});

This pattern is described in Type annotating server-side helpers.

Unfortunately, there's no direct way to access the validator object after it's been defined inline within a mutation. The best practice is to define your validators separately if you need to reuse them.

#

Is there a specific reason you need to extract the validator from an existing mutation rather than defining it separately?

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

#

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

quasi iron
#

is there a resolver similar to the zodresolver in @hookform for convex @strong prawn

strong prawnBOT
#

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

#

Yes, Convex has integration options with Zod for validation similar to the zodResolver in React Hook Form. The convex-helpers package provides utilities for using Zod with Convex functions.

There are a few approaches you can use:

1. Using withZod wrapper

You can use the withZod wrapper from convex-helpers to validate your function inputs:

export default mutation(
  withZod({
    args: {
      body: z.string(),
      author: z.string(),
    },
    handler: async ({ db }, { body, author }) => {
      const message = { body, author };
      return await db.insert("messages", message);
    }
  })
);

This approach is detailed in Stack > Zod Validation: Wrappers as "Middleware".

2. Using zCustomQuery and similar functions

For a more integrated approach, you can use the zCustomQuery, zCustomMutation, or zCustomAction functions:

import { z } from "zod";
import { NoOp } from "convex-helpers/server/customFunctions";
import { zCustomQuery } from "convex-helpers/server/zod";
import { query } from "./_generated/server";

// Make this once, to use anywhere you would have used `query`
const zQuery = zCustomQuery(query, NoOp);

export const greeting = zQuery({
  args: { name: z.string() },
  handler: async (_ctx, args) => {
    return `Hello ${args.name}`;
  },
});

This is explained in Stack > Zod with TypeScript for Server-side Validation.

3. Using zid for Document IDs

For validating Convex Document IDs, you can use the zid helper:

import { zCustomQuery, zid } from "convex-helpers/server/zod";

const zQuery = zCustomQuery(query, NoOp)
#

export const getUser = zQuery({
args: {userId: zid("users")},
handler: async (ctx, args) => {
const user = await ctx.db.get(args.userId);
return user && { id: user._id, name: user.name };
},
});


This is shown in [Stack > Zod with TypeScript for Server-side Validation](<https://stack.convex.dev/typescript-zod-function-validation#vidtablename--zidtablename>).

You can install the necessary package with:

npm i convex-helpers@latest


These approaches provide similar functionality to zodResolver in React Hook Form, but for Convex server functions.
----------------------
Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:
----------------------