---
title: "Workflows"
description: "Durable, multi-step functions that survive restarts — with default retry behavior baked in."
canonical_url: "https://nuxt-backend.dev/docs/data/jobs/workflows"
---
# Workflows

> Durable, multi-step functions that survive restarts — with default retry behavior baked in.

`setupWorkflows(components, options?)` from `nuxt-backend/workflows` returns a `WorkflowManager` with sensible defaults — a workpool of `maxParallelism: 10` and `defaultRetryBehavior: { maxAttempts: 3, initialBackoffMs: 250, base: 2 }`, both overridable through `options.workpoolOptions`. Define durable functions with `workflow.define({ args, handler })`, start them with `workflow.start(ctx, internal.x.y, args)` (it returns a `WorkflowId`), and read or stop a run with `workflow.status(ctx, id)` and `workflow.cancel(ctx, id)`.

## Email sequences

A sequence of delayed emails is a workflow built with `defineEmailSequence`: see [Send an email sequence](https://nuxt-backend.dev/docs/email/engagement/sequences), which also shows how the scaffold starts one on sign-up.

## Your own workflows

`workflow.define` is the general form. Each step is retried independently and survives restarts; the handler's `step` runs queries, mutations and actions, and sleeps:

```ts [backend/workflows.ts]
// Below the `workflow` defined above; `v` and `components` are already imported there.
export const provisionAccount = workflow.define({
  args: { email: v.string() },
  handler: async (step, { email }) => {
    await step.runMutation(components.backend.email.send, {
      to: email,
      subject: 'Your account is ready',
      text: 'Everything is set up.',
    })
    await step.sleep(60 * 60 * 1000)
    // more durable steps — step.runQuery / runMutation / runAction
  },
})
```

## Reactive status

`useWorkflowStatus(query, { workflowId })` subscribes to a query that takes `{ workflowId }` and wraps `workflow.status`. The scaffold doesn't ship one, so add it next to your workflows — `WorkflowId` is a branded string, so cast the validated arg back:

```ts [backend/workflows.ts]
// Below the `workflow` defined above — merge these imports with the file's.
import { type WorkflowId } from 'nuxt-backend/workflows'
import { query } from './_generated/server'

export const getWorkflowStatus = query({
  args: { workflowId: v.string() },
  handler: (ctx, { workflowId }) => workflow.status(ctx, workflowId as WorkflowId),
})
```

The subscription pauses while the id is `null`/`undefined`. It returns `data`, `isLoading` and `error` (a failed read never throws). `data` is the component's `WorkflowStatus`: `type` is `'inProgress'` (with the `running` steps), `'completed'` (with `result`), `'failed'` (with `error`) or `'canceled'`:

```vue
<script setup lang="ts">
import { api } from '#backend/api'

const workflowId = ref<string | null>(null)
const { data: status } = useWorkflowStatus(api.workflows.getWorkflowStatus, { workflowId })
// status.value?.type: 'inProgress' | 'completed' | 'failed' | 'canceled'
</script>
```

<note>

`useWorkflowStatus` is in the experimental tier (see STABILITY.md): it tracks the upstream workflow component's pre-1.0 status shape, which may change in a minor release.

</note>

<page-keys>

- Try it live in the playground: [/playground/platform/workflows](https://nuxt-backend.dev/playground/platform/workflows)
- API reference: [/docs/reference/api/workflows](https://nuxt-backend.dev/docs/reference/api/workflows)
- Source: [https://github.com/qruto/nuxt-backend/blob/main/src/convex/integrations/workflows.ts](https://github.com/qruto/nuxt-backend/blob/main/src/convex/integrations/workflows.ts)

</page-keys>


## Sitemap

See the full [sitemap](https://nuxt-backend.dev/sitemap.md) for all pages.
