Workflows
Use Workflows for long-running work that needs a tracked run, retries, resumable state, or durable steps.
Use Queue when you only need to deliver a job. A Workflow starts and tracks a run.
Quick start
Install
pnpm add @vite-hub/runtime @vite-hub/workflow
Configure
import { hubWorkflow } from '@vite-hub/workflow/vite'
import { defineConfig } from 'vite'
export default defineConfig({
plugins: [hubWorkflow()],
})
Start using it
import { defineWorkflow } from '@vite-hub/workflow'
export default defineWorkflow<{ email: string }>(async ({ payload }) => {
return createUser(payload.email)
})
import { runWorkflow } from '@vite-hub/workflow'
export default defineEventHandler(async () => {
return runWorkflow('onboard-user', { email: 'ada@example.com' })
})
Public imports
| Import | Use |
|---|---|
defineWorkflow from @vite-hub/workflow | Declare a Workflow Definition. |
runWorkflow, deferWorkflow, getWorkflowRun, cancelWorkflow, resumeWorkflowSignal from @vite-hub/workflow | Start, defer, inspect, cancel, or resume Workflow Runs. |
createWorkflow from @vite-hub/workflow | Create an inline Workflow Handle for app-owned code. |
normalizeWorkflowOptions from @vite-hub/workflow | Resolve Integration Options to a concrete Workflow Provider. |
ViteHubError from @vite-hub/runtime | Throw application-owned Workflow failures with stable codes. |
readRequestPayload, readValidatedPayload, validatePayload from @vite-hub/workflow | Read provider request payloads in custom runtime entrypoints. |
hubWorkflow from @vite-hub/workflow/vite | Register Workflow discovery and provider output generation. |
Workflow Provider, Definition, Run, Step, Start Options, and Integration Options types are exported from @vite-hub/workflow.
Configure the Vite Integration
import { hubWorkflow } from '@vite-hub/workflow/vite'
import { defineConfig } from 'vite'
export default defineConfig({
plugins: [hubWorkflow()],
})
The Vite config key is workflow.
| Option | Type | Default | Description |
|---|---|---|---|
workflow | boolean or WorkflowModuleOptions | disabled | Enables Workflow discovery and provider output through vitehub() with true or an options object; false leaves it disabled. |
provider | WorkflowProvider | inferred | Selects cloudflare, vercel, or openworkflow. |
binding | string | provider default | Provider binding name for generated output. |
name | string | discovered workflow name | Provider resource name override. |
database | string | none | OpenWorkflow storage through a ViteHub Named Database. |
postgres.url | WorkflowRuntimeConfigValue | none | OpenWorkflow Postgres URL. |
postgres.schema | string | provider default | OpenWorkflow Postgres schema. |
postgres.namespaceId | string | provider default | OpenWorkflow namespace id. |
postgres.runMigrations | boolean | provider default | Runs OpenWorkflow storage migrations. |
sqlite.path | WorkflowRuntimeConfigValue | none | OpenWorkflow SQLite path. |
sqlite.namespaceId | string | provider default | OpenWorkflow SQLite namespace id. |
sqlite.runMigrations | boolean | provider default | Runs OpenWorkflow SQLite migrations. |
worker.concurrency | number | provider default | OpenWorkflow worker concurrency. |
When no provider is configured, ViteHub selects Cloudflare on Cloudflare hosting and Vercel on other supported hosts. Netlify cannot infer a Workflow Provider, so set provider explicitly or disable Workflow there. On Node or Docker hosting, OpenWorkflow is inferred when OpenWorkflow storage is configured through database, postgres.url, or sqlite.path.
Providers
| Provider | Configure with | Provider output | Nuance |
|---|---|---|---|
| Cloudflare | workflow: { provider: 'cloudflare' } | Cloudflare Workflow class, binding, and runtime entry output. | Runs through Cloudflare Workflow bindings. Use binding and name when the generated names must match existing infrastructure. |
| Vercel | workflow: { provider: 'vercel' } | Vercel workflow runtime output under the build output. | Persists run state through provider runtime support and Vercel-specific workflow names. |
| OpenWorkflow | workflow: { provider: 'openworkflow', database/postgres/sqlite } | OpenWorkflow worker/runtime output. | Requires explicit storage. database, postgres.url, and sqlite.path are mutually exclusive storage choices. |
Define a workflow
Create a Workflow Definition for named long-running work.
import { defineWorkflow } from '@vite-hub/workflow'
export default defineWorkflow<{ email: string }>(async ({ payload }) => {
const user = await createUser(payload.email)
await sendWelcomeEmail(user.email)
return { userId: user.id }
})
Use Workflow Steps only when the selected provider and definition need independently retryable or inspectable units.
Workflow Definition options
defineWorkflow(handler, options?) accepts these options. The discovered file name provides the Definition name.
| Option | Type | Description |
|---|---|---|
id | string | Static provider id override for the Workflow Definition. |
native | WorkflowHandler | Provider-native durable entry used by Vercel Workflow DevKit. |
rootStep | boolean | Wraps the handler in a root Workflow Step when the provider supports steps. |
The handler receives a WorkflowExecutionContext with name, payload, provider, optional run id, and provider-backed step or typed steps helpers when available.
Add a durable Vercel entry
Vercel runs the normal handler inline unless the definition provides native.
Inline work does not survive a function restart. Register a Workflow DevKit entry
when the run needs Vercel's durable execution:
pnpm add workflow @workflow/builders
import {
defineWorkflow,
type WorkflowExecutionContext,
} from '@vite-hub/workflow'
interface OnboardPayload {
email: string
}
async function createUserStep(email: string) {
'use step'
return await createUser(email)
}
async function sendWelcomeEmailStep(email: string) {
'use step'
await sendWelcomeEmail(email)
}
async function durableOnboard({ payload }: WorkflowExecutionContext<OnboardPayload>) {
'use workflow'
const user = await createUserStep(payload.email)
await sendWelcomeEmailStep(user.email)
return { userId: user.id }
}
async function inlineOnboard({ payload }: WorkflowExecutionContext<OnboardPayload>) {
const user = await createUser(payload.email)
await sendWelcomeEmail(user.email)
return { userId: user.id }
}
export default defineWorkflow(inlineOnboard, { native: durableOnboard })
ViteHub transforms the native entry when it generates Vercel output. Other
providers keep using the normal handler. Keep external side effects in use step
functions and make them idempotent because a step can be retried.
Start a run
Use runWorkflow() from server code.
import { runWorkflow } from '@vite-hub/workflow'
export default defineEventHandler(async (event) => {
const body = await readBody<{ email: string }>(event)
return runWorkflow('onboard-user', body)
})
The run id belongs to Invocation Options. Use a stable id when the selected
provider supports caller-assigned ids and needs to deduplicate or resume the
same logical run. Native Vercel workflows reject an explicit id; let Workflow
DevKit assign it as shown above.
Runtime helpers
| Helper | Description |
|---|---|
runWorkflow(name, payload?, options?) | Starts a Workflow Run immediately. |
deferWorkflow(name, payload?, options?) | Starts a run through the deferred provider path when available. |
getWorkflowRun(name, id) | Reads the current run state. |
cancelWorkflow(name, id) | Cancels a durable Vercel run. |
resumeWorkflowSignal(token, payload) | Resumes a Vercel operation using a registered Workflow DevKit hook token. |
createWorkflow(name, options?) | Returns a handle with run, defer, getRun, and cancel. |
WorkflowStartOptions currently accepts id.
Cancellation currently requires a native Vercel Workflow Definition.
Cloudflare, OpenWorkflow, and inline Vercel runs report
WORKFLOW_OPERATION_UNSUPPORTED instead of simulating cancellation.
Signal resumption requires the Vercel provider, the Workflow DevKit runtime,
and a registered hook token. The application can choose a deterministic opaque
token; it becomes resumable when a native workflow registers the hook and
suspends while waiting for it. Pass that token to resumeWorkflowSignal().
It identifies the hook, not a Workflow Run. Cloudflare and OpenWorkflow report
signals as unsupported.
Structured errors
Throw ViteHubError when app code needs a stable failure contract across Workflow Providers. ViteHub-owned failures use the package's fixed WorkflowErrorCode vocabulary.
import { ViteHubError } from '@vite-hub/runtime'
import { defineWorkflow } from '@vite-hub/workflow'
export default defineWorkflow<{ recordingId: string }>(async ({ payload }) => {
try {
return await transcribeRecording(payload.recordingId)
}
catch (cause) {
throw new ViteHubError('TRANSCRIPTION_FAILED', 'Transcription failed.', {
cause,
details: { recordingId: payload.recordingId },
})
}
})
Every ViteHubError requires a stable code and public message. Calling error.toJSON() returns name, code, message, and JSON-safe details; it omits cause, which stays on the in-memory error for logging and debugging. ViteHub's built-in codes are typed as WorkflowErrorCode with code-derived messages and code-specific details. Configure retries on the Workflow Step; throwing an error does not override the Step's retry policy.
Workflow Run shape
| Field | Type | Description |
|---|---|---|
id | string | Provider or ViteHub Workflow Run id. |
provider | WorkflowProvider | Selected provider for the run. |
status | WorkflowRunStatus | queued, running, completed, failed, or unknown. |
result | TResult | Completed result when available. |
payload | TPayload | Original payload when the provider returns it. |
metadata | unknown | Provider metadata. |
Inspect a run
Use getWorkflowRun() when server code needs current run state.
import { getWorkflowRun } from '@vite-hub/workflow'
export default defineEventHandler((event) => {
return getWorkflowRun('onboard-user', getRouterParam(event, 'id')!)
})
Connect Workflows to Agents
An Agent can start a workflow only when you expose that action through a Capability or server route. Workflows track durable work. Agents provide model-backed behavior.
Use a product-specific Capability when a model needs to start or inspect a particular Workflow Run.
Production checks
Use Queue when background delivery is enough. Use Workflow when the app must inspect run state, resume work, or coordinate multiple steps over time.
Keep credentials and database URLs in Server Env. Hosted workflow providers may require explicit state storage or deployment setup.
Next steps
- Use Queue for simple background delivery.
- Trigger recurring work with Schedule.
- Learn shared runtime events in Runtime policy, approvals, and traces.