---
title: TanStack Start
description: Set up your first durable workflow in a TanStack Start application.
type: guide
summary: Set up Workflow SDK in a TanStack Start app.
prerequisites:
  - /docs/getting-started
related:
  - /docs/foundations/workflows-and-steps
---

<CopyPrompt
  text="In this TanStack Start app, run `npm i workflow`. In `vite.config.ts`, import `workflow` from `workflow/vite` and add `workflow()` first in the existing `plugins` array before `tanstackStart()`, `nitro()`, or other plugins. Add `{ &quot;name&quot;: &quot;workflow&quot; }` to `compilerOptions.plugins` in `tsconfig.json` if TypeScript is used. Create `src/workflows/user-signup.ts` with `handleUserSignup(email)`, `&quot;use workflow&quot;`, `sleep`, and `&quot;use step&quot;` helpers. Add `src/routes/api/signup.ts` using `createFileRoute(&quot;/api/signup&quot;)`, a POST server handler, `start` from `workflow/api`, and `json` from `@tanstack/react-start`. Run `npm run dev`, call `curl -X POST --json '{&quot;email&quot;:&quot;hello@example.com&quot;}' http://localhost:3000/api/signup`, and inspect with `npx workflow web` or `npx workflow inspect runs`."
/>

Set up your first durable workflow in a TanStack Start app and learn the core Workflow SDK concepts.

---

<Steps>

<Step>
## Create your TanStack Start project

Start by creating a new TanStack Start project:

```bash
npx @tanstack/cli create my-workflow-app
```

Enter the newly made directory:

```bash
cd my-workflow-app
```

### Install `workflow`

```package-install
npm i workflow
```

### Configure TanStack Start

TanStack Start runs on Vite, so the Workflow SDK is wired in via the same `workflow/vite` plugin. Add `workflow()` to the existing `plugins` array in your Vite config. List it first so the `"use workflow"` and `"use step"` transforms run before any other plugin processes the file.

```typescript title="vite.config.ts" lineNumbers
import { defineConfig } from "vite";
import { workflow } from "workflow/vite";
// ...

export default defineConfig({
  plugins: [
    workflow(), // [!code highlight]
    // ...the existing tanstackStart(), nitro(), and any other plugins
  ],
});
```

<Details>
<Summary className="[&_h3]:my-0">

### Set up IntelliSense for TypeScript (optional)

</Summary>

To enable helpful hints in your IDE, set up the workflow plugin in `tsconfig.json`:

```json title="tsconfig.json" lineNumbers
{
  "compilerOptions": {
    // ... rest of your TypeScript config
    "plugins": [
      {
        "name": "workflow" // [!code highlight]
      }
    ]
  }
}
```

</Details>

</Step>

<Step>

## Create your first workflow

Create a new file for our first workflow:

```typescript title="src/workflows/user-signup.ts" lineNumbers
import { sleep } from "workflow";

export async function handleUserSignup(email: string) {
  "use workflow"; // [!code highlight]

  const user = await createUser(email);
  await sendWelcomeEmail(user);

  await sleep("5s"); // Pause for 5s - doesn't consume any resources
  await sendOnboardingEmail(user);

  return { userId: user.id, status: "onboarded" };
}
```

We'll fill in those functions next, but first review this code:

* We define a **workflow** function with the directive `"use workflow"`. Think of the workflow function as the _orchestrator_ of individual **steps**.
* The Workflow SDK's `sleep` function allows us to suspend execution of the workflow without using up any resources. A sleep can be a few seconds, hours, days, or even months long.

## Create your workflow steps

Define the missing functions.

```typescript title="src/workflows/user-signup.ts" lineNumbers
import { FatalError } from "workflow"

// Our workflow function defined earlier

async function createUser(email: string) {
  "use step"; // [!code highlight]

  console.log(`Creating user with email: ${email}`);

  // Full Node.js access - database calls, APIs, etc.
  return { id: crypto.randomUUID(), email };
}

async function sendWelcomeEmail(user: { id: string; email: string; }) {
  "use step"; // [!code highlight]

  console.log(`Sending welcome email to user: ${user.id}`);

  if (Math.random() < 0.3) {
  // By default, steps will be retried for unhandled errors
   throw new Error("Retryable!");
  }
}

async function sendOnboardingEmail(user: { id: string; email: string}) {
  "use step"; // [!code highlight]

  if (!user.email.includes("@")) {
    // To skip retrying, throw a FatalError instead
    throw new FatalError("Invalid Email");
  }

  console.log(`Sending onboarding email to user: ${user.id}`);
}
```

Taking a look at this code:

* Business logic lives inside **steps**. When a workflow invokes a step, the workflow suspends while the step runs with full Node.js access. The combined handler may execute it inline; queued retries and continuations return through the same flow route.
* If a step throws an error, like in `sendWelcomeEmail`, the step will automatically be retried until it succeeds (or hits the step's max retry count).
* Steps can throw a `FatalError` if an error is intentional and should not be retried.

<Callout>
We'll dive deeper into workflows, steps, and other ways to suspend or handle events in [Foundations](/docs/foundations).
</Callout>

</Step>

<Step>

## Create your route handler

To invoke your new workflow, add a server handler at `src/routes/api/signup.ts`:

```typescript title="src/routes/api/signup.ts"
import { createFileRoute } from "@tanstack/react-router";
import { json } from "@tanstack/react-start";
import { start } from "workflow/api";
import { handleUserSignup } from "../../workflows/user-signup";

export const Route = createFileRoute("/api/signup")({
  server: {
    handlers: {
      POST: async ({ request }) => {
        const { email } = await request.json();
        // Executes asynchronously and doesn't block your app
        await start(handleUserSignup, [email]);
        return json({ message: "User signup workflow started" });
      },
    },
  },
});
```

This route handler creates a `POST` request endpoint at `/api/signup` that will trigger your workflow.

<Callout>
Workflows can be triggered from API routes or any server-side code.
</Callout>

</Step>

</Steps>

## Run in development

To start your development server, run the following command in your terminal in the TanStack Start root directory:

```bash
npm run dev
```

Once your development server is running, you can trigger your workflow by running this command in the terminal:

```bash
curl -X POST --json '{"email":"hello@example.com"}' http://localhost:3000/api/signup
```

Check the dev server logs to see your workflow execute as well as the steps that are being processed.

Additionally, you can use the [Workflow SDK CLI or Web UI](/docs/observability) to inspect your workflow runs and steps in detail.

```bash
# Open the observability Web UI on http://localhost:3456
npx workflow web
# or if you prefer a terminal interface, use the CLI inspect command
npx workflow inspect runs
```

![Workflow SDK Web UI](/o11y-ui.png)

---

## Deploying to production

Workflow SDK apps currently work best when deployed to [Vercel](https://vercel.com/home) and need no special configuration.

<FluidComputeCallout />

Check the [Deploying](/docs/deploying) section to learn how your workflows can be deployed elsewhere.

## Next steps

* Learn more about the [Foundations](/docs/foundations).
* Check [Errors](/docs/errors) if you encounter issues.
* Explore the [API Reference](/docs/api-reference).
