惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Google Online Security Blog
Google Online Security Blog
S
Security @ Cisco Blogs
Recent Commits to openclaw:main
Recent Commits to openclaw:main
人人都是产品经理
人人都是产品经理
The Hacker News
The Hacker News
W
WeLiveSecurity
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
The Cloudflare Blog
博客园 - 司徒正美
雷峰网
雷峰网
L
LINUX DO - 最新话题
博客园 - 叶小钗
云风的 BLOG
云风的 BLOG
The Last Watchdog
The Last Watchdog
V2EX - 技术
V2EX - 技术
S
Security Affairs
有赞技术团队
有赞技术团队
月光博客
月光博客
T
Threatpost
T
Tor Project blog
O
OpenAI News
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
V
V2EX
Know Your Adversary
Know Your Adversary
Project Zero
Project Zero
博客园 - 三生石上(FineUI控件)
D
Docker
AWS News Blog
AWS News Blog
AI
AI
P
Proofpoint News Feed
K
Kaspersky official blog
H
Hackread – Cybersecurity News, Data Breaches, AI and More
D
Darknet – Hacking Tools, Hacker News & Cyber Security
www.infosecurity-magazine.com
www.infosecurity-magazine.com
S
Securelist
F
Fortinet All Blogs
F
Full Disclosure
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
量子位
Hacker News - Newest:
Hacker News - Newest: "LLM"
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
P
Palo Alto Networks Blog
Cyberwarzone
Cyberwarzone
Cisco Talos Blog
Cisco Talos Blog
美团技术团队
N
News | PayPal Newsroom
T
The Blog of Author Tim Ferriss
MyScale Blog
MyScale Blog

Workflow SDK Documentation

Patterns for Defining Tools Human-in-the-Loop Building Durable AI Agents Queueing User Messages Resumable Streams Sleep, Suspense, and Scheduling Streaming Updates from Tools API Reference Workflow Globals Changelog Resilient run start Cookbook Building a World Deploying Astro Express Fastify Hono Getting Started NestJS Next.js Nitro Nuxt Python SvelteKit Vite corrupted-event-log fetch-in-workflow hook-conflict Errors node-js-module-in-workflow serialization-failed start-invalid-workflow-function step-not-registered timeout-in-workflow webhook-invalid-respond-with-value webhook-response-not-sent workflow-not-registered Errors & Retrying Hooks & Webhooks Idempotency Foundations Serialization Starting Workflows Streaming Versioning Workflows and Steps How the Directives Work Encryption Event Sourcing Understanding Directives Migration Guides Migrating from AWS Step Functions Migrating from Inngest Migrating from Temporal Observability Testing Server-Based Testing createHook createWebhook defineHook FatalError fetch getStepMetadata getWorkflowMetadata getWritable workflow RetryableError sleep @workflow/vitest DurableAgent @workflow/ai WorkflowChatTransport getHookByToken getRun getWorld workflow/api resumeHook resumeWebhook Chat Session Modeling runtime-decryption-failed Upgrading Workflows abort-signal-timeout-in-workflow Cancellation How Cancellation Works Internal Serializable AbortController and AbortSignal Eager Processing of Steps & Incremental Event Replay TanStack Start Agent Cancellation Sequential & Parallel Execution Workflow Composition Local World | Workflow SDK Postgres World | Workflow SDK Vercel World | Workflow SDK Migrating from trigger.dev Secure Credential Handling Local World | Workflow SDK Postgres World | Workflow SDK Vercel World | Workflow SDK
Framework Integrations
2026-05-31 · via Workflow SDK Documentation

Guide for framework authors to integrate Workflow SDK with custom frameworks or runtimes.

For users: If you just want to use Workflow SDK with an existing framework, check out the Getting Started guide instead. This page is for framework authors who want to integrate Workflow SDK with their framework or runtime.

This guide walks you through building a framework integration for Workflow SDK using Bun as a concrete example. The same principles apply to any JavaScript runtime (Node.js, Deno, Cloudflare Workers, etc.).

Prerequisites: Before building a framework integration, we recommend reading How the Directives Work to understand the transformation system that powers Workflow SDK.

A framework integration has two main components:

  1. Build-time: Generate workflow handler files (flow.js, step.js, webhook.js)
  2. Runtime: Expose these handlers as HTTP endpoints in your application server

The purple boxes are what you implement—everything else is provided by Workflow SDK.

Let's build a complete integration for Bun. Bun is unique because it serves as both a runtime (needs code transformations) and a framework (provides Bun.serve() for HTTP routing).

Step 1: Generate Handler Files

Use the workflow CLI to generate the handler bundles. The CLI scans your workflows/ directory and creates flow.js, step.js, and webhook.js.

{
  "scripts": {
    "dev": "bun x workflow build && PORT=3152 bun run server.ts"
  }
}

For production integrations: Instead of using the CLI, extend the BaseBuilder class directly in your framework plugin. This gives you control over file watching, custom output paths, and framework-specific hooks. See the Next.js plugin for an example.

What gets generated:

  • /.well-known/workflow/v1/flow.js - Handles workflow execution (workflow mode transform)
  • /.well-known/workflow/v1/step.js - Handles step execution (step mode transform)
  • /.well-known/workflow/v1/webhook.js - Handles webhook delivery

Each file exports a POST function that accepts Web standard Request objects.

Step 2: Add Client Mode Transform (Optional)

Client mode transforms your application code to provide better DX. Add a Bun plugin to apply this transformation at runtime:

import { plugin } from "bun";
import { transform } from "@swc/core";

plugin({
  name: "workflow-transform",
  setup(build) {
    build.onLoad({ filter: /\.(ts|tsx|js|jsx)$/ }, async (args) => {
      const source = await Bun.file(args.path).text();

      // Optimization: Skip files that do not have any directives
      if (!source.match(/(use step|use workflow)/)) {
        return { contents: source };
      }

      const result = await transform(source, {
        filename: args.path,
        jsc: {
          experimental: {
            plugins: [
              [require.resolve("@workflow/swc-plugin"), { mode: "client" }], 
            ],
          },
        },
      });

      return { contents: result.code, loader: "ts" };
    });
  },
});

Activate the plugin in bunfig.toml:

preload = ["./workflow-plugin.ts"]

What this does:

  • Attaches workflow IDs to functions for use with start()
  • Provides TypeScript type safety
  • Prevents accidental direct execution of workflows

Why optional? Without client mode, you can still use workflows by manually constructing IDs or referencing the build manifest.

Step 3: Expose HTTP Endpoints

Wire up the generated handlers to HTTP endpoints using Bun.serve():

import flow from "./.well-known/workflow/v1/flow.js";
import step from "./.well-known/workflow/v1/step.js";
import * as webhook from "./.well-known/workflow/v1/webhook.js";

import { start } from "workflow/api";
import { handleUserSignup } from "./workflows/user-signup.js";

const server = Bun.serve({
  port: process.env.PORT,
  routes: {
    "/.well-known/workflow/v1/flow": {
      POST: (req) => flow.POST(req),
    },
    "/.well-known/workflow/v1/step": {
      POST: (req) => step.POST(req),
    },
    // webhook exports handlers for GET, POST, DELETE, etc.
    "/.well-known/workflow/v1/webhook/:token": webhook,

    // Example: Start a workflow
    "/": {
      GET: async (req) => {
        const email = `test-${crypto.randomUUID()}@test.com`;
        const run = await start(handleUserSignup, [email]);
        return Response.json({
          message: "User signup workflow started",
          runId: run.runId,
        });
      },
    },
  },
});

console.log(`Server listening on http://localhost:${server.port}`);

That's it! Your Bun integration is complete.

Your integration must expose three HTTP endpoints. The generated handlers manage all protocol details—you just route requests.

Workflow Endpoint

Route: POST /.well-known/workflow/v1/flow

Executes workflow orchestration logic. The workflow function is "rendered" multiple times during execution—each time it progresses until it encounters the next step.

Called when:

  • Starting a new workflow
  • Resuming after a step completes
  • Resuming after a webhook or hook triggers
  • Recovering from failures

Step Endpoint

Route: POST /.well-known/workflow/v1/step

Executes individual atomic operations within workflows. Each step runs exactly once per execution (unless retried due to failure). Steps have full runtime access (Node.js APIs, file system, databases, etc.).

Webhook Endpoint

Route: POST /.well-known/workflow/v1/webhook/:token

Delivers webhook data to running workflows via createWebhook(). The :token parameter identifies which workflow run should receive the data.

The webhook file structure varies by framework. Next.js generates webhook/[token]/route.js to leverage App Router's dynamic routing, while other frameworks generate a single webhook.js handler.

The Bun example demonstrates the core pattern. To adapt for your framework:

Build-Time

Option 1: Use the CLI (simplest)

This will default to scanning the ./workflows top-level directory for workflow files, and will output bundled files directly into your working directory.

Option 2: Extend BaseBuilder (recommended)

import { BaseBuilder } from "@workflow/cli/dist/lib/builders/base-builder";

class MyFrameworkBuilder extends BaseBuilder {
  constructor(options) {
    super({
      dirs: ["workflows"],
      workingDir: options.rootDir,
      watch: options.dev,
    });
  }

  override async build(): Promise<void> {
    const inputFiles = await this.getInputFiles();

    await this.createWorkflowsBundle({
      outfile: "/path/to/.well-known/workflow/v1/flow.js",
      format: "esm",
      inputFiles,
    });

    await this.createStepsBundle({
      outfile: "/path/to/.well-known/workflow/v1/step.js",
      format: "esm",
      inputFiles,
    });

    await this.createWebhookBundle({
      outfile: "/path/to/.well-known/workflow/v1/webhook.js",
    });
  }
}

If your framework supports virtual server routes and dev mode watching, make sure to adapt accordingly. Please open a PR to the Workflow SDK if the base builder class is missing necessary functionality.

Monorepos and Workspace Imports

If your framework integration lives in a subdirectory and your workflows import code from sibling workspace packages, pass projectRoot to BaseBuilder. Use the smallest directory that contains every workspace package imported by your workflows.

import { BaseBuilder } from "@workflow/cli/dist/lib/builders/base-builder";

class MyFrameworkBuilder extends BaseBuilder {
  constructor(options: {
    rootDir: string;
    workspaceRoot?: string;
    dev: boolean;
  }) {
    super({
      dirs: ["workflows"],
      workingDir: options.rootDir,
      projectRoot: options.workspaceRoot ?? options.rootDir, 
      watch: options.dev,
    });
  }

  override async build(): Promise<void> {
    const inputFiles = await this.getInputFiles();
    // ...
  }
}

Hook into your framework's build:

framework.hooks.hook("build:before", async () => {
  await new MyFrameworkBuilder(framework).build();
});

Runtime (Client Mode)

Add a loader/plugin for your bundler:

Rollup/Vite:

export function workflowPlugin() {
  return {
    name: "workflow-client-transform",
    async transform(code, id) {
      if (!code.match(/(use step|use workflow)/)) return null;

      const result = await transform(code, {
        filename: id,
        jsc: {
          experimental: {
            plugins: [[require.resolve("@workflow/swc-plugin"), { mode: "client" }]], 
          },
        },
      });

      return { code: result.code, map: result.map };
    },
  };
}

Webpack:

module.exports = {
  module: {
    rules: [
      {
        test: /\.(ts|tsx|js|jsx)$/,
        use: "workflow-client-loader", // Similar implementation
      },
    ],
  },
};

HTTP Server

Route the three endpoints to the generated handlers. The exact implementation depends on your framework's routing API.

In the bun example above, we left routing to the user. Essentially, the user has to serve routes like this:

import flow from "./.well-known/workflow/v1/flow.js";
import step from "./.well-known/workflow/v1/step.js";
import * as webhook from "./.well-known/workflow/v1/webhook.js";

// Expose the 3 generated routes
const server = Bun.serve({
  routes: {
    "/.well-known/workflow/v1/flow": {
      POST: (req) => flow.POST(req),
    },
    "/.well-known/workflow/v1/step": {
      POST: (req) => step.POST(req),
    },
    // webhook exports handlers for GET, POST, DELETE, etc.
    "/.well-known/workflow/v1/webhook/:token": webhook,
  },
});

Production framework integrations should handle this routing in the plugin instead of leaving it to the user, and this depends on each framework's unique implementaiton. Check the Workflow SDK source code for examples of production framework implementations. In the future, the Workflow SDK will emit more routes under the .well-known/workflow namespace.

The workflow and step handler endpoints are invoked by the world's queuing infrastructure, not by end users. How they're secured depends on which world you're deploying to.

Vercel (@workflow/world-vercel)

On Vercel, workflow handler functions are not accessible through public endpoints. Handlers use the same consumer function security mechanism that secures Vercel Queues consumers.

During the build step, the Workflow SDK automatically configures each handler as a queue consumer by writing experimentalTriggers to the function's .vc-config.json:

{
  "experimentalTriggers": [
    {
      "type": "queue/v2beta",
      "topic": "__wkf_step_*",
      "consumer": "default",
      "retryAfterSeconds": 5,
      "initialDelaySeconds": 0
    }
  ]
}

Two queue topics are created per deployment:

HandlerTopicDescription
step.func__wkf_step_*Step execution (long-running, maxDuration: max)
flow.func__wkf_workflow_*Workflow orchestration (maxDuration: 60)

If you're building a framework integration that targets Vercel, you should write these triggers into the .vc-config.json for each generated function. The STEP_QUEUE_TRIGGER and WORKFLOW_QUEUE_TRIGGER constants are exported from @workflow/builders for this purpose:

import { STEP_QUEUE_TRIGGER, WORKFLOW_QUEUE_TRIGGER } from "@workflow/builders";

Custom implementations

For self-hosted or non-Vercel deployments, you are responsible for securing the handler endpoints:

  • Framework middleware — Add authentication (API keys, JWT, OIDC) in front of the /.well-known/workflow/v1/* routes
  • Network-level security — Deploy handlers behind a VPC, private network, or firewall rules so only your queue infrastructure can reach them
  • Rate limiting — Add request validation and rate limiting to prevent abuse

Learn more about building custom Worlds.

1. Test Build Output

Create a test workflow:

import { sleep, createWebhook } from "workflow";

export async function handleUserSignup(email: string) {
  "use workflow";

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

  await sleep("5s");

  const webhook = createWebhook();
  await sendOnboardingEmail(user, webhook.url);

  await webhook;
  console.log("Webhook Resolved");

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

async function createUser(email: string) {
  "use step";

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

  return { id: crypto.randomUUID(), email };
}

async function sendWelcomeEmail(user: { id: string; email: string }) {
  "use step";

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

async function sendOnboardingEmail(user: { id: string; email: string }, callback: string) {
  "use step";

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

  console.log(`Click this link to resolve the webhook: ${callback}`);
}

Run your build and verify:

  • .well-known/workflow/v1/flow.js exists
  • .well-known/workflow/v1/step.js exists
  • .well-known/workflow/v1/webhook.js exists

2. Test HTTP Endpoints

Start your server and verify routes respond:

curl -X POST http://localhost:3000/.well-known/workflow/v1/flow
curl -X POST http://localhost:3000/.well-known/workflow/v1/step
curl -X POST http://localhost:3000/.well-known/workflow/v1/webhook/test

(Should respond but not trigger meaningful code without authentication/proper workflow run)

3. Run a Workflow End-to-End

import { start } from "workflow/api";
import { handleUserSignup } from "./workflows/test";

const run = await start(handleUserSignup, ["test@example.com"]);
console.log("Workflow started:", run.runId);