Api

useAction

API reference for the useAction Vue composable — reactive execution, status tracking, validation errors, and lifecycle callbacks for type-safe server actions.

useAction

Vue composable for executing server actions with reactive state.

Import

import { useAction } from '#imports' // auto-imported

Signature

function useAction<TInput, TOutput>(
  action: SafeAction<TInput, TOutput>,
  callbacks?: ActionCallbacks<TInput, TOutput>
): UseActionReturn<TInput, TOutput>

Parameters

ParameterTypeRequiredDescription
actionSafeActionYesAn action imported from #safe-action/actions
callbacksActionCallbacksNoLifecycle callbacks

Return Values

PropertyTypeDescription
execute(input)(input: TInput) => voidFire-and-forget execution. Triggers callbacks but does not return a value.
executeAsync(input)(input: TInput) => Promise<ActionResult>Awaitable execution. Returns the full action result.
dataRef<TOutput | undefined>The success data from the last execution.
serverErrorRef<string | undefined>Server error message from the last execution.
validationErrorsRef<Record<string, string[]> | undefined>Per-field validation errors from Zod or returnValidationErrors.
statusRef<ActionStatus>Current status: 'idle', 'executing', 'hasSucceeded', or 'hasErrored'.
isIdleComputedRef<boolean>true when status is 'idle'.
isExecutingComputedRef<boolean>true when status is 'executing'.
hasSucceededComputedRef<boolean>true when status is 'hasSucceeded'.
hasErroredComputedRef<boolean>true when status is 'hasErrored'.
reset()() => voidReset all state (data, serverError, validationErrors, status) to initial values.

Callbacks

CallbackSignatureDescription
onSuccess({ data, input }) => voidCalled when the action succeeds. data is the typed return value.
onError({ error, input }) => voidCalled on server or validation error.
onSettled({ result, input }) => voidCalled after every execution, regardless of outcome.
onExecute({ input }) => voidCalled when execution starts, before the request is made.

Examples

Basic

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

const { execute, data, isExecuting } = useAction(greet)
</script>

With All Callbacks

<script setup lang="ts">
import { createPost } from '#safe-action/actions'

const { execute, data, validationErrors, serverError } = useAction(createPost, {
  onExecute({ input }) {
    console.log('Starting with input:', input)
  },
  onSuccess({ data, input }) {
    console.log('Created:', data.title)
    navigateTo(`/posts/${data.id}`)
  },
  onError({ error, input }) {
    console.error('Failed to create post:', error)
  },
  onSettled({ result, input }) {
    console.log('Finished, result:', result)
  },
})
</script>

Async Execution

<script setup lang="ts">
import { createPost } from '#safe-action/actions'

const { executeAsync } = useAction(createPost)

async function handleSubmit(formData: { title: string; body: string }) {
  const result = await executeAsync(formData)

  if (result.data) {
    await navigateTo(`/posts/${result.data.id}`)
  }

  if (result.serverError) {
    showToast(result.serverError)
  }
}
</script>

Form with Validation Errors

<script setup lang="ts">
import { register } from '#safe-action/actions'

const form = reactive({ email: '', password: '' })

const { execute, validationErrors, serverError, isExecuting } = useAction(register, {
  onSuccess() {
    navigateTo('/dashboard')
  },
})
</script>

<template>
  <form @submit.prevent="execute(form)">
    <div>
      <label>Email</label>
      <input v-model="form.email" type="email" />
      <p v-if="validationErrors?.email" class="error">
        {{ validationErrors.email[0] }}
      </p>
    </div>
    <div>
      <label>Password</label>
      <input v-model="form.password" type="password" />
      <p v-if="validationErrors?.password" class="error">
        {{ validationErrors.password[0] }}
      </p>
    </div>
    <p v-if="serverError" class="error">{{ serverError }}</p>
    <button type="submit" :disabled="isExecuting">
      {{ isExecuting ? 'Registering...' : 'Register' }}
    </button>
  </form>
</template>
Copyright © 2026