> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/get-convex/convex-backend/llms.txt
> Use this file to discover all available pages before exploring further.

# Functions

> Learn about Convex's three function types - queries for reading data, mutations for writing data, and actions for external operations

Convex functions are the building blocks of your backend. There are three types of functions, each designed for specific use cases:

* **Queries** - Read data from the database (reactive, transactional, fast)
* **Mutations** - Write data to the database (transactional, atomic)
* **Actions** - Interact with external services (non-transactional, can call APIs)

## Queries

Queries are read-only functions that fetch data from your database. They're reactive - when data changes, queries automatically re-run and update subscribed clients.

### Basic query

```typescript theme={null}
import { query } from "./_generated/server";
import { v } from "convex/values";

export const list = query({
  args: {},
  handler: async (ctx) => {
    return await ctx.db.query("messages").collect();
  },
});
```

### Query context

Queries receive a context object with:

* `ctx.db` - Read-only database access (`GenericDatabaseReader`)
* `ctx.auth` - Current user authentication information
* `ctx.storage` - Read-only access to stored files
* `ctx.runQuery` - Call another query within the same transaction

```typescript theme={null}
import { query } from "./_generated/server";
import { v } from "convex/values";

export const getMessage = query({
  args: { messageId: v.id("messages") },
  handler: async (ctx, args) => {
    // Get authenticated user
    const identity = await ctx.auth.getUserIdentity();
    if (!identity) throw new Error("Not authenticated");
    
    // Read from database
    const message = await ctx.db.get(args.messageId);
    if (!message) return null;
    
    // Get file URL from storage
    if (message.imageId) {
      const imageUrl = await ctx.storage.getUrl(message.imageId);
      return { ...message, imageUrl };
    }
    
    return message;
  },
});
```

### Query characteristics

* **Read-only** - Cannot modify the database
* **Reactive** - Automatically re-run when data changes
* **Transactional** - See a consistent snapshot of the database
* **Fast** - Typically run in less than 10ms
* **Cached** - Results can be cached by the client

<Note>
  Queries see a consistent snapshot of the database at a single point in time. All reads within a query are isolated from concurrent writes.
</Note>

## Mutations

Mutations modify data in your database. All writes within a mutation are atomic - either all succeed or all fail.

### Basic mutation

```typescript theme={null}
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) => {
    const messageId = await ctx.db.insert("messages", {
      body: args.body,
      author: args.author,
    });
    return messageId;
  },
});
```

### Mutation context

Mutations receive a context object with:

* `ctx.db` - Read-write database access (`GenericDatabaseWriter`)
* `ctx.auth` - Current user authentication information
* `ctx.storage` - Generate upload URLs and delete files
* `ctx.scheduler` - Schedule functions to run later
* `ctx.runQuery` - Call a query within the same transaction
* `ctx.runMutation` - Call another mutation in a sub-transaction

```typescript theme={null}
import { mutation } from "./_generated/server";
import { internal } from "./_generated/api";
import { v } from "convex/values";

export const createTask = mutation({
  args: { text: v.string() },
  handler: async (ctx, args) => {
    // Check authentication
    const identity = await ctx.auth.getUserIdentity();
    if (!identity) throw new Error("Not authenticated");
    
    // Insert document
    const taskId = await ctx.db.insert("tasks", {
      text: args.text,
      completed: false,
      userId: identity.subject,
    });
    
    // Schedule a function to run later
    await ctx.scheduler.runAfter(
      24 * 60 * 60 * 1000, // 24 hours
      internal.tasks.sendReminder,
      { taskId }
    );
    
    return taskId;
  },
});
```

### Mutation characteristics

* **Read-write** - Can read and modify the database
* **Atomic** - All writes succeed or all fail (no partial states)
* **Isolated** - Concurrent mutations don't interfere with each other
* **Reactive triggers** - Automatically updates subscribed queries
* **Optimistically retried** - Automatically retried on conflicts

<Warning>
  Mutations may be retried automatically if there are concurrent conflicts. Avoid non-idempotent side effects (like sending emails) in mutations. Use actions for external side effects instead.
</Warning>

### Database write operations

Mutations can use four write operations:

<Steps>
  <Step title="Insert">
    Add new documents to a table:

    ```typescript theme={null}
    const userId = await ctx.db.insert("users", {
      name: "Alice",
      email: "alice@example.com",
    });
    ```
  </Step>

  <Step title="Patch">
    Shallow merge updates (only specified fields change):

    ```typescript theme={null}
    await ctx.db.patch(userId, {
      name: "Alice Smith", // Update name
      // email remains unchanged
    });
    ```
  </Step>

  <Step title="Replace">
    Completely replace a document:

    ```typescript theme={null}
    await ctx.db.replace(userId, {
      name: "Bob",
      email: "bob@example.com",
      // All other fields (except system fields) are removed
    });
    ```
  </Step>

  <Step title="Delete">
    Remove a document:

    ```typescript theme={null}
    await ctx.db.delete(userId);
    ```
  </Step>
</Steps>

## Actions

Actions are functions that can interact with external services and use Node.js APIs. Unlike queries and mutations, they don't have direct database access.

### Basic action

```typescript theme={null}
import { action } from "./_generated/server";
import { internal } from "./_generated/api";
import { v } from "convex/values";

export const processPayment = action({
  args: {
    orderId: v.id("orders"),
    amount: v.number(),
  },
  handler: async (ctx, args) => {
    // Read data via runQuery
    const order = await ctx.runQuery(internal.orders.get, {
      orderId: args.orderId,
    });
    
    // Call external API
    const response = await fetch("https://api.stripe.com/v1/charges", {
      method: "POST",
      headers: {
        Authorization: `Bearer ${process.env.STRIPE_SECRET_KEY}`,
      },
      body: JSON.stringify({
        amount: args.amount,
        currency: "usd",
        source: order.paymentToken,
      }),
    });
    
    const result = await response.json();
    
    // Write results via runMutation
    await ctx.runMutation(internal.orders.markPaid, {
      orderId: args.orderId,
      chargeId: result.id,
    });
  },
});
```

### Action context

Actions receive a context object with:

* `ctx.runQuery` - Run a query (separate read transaction)
* `ctx.runMutation` - Run a mutation (separate write transaction)
* `ctx.runAction` - Call another action
* `ctx.scheduler` - Schedule functions to run later
* `ctx.auth` - Current user authentication information
* `ctx.storage` - Generate upload URLs and get file URLs
* `ctx.vectorSearch` - Perform vector similarity searches

<Note>
  `ctx.db` is **not available** in actions. Use `ctx.runQuery` and `ctx.runMutation` to interact with the database.
</Note>

### Action characteristics

* **Non-transactional** - Can make multiple separate database calls
* **External access** - Can call third-party APIs and use Node.js libraries
* **No reactivity** - Don't automatically re-run when data changes
* **Longer timeout** - Can run for several minutes (vs milliseconds for queries/mutations)
* **Environment variables** - Can access `process.env`

### When to use actions

<CodeGroup>
  ```typescript Calling external APIs theme={null}
  import { action } from "./_generated/server";
  import { OpenAI } from "openai";

  export const generateText = action({
    args: { prompt: v.string() },
    handler: async (ctx, args) => {
      const openai = new OpenAI({
        apiKey: process.env.OPENAI_API_KEY,
      });
      
      const response = await openai.chat.completions.create({
        model: "gpt-4",
        messages: [{ role: "user", content: args.prompt }],
      });
      
      return response.choices[0].message.content;
    },
  });
  ```

  ```typescript Sending emails theme={null}
  import { action } from "./_generated/server";
  import { Resend } from "resend";

  export const sendWelcomeEmail = action({
    args: { email: v.string(), name: v.string() },
    handler: async (ctx, args) => {
      const resend = new Resend(process.env.RESEND_API_KEY);
      
      await resend.emails.send({
        from: "hello@example.com",
        to: args.email,
        subject: "Welcome!",
        html: `<p>Welcome, ${args.name}!</p>`,
      });
    },
  });
  ```

  ```typescript Complex computation theme={null}
  import { action } from "./_generated/server";
  import { internal } from "./_generated/api";

  export const processLargeDataset = action({
    args: { datasetId: v.id("datasets") },
    handler: async (ctx, args) => {
      // Fetch data
      const dataset = await ctx.runQuery(
        internal.datasets.get,
        { datasetId: args.datasetId }
      );
      
      // Perform expensive computation
      const results = await heavyProcessing(dataset.data);
      
      // Store results
      await ctx.runMutation(
        internal.datasets.storeResults,
        { datasetId: args.datasetId, results }
      );
    },
  });
  ```
</CodeGroup>

## Argument validation

All function types support argument validation using the `args` field:

```typescript theme={null}
import { mutation } from "./_generated/server";
import { v } from "convex/values";

export const createMessage = mutation({
  args: {
    body: v.string(),
    channelId: v.id("channels"),
    metadata: v.optional(v.object({
      priority: v.union(v.literal("high"), v.literal("normal")),
      tags: v.array(v.string()),
    })),
  },
  handler: async (ctx, args) => {
    // args is fully typed based on the validator
    // TypeScript knows: args.body is string
    //                   args.channelId is Id<"channels">
    //                   args.metadata is optional
  },
});
```

<Note>
  For security, **always add argument validation to public functions**. This prevents users from passing unexpected or malicious input.
</Note>

## Return value validation

You can optionally validate return values:

```typescript theme={null}
import { query } from "./_generated/server";
import { v } from "convex/values";

export const getUser = query({
  args: { userId: v.id("users") },
  returns: v.object({
    _id: v.id("users"),
    _creationTime: v.number(),
    name: v.string(),
    email: v.string(),
  }),
  handler: async (ctx, args) => {
    const user = await ctx.db.get(args.userId);
    if (!user) throw new Error("User not found");
    return user;
  },
});
```

## Public vs internal functions

Functions can be public (callable from clients) or internal (only callable from other functions):

<CodeGroup>
  ```typescript Public function theme={null}
  import { query } from "./_generated/server";

  // Exported from _generated/server - public by default
  export const list = query({
    args: {},
    handler: async (ctx) => {
      return await ctx.db.query("messages").collect();
    },
  });
  ```

  ```typescript Internal function theme={null}
  import { internalMutation } from "./_generated/server";

  // Can only be called from other Convex functions
  export const deleteAll = internalMutation({
    args: {},
    handler: async (ctx) => {
      const messages = await ctx.db.query("messages").collect();
      for (const message of messages) {
        await ctx.db.delete(message._id);
      }
    },
  });
  ```
</CodeGroup>

Use internal functions for:

* Administrative operations
* Functions called by scheduled jobs
* Helper functions shared between other functions
* Operations that should only run server-side

## Calling functions

Functions call each other using generated references:

```typescript theme={null}
import { mutation } from "./_generated/server";
import { internal } from "./_generated/api";
import { v } from "convex/values";

export const createOrder = mutation({
  args: { items: v.array(v.id("items")) },
  handler: async (ctx, args) => {
    const orderId = await ctx.db.insert("orders", {
      items: args.items,
      status: "pending",
    });
    
    // Schedule an action to process payment
    await ctx.scheduler.runAfter(
      0,
      internal.payments.processOrder,
      { orderId }
    );
    
    return orderId;
  },
});
```

## Function comparison

| Feature           | Query               | Mutation                  | Action             |
| ----------------- | ------------------- | ------------------------- | ------------------ |
| Database read     | Yes (transactional) | Yes (transactional)       | Via `runQuery`     |
| Database write    | No                  | Yes (atomic)              | Via `runMutation`  |
| External APIs     | No                  | No                        | Yes                |
| Reactive          | Yes                 | No (but triggers queries) | No                 |
| Transaction       | Single snapshot     | Atomic writes             | Multiple separate  |
| Typical duration  | Less than 10ms      | Less than 50ms            | Seconds to minutes |
| Retry on conflict | N/A                 | Automatic                 | Manual             |
| Node.js APIs      | No                  | No                        | Yes                |

## Next steps

* Learn about [Schemas](/concepts/schemas) to validate your data structure
* Understand the [Reactive database](/concepts/reactive-database) model
* Explore [Real-time sync](/concepts/real-time-sync) for client integration
