Jehlani Luciano Logo

Astro Actions

Guidelines for defining and using Astro Actions for server communication.

astro
          
            ## Astro Actions Guidelines

1.  Purpose: Actions provide a type-safe way to define and call server-side functions (backend logic) from client-side code or HTML forms, simplifying data validation, fetching, and error handling compared to manual API endpoints for specific tasks like form submissions or mutations.

2.  Definition (`src/actions/index.ts`):

    - Create `src/actions/index.ts`.
    - Export a `server` object containing your actions.
    - Define each action using `defineAction` imported from `astro:actions`.

    ```typescript
    // src/actions/index.ts
    import { defineAction, z } from "astro:actions"; // z also available from 'astro:schema'

    export const server = {
      // Example action
      likePost: defineAction({
        // Optional: Define input schema using Zod for automatic validation
        input: z.object({
          postId: z.string(),
        }),
        // Handler function runs on the server
        handler: async (input, context) => {
          // input is validated based on the schema
          // context provides APIContext (locals, cookies, request, etc.)
          console.log(`Liking post ${input.postId}`);
          // Check authorization using context.locals, context.cookies etc.
          if (!context.locals.user) {
            throw new ActionError({
              code: "UNAUTHORIZED",
              message: "Must be logged in.",
            });
          }
          try {
            // ... perform database update or other server logic ...
            return { success: true, postId: input.postId };
          } catch (e) {
            console.error(e);
            throw new ActionError({
              code: "INTERNAL_SERVER_ERROR",
              message: "Failed to like post.",
            });
          }
        },
      }),
      // Add other actions...
    };
    ```

3.  Calling Actions (Client-Side):

    - Import the `actions` object from `astro:actions`.
    - Call actions as async functions: `const result = await actions.actionName(input);`.
    - Works within `<script>` tags in `.astro` files or inside UI framework components.

    ```javascript
    import { actions } from "astro:actions";

    async function handleLike(postId) {
      const { data, error } = await actions.likePost({ postId });
      if (error) {
        console.error("Action failed:", error.message, "Code:", error.code);
        // Handle specific error codes like error.code === 'UNAUTHORIZED'
      } else {
        console.log("Action succeeded:", data);
      }
    }
    ```

4.  Return Value (`{ data, error }`):

    - Actions _always_ return an object with either a `data` property (on success) or an `error` property (on failure).
    - `data`: The JSON-serializable value returned by the `handler`.
    - `error`: An `ActionError` instance containing:
      - `code`: A standardized error code string (e.g., `VALIDATION_ERROR`, `UNAUTHORIZED`, `INTERNAL_SERVER_ERROR`, `NOT_FOUND`).
      - `message`: A description of the error.
      - `fields` (for `VALIDATION_ERROR`): An object detailing validation errors per input field.

5.  Input Validation:

    - Defining an `input` schema with Zod in `defineAction` enables automatic validation.
    - Works for both JSON payloads (from client-side calls) and `FormData` (from HTML forms).
    - If validation fails, the action call returns an `error` with `code: 'VALIDATION_ERROR'` and details in `error.fields`.

6.  HTML Form Integration:

    - Use an action directly in a `<form>`'s `action` attribute. Import the specific action and use `action={actions.actionName}`.
    - Method must be `POST`. Astro handles sending `FormData`.
    - Access the result on the _next_ page load (after submission/redirect) using `Astro.getActionResult(actions.actionName)` in the page's frontmatter script. This returns the same `{ data, error }` structure or `undefined` if the action wasn't the last one run for that page load.
    - Use this result to display success/error messages or validation feedback (`result.error?.fields`).
    - For more complex state persistence across redirects (avoiding browser resubmission warnings), use middleware with `getActionContext` and session storage (see advanced docs).

7.  Security:

    - Actions create public endpoints under `/_actions/actionName`.
    - Crucially, implement authorization checks within each action's `handler` using `context.locals`, cookies, etc. Throw an `ActionError({ code: 'UNAUTHORIZED' })` if the user is not permitted.
    - Optionally, gate access to actions more broadly using middleware and `getActionContext` from `astro:actions`, but per-action checks in the handler are still recommended for fine-grained control.

8.  Server-Side Calling:
    - Call actions from `.astro` frontmatter using `Astro.callAction(actions.actionName, input)`.
    - Call actions from endpoints or middleware using `context.callAction(actions.actionName, input)`.
    - Returns the same `{ data, error }` structure. Useful for reusing action logic on the server.

Reference: [Astro Actions Docs](mdc:https:/docs.astro.build/en/guides/actions)