diff --git a/src/content/changelog/workflows/2025-04-07-workflows-ga.mdx b/src/content/changelog/workflows/2025-04-07-workflows-ga.mdx new file mode 100644 index 000000000000000..360f973e70d5b62 --- /dev/null +++ b/src/content/changelog/workflows/2025-04-07-workflows-ga.mdx @@ -0,0 +1,69 @@ +--- +title: Workflows is now Generally Available +description: Workflows is now GA - ship Workflows that you can rely on in production. + - workflows + - workers +date: 2025-04-07T13:00:00Z +hidden: true +--- + +import { Render, PackageManagers, TypeScriptExample } from "~/components" + +[Workflows](/workflows/) is now _Generally Available_ (or "GA"): in short, it's ready for production workloads. Alongside marking Workflows as GA, we've introduced a number of changes during the beta period, including: + +* A new `waitForEvent` API that allows a Workflow to wait for an event to occur before continuing execution. +* Increased concurrency: you can [run up to 4,500 Workflow instances](/changelog/2025-02-25-workflows-concurrency-increased/) concurrently — and this will continue to grow. +* Improved observability, including new CPU time metrics that allow you to better understand which Workflow instances are consuming the most resources and/or contributing to your bill. +* Support for `vitest` for testing Workflows locally and in CI/CD pipelines. + +Workflows also supports the new [increased CPU limits](/changelog/2025-03-25-higher-cpu-limits/) that apply to Workers, allowing you to run more CPU-intensive tasks (up to 5 minutes of CPU time per instance), not including the time spent waiting on network calls, AI models, or other I/O bound tasks. + +#### Human-in-the-loop + +The new `step.waitForEvent` API allows a Workflow instance to wait on events and data, enabling human-in-the-the-loop interactions, such as approving or rejecting a request, directly handling webhooks from other systems, or pushing event data to a Workflow while it's running. + +Because Workflows are just code, you can conditionally execute code based on the result of a `waitForEvent` call, and/or call `waitForEvent` multiple times in a single Workflow based on what the Workflow needs. + +For example, if you wanted to implement a human-in-the-loop approval process, you could use `waitForEvent` to wait for a user to approve or reject a request, and then conditionally execute code based on the result. + + + +```ts +import { Workflow, WorkflowEvent } from "cloudflare:workflows"; + +export class MyWorkflow extends WorkflowEntrypoint { + async run(event: WorkflowEvent, step: WorkflowStep) { + // Other steps in your Workflow + let event = await step.waitForEvent("receive invoice paid webhook from Stripe", { type: "stripe-webhook", timeout: "1 hour" }) + // Rest of your Workflow + } +} +``` + + + +You can then send a Workflow an event from an external service via HTTP or from within a Worker using the [Workers API](/workflows/build/workers-api/) for Workflows: + + + +```ts +export default { + async fetch(req: Request, env: Env) { + const instanceId = new URL(req.url).searchParams.get("instanceId") + const webhookPayload = await req.json() + + let instance = await env.MY_WORKFLOW.get(instanceId); + // Send our event, with `type` matching the event type defined in + // our step.waitForEvent call + await instance.sendEvent({type: "stripe-webhook", payload: webhookPayload}) + + return Response.json({ + status: await instance.status(), + }); + }, +}; +``` + + + +Read the [GA announcement blog](https://blog.cloudflare.com/workflows-is-now-generally-available/) to learn more about what landed as part of the Workflows GA. diff --git a/src/content/docs/workflows/build/events-and-parameters.mdx b/src/content/docs/workflows/build/events-and-parameters.mdx index 143632df05b212d..0245453a5fd5f6f 100644 --- a/src/content/docs/workflows/build/events-and-parameters.mdx +++ b/src/content/docs/workflows/build/events-and-parameters.mdx @@ -12,12 +12,13 @@ When a Workflow is triggered, it can receive an optional event. This event can i Events are a powerful part of a Workflow, as you often want a Workflow to act on data. Because a given Workflow instance executes durably, events are a useful way to provide a Workflow with data that should be immutable (not changing) and/or represents data the Workflow needs to operate on at that point in time. -## Pass parameters to a Workflow +## Pass data to a Workflow -You can pass parameters to a Workflow in two ways: +You can pass parameters to a Workflow in three ways: * As an optional argument to the `create` method on a [Workflow binding](/workers/wrangler/commands/#trigger) when triggering a Workflow from a Worker. * Via the `--params` flag when using the `wrangler` CLI to trigger a Workflow. +* Via the `step.waitForEvent` API, which allows a Workflow instance to wait for an event (and optional data) to be received _while it is running_. Workflow instances can be sent events from external services over HTTP or via the Workers API for Workflows. You can pass any JSON-serializable object as a parameter. @@ -29,6 +30,8 @@ Store state durably by returning it from your `step.do` callbacks. ::: + + ```ts export default { async fetch(req: Request, env: Env) { @@ -51,6 +54,8 @@ export default { }; ``` + + To pass parameters via the `wrangler` command-line interface, pass a JSON string as the second parameter to the `workflows trigger` sub-command: ```sh @@ -60,6 +65,69 @@ npx wrangler@latest workflows trigger workflows-starter '{"some":"data"}' 🚀 Workflow instance "57c7913b-8e1d-4a78-a0dd-dce5a0b7aa30" has been queued successfully ``` +### Wait for events + +A running Workflow can wait for an event (or events) by calling `step.waitForEvent` within the Workflow, which allows you to send events to the Workflow in one of two ways: + +1. Via the [Workers API binding](/workflows/build/workers-api/): call `instance.sendEvent` to send events to specific workflow instances. +2. Using the REST API (HTTP API)'s [Events endpoint](/api/resources/workflows/subresources/instances/subresources/events/methods/create/). + +Because `waitForEvent` is part of the `WorkflowStep` API, you can call it multiple times within a Workflow, and use control flow to conditionally wait for an event. + +Calling `waitForEvent` requires you to specify an `type`, which is used to match the corresponding `type` when sending an event to a Workflow instance. + +For example, to wait for billing webhook: + + + +```ts +export class MyWorkflow extends WorkflowEntrypoint { + async run(event: WorkflowEvent, step: WorkflowStep) { + // Other steps in your Workflow + let event = await step.waitForEvent("receive invoice paid webhook from Stripe", { type: "stripe-webhook", timeout: "1 hour" }) + // Rest of your Workflow + } +} +``` + + + +The above example: + +* Calls `waitForEvent` with a `type` of `stripe-webhook` - the corresponding `sendEvent` call would thus be `await instance.sendEvent({type: "stripe-webhook", payload: webhookPayload})`. +* Uses a TypeScript [type parameter](https://www.typescriptlang.org/docs/handbook/2/generics.html) to type the return value of `step.waitForEvent` as our `IncomingStripeWebhook`. +* Continues on with the rest of the Workflow. + +### Send events to running workflows + +Workflow instances that are waiting on events using the `waitForEvent` API can be sent events using the `instance.sendEvent` API: + + + +```ts +export default { + async fetch(req: Request, env: Env) { + const instanceId = new URL(req.url).searchParams.get("instanceId") + const webhookPayload = await req.json() + + let instance = await env.MY_WORKFLOW.get(instanceId); + // Send our event, with `type` matching the event type defined in + // our step.waitForEvent call + await instance.sendEvent({type: "stripe-webhook", payload: webhookPayload}) + + return Response.json({ + status: await instance.status(), + }); + }, +}; +``` + + + +* Similar to the [`waitForEvent`](#wait-for-events) example in this guide, the `type` property in our `waitForEvent` and `sendEvent` fields must match. +* To send multiple events to a Workflow that has multiple `waitForEvent` calls, call `sendEvent` with the corresponding `type` property set. +* Events can also be sent using the REST API (HTTP API)'s [Events endpoint](/api/resources/workflows/subresources/instances/subresources/events/methods/create/). + ## TypeScript and type parameters By default, the `WorkflowEvent` passed to the `run` method of your Workflow definition has a type that conforms to the following, with `payload` (your data), `timestamp`, and `instanceId` properties: diff --git a/src/content/docs/workflows/build/workers-api.mdx b/src/content/docs/workflows/build/workers-api.mdx index 3f5499b8c7744e3..17654e0bcc64798 100644 --- a/src/content/docs/workflows/build/workers-api.mdx +++ b/src/content/docs/workflows/build/workers-api.mdx @@ -6,7 +6,7 @@ sidebar: --- -import { MetaInfo, Render, Type, WranglerConfig } from "~/components"; +import { MetaInfo, Render, Type, TypeScriptExample, WranglerConfig } from "~/components"; This guide details the Workflows API within Cloudflare Workers, including methods, types, and usage examples. @@ -84,7 +84,7 @@ When returning state from a `step`, ensure that the object you return is _serial Primitive types like `string`, `number`, and `boolean`, along with composite structures such as `Array` and `Object` (provided they only contain serializable values), can be serialized. -Objects that include `Function` or `Symbol` types, and objects with circular references, cannot be serialized and the Workflow instance will throw an error if objects with those types is returned. +Objects that include `Function` or `Symbol` types, and objects with circular references, cannot be serialized and the Workflow instance will throw an error if objects with those types is returned. ::: @@ -107,6 +107,27 @@ More information about the limits imposed on Workflow can be found in the [Workf ::: +* step.waitForEvent(name: string, options: ): Promise<void> + + * `name` - the name of the step. + * `options` - an object with properties for `type`, which determines which event type this `waitForEvent` call will match on when calling `instance.sendEvent`, and an optional `timeout` property, which defines how long the `waitForEvent` call will block for before throwing a timeout exception. The default timeout is 24 hours. + + + +```ts +export class MyWorkflow extends WorkflowEntrypoint { + async run(event: WorkflowEvent, step: WorkflowStep) { + // Other steps in your Workflow + let event = await step.waitForEvent("receive invoice paid webhook from Stripe", { type: "stripe-webhook", timeout: "1 hour" }) + // Rest of your Workflow + } +} +``` + + + +Review the documentation on [events and parameters](/workflows/build/events-and-parameters/) to learn how to send events to a running Workflow instance. + ## WorkflowStepConfig ```ts @@ -133,12 +154,6 @@ Refer to the [documentation on sleeping and retrying](/workflows/build/sleeping- ## Call Workflows from Workers -:::note[Workflows beta] - -Workflows currently requires you to bind to a Workflow via the [Wrangler configuration file](/workers/wrangler/configuration/), and does not yet support bindings via the Workers dashboard. - -::: - Workflows exposes an API directly to your Workers scripts via the [bindings](/workers/runtime-apis/bindings/#what-is-a-binding) concept. Bindings allow you to securely call a Workflow without having to manage API keys or clients. You can bind to a Workflow by defining a `[[workflows]]` binding within your Wrangler configuration. @@ -286,7 +301,7 @@ This is useful when you are scheduling multiple instances at once. A call to `cr * `batch` - list of Options to pass when creating an instance, including a user-provided ID and payload parameters. -Each element of the `batch` list is expected to the +Each element of the `batch` list is expected to include both `id` and `params` properties: ```ts // Create a new batch of 3 Workflow instances, each with its own ID and pass params to the Workflow instances @@ -410,6 +425,42 @@ Terminate a Workflow instance. * terminate(): Promise<void> +### sendEvent + +[Send an event](/workflows/build/events-and-parameters/) to a running Workflow instance. + +* sendEvent(): Promise<void> + + * `options` - the event `type` and `payload` to send to the Workflow instance. The `type` must match the `type` in the corresponding `waitForEvent` call in your Workflow. + +Return `void` on success; throws an exception if the Workflow is not running or is an errored state. + + + +```ts +export default { + async fetch(req: Request, env: Env) { + const instanceId = new URL(req.url).searchParams.get("instanceId") + const webhookPayload = await req.json() + + let instance = await env.MY_WORKFLOW.get(instanceId); + // Send our event, with `type` matching the event type defined in + // our step.waitForEvent call + await instance.sendEvent({type: "stripe-webhook", payload: webhookPayload}) + + return Response.json({ + status: await instance.status(), + }); + }, +}; +``` + + + +You can call `sendEvent` multiple times, setting the value of the `type` property to match the specific `waitForEvent` calls in your Workflow. + +This allows you to wait for multiple events at once, or use `Promise.race` to wait for multiple events and allow the first event to progress the Workflow. + ### InstanceStatus Details the status of a Workflow instance. diff --git a/src/content/docs/workflows/get-started/cli-quick-start.mdx b/src/content/docs/workflows/get-started/cli-quick-start.mdx index d8573317efc6e60..ad1ab347278b355 100644 --- a/src/content/docs/workflows/get-started/cli-quick-start.mdx +++ b/src/content/docs/workflows/get-started/cli-quick-start.mdx @@ -9,14 +9,6 @@ sidebar: import { Render, PackageManagers, WranglerConfig } from "~/components" -:::note - -Workflows is in **public beta**, and any developer with a [free or paid Workers plan](/workers/platform/pricing/#workers) can start using Workflows immediately. - -To learn more about Workflows and how it works, read [the beta announcement blog](https://blog.cloudflare.com/building-workflows-durable-execution-on-workers). - -::: - Workflows allow you to build durable, multi-step applications using the Workers platform. A Workflow can automatically retry, persist state, run for hours or days, and coordinate between third-party APIs. You can build Workflows to post-process file uploads to [R2 object storage](/r2/), automate generation of [Workers AI](/workers-ai/) embeddings into a [Vectorize](/vectorize/) vector database, or to trigger user lifecycle emails using your favorite email API. diff --git a/src/content/docs/workflows/get-started/guide.mdx b/src/content/docs/workflows/get-started/guide.mdx index aa3b703db5ae4dd..c29d39d90b5fc2e 100644 --- a/src/content/docs/workflows/get-started/guide.mdx +++ b/src/content/docs/workflows/get-started/guide.mdx @@ -9,14 +9,6 @@ sidebar: import { Render, PackageManagers, WranglerConfig } from "~/components" -:::note - -Workflows is in **public beta**, and any developer with a [free or paid Workers plan](/workers/platform/pricing/#workers) can start using Workflows immediately. - -To learn more about Workflows and how it works, read [the beta announcement blog](https://blog.cloudflare.com/building-workflows-durable-execution-on-workers). - -::: - Workflows allow you to build durable, multi-step applications using the Workers platform. A Workflow can automatically retry, persist state, run for hours or days, and coordinate between third-party APIs. You can build Workflows to post-process file uploads to [R2 object storage](/r2/), automate generation of [Workers AI](/workers-ai/) embeddings into a [Vectorize](/vectorize/) vector database, or to trigger user lifecycle emails using your favorite email API. diff --git a/src/content/docs/workflows/index.mdx b/src/content/docs/workflows/index.mdx index 665c376a83c668e..4c0a22bc4bc2c61 100644 --- a/src/content/docs/workflows/index.mdx +++ b/src/content/docs/workflows/index.mdx @@ -5,8 +5,6 @@ type: overview pcx_content_type: overview sidebar: order: 1 - badge: - text: Beta head: - tag: title content: Overview diff --git a/src/content/docs/workflows/reference/limits.mdx b/src/content/docs/workflows/reference/limits.mdx index f92128d6bf6a28e..c85be2585d9b45e 100644 --- a/src/content/docs/workflows/reference/limits.mdx +++ b/src/content/docs/workflows/reference/limits.mdx @@ -22,17 +22,15 @@ Many limits are inherited from those applied to Workers scripts and as documente | Maximum event [payload size](/workflows/build/events-and-parameters/) | 1MiB (2^20 bytes) | 1MiB (2^20 bytes) | | Maximum state that can be persisted per Workflow instance | 100MB | 1GB | | Maximum length of a Workflow ID [^4] | 64 characters | 64 characters | -| Maximum `step.sleep` duration | 365 days (1 year) [^1] | 365 days (1 year) [^1] | -| Maximum steps per Workflow [^5] | 1024 [^1] | 1024 [^1] | +| Maximum `step.sleep` duration | 365 days (1 year) | 365 days (1 year) | +| Maximum steps per Workflow [^5] | 1024 | 1024 | | Maximum Workflow executions | 100,000 per day [shared with Workers daily limit](/workers/platform/limits/#worker-limits) | Unlimited | -| Concurrent Workflow instances (executions) per account | 25 | 4500 [^1] | -| Maximum Workflow instance creation rate | 100 per 10 seconds [^1][^6] | 100 per 10 seconds [^1][^6] | -| Maximum number of [queued instances](/workflows/observability/metrics-analytics/#event-types) | 10,000 [^1] | 100,000 [^1] | +| Concurrent Workflow instances (executions) per account | 25 | 4500 | +| Maximum Workflow instance creation rate | 100 per 10 seconds [^6] | 100 per 10 seconds [^6] | +| Maximum number of [queued instances](/workflows/observability/metrics-analytics/#event-types) | 10,000 | 100,000 | | Retention limit for completed Workflow state | 3 days | 30 days [^2] | | Maximum length of a Workflow ID [^4] | 64 characters | 64 characters | -[^1]: This limit will be reviewed and revised during the open beta for Workflows. Follow the [Workflows changelog](/workflows/reference/changelog/) for updates. - [^2]: Workflow state and logs will be retained for 3 days on the Workers Free plan and for 7 days on the Workers Paid plan. [^3]: A Workflow instance can run forever, as long as each step does not take more than the CPU time limit and the maximum number of steps per Workflow is not reached. diff --git a/src/content/docs/workflows/reference/pricing.mdx b/src/content/docs/workflows/reference/pricing.mdx index be45f5a44ddc986..e7847df3c8b69f5 100644 --- a/src/content/docs/workflows/reference/pricing.mdx +++ b/src/content/docs/workflows/reference/pricing.mdx @@ -14,20 +14,52 @@ Workflows is included in both the Free and Paid [Workers plans](/workers/platfor ::: -Workflows pricing is identical to [Workers Standard pricing](/workers/platform/pricing/#workers) and are billed on two dimensions: +Workflows pricing is identical to [Workers Standard pricing](/workers/platform/pricing/#workers) and are billed on three dimensions: * **CPU time**: the total amount of compute (measured in milliseconds) consumed by a given Workflow. * **Requests** (invocations): the number of Workflow invocations. [Subrequests](/workers/platform/limits/#subrequests) made from a Workflow do not incur additional request costs. +* **Storage**: the total amount of storage (measured in GB) persisted by your Workflows. A Workflow that is waiting on a response to an API call, paused as a result of calling `step.sleep`, or otherwise idle, does not incur CPU time. +### Workflows Pricing + +| Unit | Workers Free | Workers Paid | +|------|--------------|--------------| +| Requests (millions) | 100,000 per day ([shared with Workers requests](/workers/platform/pricing/#workers)| 10 million included per month + $0.30 per additional million | +| CPU time (ms) | 10 milliseconds of CPU time per invocation | 30 million CPU milliseconds included per month + $0.02 per additional million CPU milliseconds | +| Storage (GB-mo) | 1GB | 1GB included per month + $0.20/ GB-month | + +::note[CPU limits] + +You can increase the CPU limit available to your Workflow instances up to 5 minutes per Workflow by [setting the `limits.cpu_ms` property](/workers/wrangler/configuration/#limits) in your Wrangler configuration. + +::: + +### Storage Usage + +:::Note + +Storage billing for Workflows will go live on September 15th, 2025. + +::: + +Storage is billed using gigabyte-month (GB-month) as the billing metric, identical to [Durable Objects SQL storage](/durable-objects/platform/pricing/#sqlite-storage-backend). A GB-month is calculated by averaging the peak storage per day over a billing period (30 days). + +* Storage is calculated across all instances, and includes running, errored, sleeping and completed instances. +* By default, instance state is retained for [3 days on the Free plan](/workflows/reference/limits/) and [7 days on the Paid plan](/workflows/reference/limits/). +* When creating a Workflow instance, you can set a shorter state retention period if you do not need to retain state for errored or completed Workflows. +* Deleting instances via the [Workers API](/workflows/build/workers-api/), [Wrangler CLI](/workers/wrangler/commands/#workflows), REST API, or dashboard will free up storage. Note that it may take a few minutes for storage limits to update. + +An instance that attempts to store state when your have reached the storage limit on the Free plan will cause an error to be thrown. + ## Frequently Asked Questions Frequently asked questions related to Workflows pricing: ### Are there additional costs for Workflows? -No. Workflows are priced based on the same compute (CPU time) and requests (invocations) as Workers. +No. Workflows are priced based on the same compute (CPU time), requests (invocations) as Workers, as well as storage (state from a Workflow). ### Are Workflows available on the [Workers Free](/workers/platform/pricing/#workers) plan?