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

# Actions API

> API reference for Astro Actions - type-safe server functions

Astro Actions provide type-safe server functions that can be called from client code or Astro components.

## Importing

```typescript theme={null}
import { defineAction } from 'astro:actions';
import { z } from 'astro:schema';
```

## defineAction

Define a server-side action that can be called from the client or server.

```typescript theme={null}
defineAction(config: ActionConfig): ActionClient
```

<ParamField path="config" type="object" required>
  Action configuration object.

  <ParamField path="config.input" type="ZodSchema">
    Zod schema to validate input data. If `accept` is `'form'`, defaults to `FormData` schema.

    ```typescript theme={null}
    import { defineAction } from 'astro:actions';
    import { z } from 'astro:schema';

    export const server = {
      getUser: defineAction({
        input: z.object({
          userId: z.string(),
        }),
        handler: async (input) => {
          return { name: 'John Doe', id: input.userId };
        },
      }),
    };
    ```
  </ParamField>

  <ParamField path="config.accept" type="'form' | 'json'">
    Type of input the action accepts. Use `'form'` for form submissions or `'json'` for JSON payloads.

    **Default:** `'json'`

    ```typescript theme={null}
    defineAction({
      accept: 'form',
      input: z.object({
        email: z.string().email(),
        message: z.string(),
      }),
      handler: async (input) => {
        // input is parsed from FormData
        return { success: true };
      },
    })
    ```
  </ParamField>

  <ParamField path="config.handler" type="(input, context) => Promise<Output>" required>
    The server function that executes the action.

    **Parameters:**

    <ParamField path="input" type="Input">
      Parsed and validated input data matching your schema.
    </ParamField>

    <ParamField path="context" type="ActionAPIContext">
      Server context with request information.

      <ParamField path="context.cookies" type="AstroCookies">
        Cookie utilities.
      </ParamField>

      <ParamField path="context.request" type="Request">
        The original request object.
      </ParamField>

      <ParamField path="context.url" type="URL">
        Request URL.
      </ParamField>

      <ParamField path="context.clientAddress" type="string">
        Client IP address.
      </ParamField>

      <ParamField path="context.locals" type="App.Locals">
        Shared locals object.
      </ParamField>
    </ParamField>

    ```typescript theme={null}
    handler: async ({ userId }, context) => {
      const user = await db.users.findOne({ id: userId });
      
      if (!user) {
        throw new Error('User not found');
      }

      return user;
    }
    ```
  </ParamField>
</ParamField>

### Example

```typescript theme={null}
// src/actions/index.ts
import { defineAction } from 'astro:actions';
import { z } from 'astro:schema';

export const server = {
  createPost: defineAction({
    accept: 'form',
    input: z.object({
      title: z.string(),
      content: z.string(),
      published: z.boolean().optional(),
    }),
    handler: async (input, context) => {
      const user = context.locals.user;
      
      if (!user) {
        throw new Error('Not authenticated');
      }

      const post = await db.posts.create({
        ...input,
        authorId: user.id,
      });

      return { id: post.id };
    },
  }),

  getPost: defineAction({
    input: z.object({
      postId: z.string(),
    }),
    handler: async ({ postId }) => {
      const post = await db.posts.findOne({ id: postId });
      
      if (!post) {
        throw new Error('Post not found');
      }

      return post;
    },
  }),
};
```

## Calling Actions

### From Client-Side JavaScript

```typescript theme={null}
import { actions } from 'astro:actions';

const result = await actions.getPost({ postId: '123' });

if (result.error) {
  console.error(result.error);
} else {
  console.log(result.data);
}
```

### From Forms

```astro theme={null}
---
import { actions } from 'astro:actions';
---

<form method="POST" action={actions.createPost}>
  <input type="text" name="title" required />
  <textarea name="content" required></textarea>
  <button type="submit">Create Post</button>
</form>
```

### From Astro Components

Use `Astro.callAction()` to call actions server-side:

```astro theme={null}
---
import { actions } from 'astro:actions';

const result = await Astro.callAction(actions.getPost, { 
  postId: Astro.params.id 
});

if (result.error) {
  return Astro.redirect('/404');
}

const post = result.data;
---

<h1>{post.title}</h1>
<div>{post.content}</div>
```

### Getting Form Results

Use `Astro.getActionResult()` to get results from form submissions:

```astro theme={null}
---
import { actions } from 'astro:actions';

const result = Astro.getActionResult(actions.createPost);
---

{result?.error && (
  <p class="error">{result.error.message}</p>
)}

{result?.data && (
  <p class="success">Post created with ID: {result.data.id}</p>
)}

<form method="POST" action={actions.createPost}>
  <input type="text" name="title" required />
  <textarea name="content" required></textarea>
  <button type="submit">Create Post</button>
</form>
```

## Error Handling

Actions return a `SafeResult` object with either `data` or `error`:

```typescript theme={null}
type SafeResult<TOutput> = 
  | { data: TOutput; error: undefined }
  | { data: undefined; error: ActionError };
```

### Throwing Errors

Throw errors in your handler to return error responses:

```typescript theme={null}
import { defineAction, ActionError } from 'astro:actions';
import { z } from 'astro:schema';

export const server = {
  deletePost: defineAction({
    input: z.object({ postId: z.string() }),
    handler: async ({ postId }, context) => {
      const user = context.locals.user;
      
      if (!user) {
        throw new ActionError({
          code: 'UNAUTHORIZED',
          message: 'You must be logged in to delete posts',
        });
      }

      const post = await db.posts.findOne({ id: postId });
      
      if (!post) {
        throw new ActionError({
          code: 'NOT_FOUND',
          message: 'Post not found',
        });
      }

      if (post.authorId !== user.id) {
        throw new ActionError({
          code: 'FORBIDDEN',
          message: 'You can only delete your own posts',
        });
      }

      await db.posts.delete({ id: postId });
      return { success: true };
    },
  }),
};
```

### Error Codes

<ParamField path="code" type="string">
  HTTP-like error codes:

  * `'BAD_REQUEST'` - Invalid input (400)
  * `'UNAUTHORIZED'` - Authentication required (401)
  * `'FORBIDDEN'` - Insufficient permissions (403)
  * `'NOT_FOUND'` - Resource not found (404)
  * `'TIMEOUT'` - Request timeout (408)
  * `'CONFLICT'` - Resource conflict (409)
  * `'PRECONDITION_FAILED'` - Precondition not met (412)
  * `'PAYLOAD_TOO_LARGE'` - Request too large (413)
  * `'UNSUPPORTED_MEDIA_TYPE'` - Invalid content type (415)
  * `'UNPROCESSABLE_CONTENT'` - Validation failed (422)
  * `'TOO_MANY_REQUESTS'` - Rate limit exceeded (429)
  * `'CLIENT_CLOSED_REQUEST'` - Client cancelled (499)
  * `'INTERNAL_SERVER_ERROR'` - Server error (500)
  * `'NOT_IMPLEMENTED'` - Not implemented (501)
  * `'BAD_GATEWAY'` - Bad gateway (502)
  * `'SERVICE_UNAVAILABLE'` - Service unavailable (503)
  * `'GATEWAY_TIMEOUT'` - Gateway timeout (504)
</ParamField>

## Input Validation

Input is automatically validated using the provided Zod schema:

```typescript theme={null}
export const server = {
  updateProfile: defineAction({
    accept: 'form',
    input: z.object({
      name: z.string().min(2).max(50),
      email: z.string().email(),
      age: z.number().int().min(18).optional(),
    }),
    handler: async (input) => {
      // input is fully typed and validated
      return { success: true };
    },
  }),
};
```

Validation errors are returned automatically:

```typescript theme={null}
const result = await actions.updateProfile({
  name: 'A', // Too short
  email: 'invalid', // Invalid email
});

if (result.error) {
  console.log(result.error.fields); // Validation errors by field
}
```

## orThrow

Call `.orThrow()` to throw on errors instead of returning a result object:

```typescript theme={null}
try {
  const post = await actions.getPost.orThrow({ postId: '123' });
  // post is TOutput, not SafeResult
  console.log(post.title);
} catch (error) {
  console.error(error);
}
```

This is useful for server-side code where you want exceptions instead of result objects.
