Getting Started

Usage

Learn how to create a type-safe action client, define validated server actions, and call them from Vue components with useAction.

Usage

Using nuxt-safe-action is a three-step process: create a client, define actions, and use them in components.

1. Create an Action Client

Create a reusable action client in your server utilities:

server/utils/action-client.ts
import { createSafeActionClient } from '#safe-action'

export const actionClient = createSafeActionClient({
  handleServerError: (error) => {
    console.error('Action error:', error.message)
    return error.message
  },
})

2. Define an Action

Create a file in server/actions/ that default-exports an action:

server/actions/greet.ts
import { z } from 'zod'
import { actionClient } from '../utils/action-client'

export default actionClient
  .schema(z.object({
    name: z.string().min(1, 'Name is required'),
  }))
  .action(async ({ parsedInput }) => {
    return { greeting: `Hello, ${parsedInput.name}!` }
  })

3. Use in a Component

Import the action from the virtual module and use it with the useAction composable:

app/components/GreetForm.vue
<script setup lang="ts">
import { greet } from '#safe-action/actions'

const { execute, data, isExecuting, hasSucceeded, validationErrors } = useAction(greet, {
  onSuccess({ data }) {
    console.log(data.greeting) // fully typed!
  },
  onError({ error }) {
    console.error(error)
  },
})
</script>

<template>
  <form @submit.prevent="execute({ name: 'World' })">
    <button :disabled="isExecuting">
      {{ isExecuting ? 'Loading...' : 'Greet' }}
    </button>
    <p v-if="hasSucceeded">{{ data?.greeting }}</p>
  </form>
</template>

How It Works

  1. You define actions in server/actions/ using the builder chain
  2. The module scans that directory and generates Nitro API routes at /api/_actions/<name>
  3. A typed virtual module (#safe-action/actions) provides client-side references with full type inference
  4. useAction() calls the generated route via $fetch and returns reactive state
Action file names become the import names. server/actions/create-post.ts is imported as createPost from #safe-action/actions. You can add a method suffix (e.g. .get, .put, .delete) to change the HTTP method — see Defining Actions.
Copyright © 2026