Skip to main content

Actions

Call external services or perform non-transactional Convex work with reactive operation state.

Use a Convex action for work that cannot run inside a query or mutation, such as calling a third-party API.

ts
const { run: generateSummary, pending: generating } = useConvexAction(api.ai.generateSummary)
const result = await generateSummary({ documentId })

Mutation or action

WorkUse
Read Convex dataQuery
Transactionally write Convex dataMutation
Call an external APIAction
Schedule durable Convex workMutation/action plus Convex scheduler as appropriate
Hold a Nuxt-only secretNitro server route

Do not use an action for ordinary database writes. Actions are not transactional across their external effects.

State and control flow

useConvexAction returns run and the same state as useConvexMutation:

  • status
  • pending
  • data
  • error
  • reset()

run resolves with the action result and rejects with a ConvexCallError that carries the action path in functionName.

ts
import { isConvexCallError } from '@lupinum/better-convex-nuxt/errors'

const { run: sendInvite, pending: sending } = useConvexAction(api.email.sendInvite)

async function invite() {
  try {
    await sendInvite({ email: email.value })
    toast.success('Invitation sent')
  } catch (error) {
    toast.error(isConvexCallError(error) ? error.message : 'Invitation failed')
  }
}

A server error message is the text that the Convex function put in ConvexError data. Library failures use fixed messages.

Progress

The composable tracks whether the action call is pending. It does not provide step-by-step action progress.

For long workflows, write progress to Convex data and observe that data with a query. The action remains the worker; the query becomes the observable product state.

Retries

An action may have completed an external side effect before its response fails. Design idempotency into external calls and do not retry unknown failures blindly.

Like mutations, an action call rejects with IDENTITY_CHANGED when the signed-in user changes before it finishes. Its result never appears in the new user's state.