Skip to content

Commit cedce7f

Browse files
committed
[Workflows] add subscribe documentation and changelog
1 parent 7233115 commit cedce7f

4 files changed

Lines changed: 349 additions & 1 deletion

File tree

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
---
2+
title: Workflows now support event subscriptions
3+
description: Workflows instances now support being subscribed to. This makes it possible to consume events as the workflow executes in your workers and applications.
4+
products:
5+
- workflows
6+
- workers
7+
date: 2026-09-10 12:00:00 UTC
8+
---
9+
10+
import { TypeScriptExample } from "~/components";
11+
12+
Workflows now supports event subscriptions through `WorkflowInstance.subscribe()` and the `GET /subscribe` API endpoint. A Worker or HTTP client can now react to Workflow, step, attempt, sleep, wait, and rollback events without polling it.
13+
14+
Subscriptions replay retained historical events before waiting for live updates, this means you that you will always receive the full list of events that occured in a instance in each new subscription. You can filter event types and resume after the last processed event with a cursor.
15+
16+
<TypeScriptExample>
17+
18+
```ts
19+
const instance = await env.MY_WORKFLOW.get("report-123");
20+
21+
using subscription = await instance.subscribe();
22+
23+
while (true) {
24+
const { value, done } = await subscription.next();
25+
if (done) {
26+
break;
27+
}
28+
29+
console.log(value.type, value);
30+
}
31+
```
32+
33+
</TypeScriptExample>
34+
35+
For event types, available fields, and subscription options, refer to [Subscribe to events](/workflows/build/subscribe-to-instance-events/).
Lines changed: 257 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
1+
---
2+
title: Subscribe to events
3+
description: Receive historical and live events from a Workflow instance without polling its status.
4+
pcx_content_type: concept
5+
sidebar:
6+
order: 7
7+
products:
8+
- workflows
9+
---
10+
11+
import { TypeScriptExample } from "~/components";
12+
13+
Use `WorkflowInstance.subscribe()` to receive events without polling [`status()`](/workflows/build/workers-api/#status). A subscription will always receive the entire history of events that occur in a instance and block waiting for new events if its still running.
14+
15+
You can subscribe immediately after creating an instance or you can also retrieve an existing instance with [`get()`](/workflows/build/workers-api/#get) and subscribe while its state remains within the [retention period](/workflows/reference/limits/).
16+
17+
## Subscribe to all events
18+
19+
<TypeScriptExample>
20+
21+
```ts
22+
interface Env {
23+
MY_WORKFLOW: Workflow;
24+
}
25+
26+
export default {
27+
async fetch(_request: Request, env: Env) {
28+
const instance = await env.MY_WORKFLOW.create({
29+
params: { reportId: "report-123" },
30+
});
31+
32+
using subscription = await instance.subscribe();
33+
34+
while (true) {
35+
const result = await subscription.next();
36+
if (result.done) {
37+
break;
38+
}
39+
40+
console.log(result.value.type, result.value);
41+
}
42+
43+
return Response.json({ instanceId: instance.id });
44+
},
45+
} satisfies ExportedHandler<Env>;
46+
```
47+
48+
</TypeScriptExample>
49+
50+
The subscription ends after the instance emits `workflow_completed`, `workflow_errored`, or `workflow_terminated`. After that evey call to next returns `done: true`.
51+
52+
## Filter events
53+
54+
Use `filter` to receive only specific event types during `next` calls, avoiding processing of unwanted events.
55+
56+
<TypeScriptExample>
57+
58+
```ts
59+
const instance = await env.MY_WORKFLOW.get("report-123");
60+
61+
using subscription = await instance.subscribe({
62+
filter: ["workflow_completed", "workflow_errored", "workflow_terminated"],
63+
});
64+
65+
const result = await subscription.next();
66+
if (result.done) {
67+
throw new Error("The instance ended without a matching event.");
68+
}
69+
70+
switch (result.value.type) {
71+
case "workflow_completed":
72+
console.log("Workflow output:", result.value.output);
73+
break;
74+
case "workflow_errored":
75+
console.error("Workflow errored:", result.value.error);
76+
break;
77+
case "workflow_terminated":
78+
console.log("Workflow terminated.");
79+
break;
80+
}
81+
```
82+
83+
</TypeScriptExample>
84+
85+
A subscription still ends when it reaches a terminal event excluded by its filter. In that case, `next()` returns `done: true` without returning the excluded event.
86+
87+
## Resume from a cursor
88+
89+
Each event includes an `eventId`. To resume after a RPC call failure store the last processed event ID and pass it as `cursor`:
90+
91+
<TypeScriptExample>
92+
93+
```ts
94+
using subscription = await instance.subscribe({
95+
cursor: lastProcessedEventId,
96+
filter: ["step_completed", "workflow_completed", "workflow_errored"],
97+
});
98+
99+
while (true) {
100+
const result = await subscription.next();
101+
if (result.done) {
102+
break;
103+
}
104+
105+
await processEvent(result.value);
106+
await saveLastProcessedEventId(result.value.eventId);
107+
}
108+
```
109+
110+
</TypeScriptExample>
111+
112+
The cursor identifies the last event you processed. The subscription starts with the first event whose `eventId` is greater than the cursor.
113+
114+
## Sensitive outputs
115+
116+
If a step is marked as sensitive, the `output` field in its `step_completed` will be the string `"[REDACTED]"`.
117+
118+
## Dispose of a subscription
119+
120+
A subscription holds a Workers RPC resource. Declare it with `using` so the runtime disposes it when the scope exits. This also cleans up the subscription when you stop reading before the instance ends.
121+
122+
If you cannot use `using`, call `subscription[Symbol.dispose]()` in a `finally` block. For more information, refer to [RPC lifecycle](/workers/runtime-apis/rpc/lifecycle/).
123+
124+
## Event fields
125+
126+
The public type definition shows the fields available on each event:
127+
128+
```ts
129+
type WorkflowInstanceEvent = {
130+
instanceId: string;
131+
eventId: number;
132+
timestamp: number;
133+
} & (
134+
| { type: "workflow_queued" }
135+
| { type: "workflow_started"; params?: unknown }
136+
| { type: "workflow_running" }
137+
| { type: "workflow_paused" }
138+
| { type: "workflow_waiting_for_pause" }
139+
| { type: "workflow_waiting" }
140+
| { type: "workflow_completed"; output?: unknown }
141+
| { type: "workflow_errored"; error: { name: string; message: string } }
142+
| { type: "workflow_terminated" }
143+
| {
144+
type: "step_started";
145+
stepName: string;
146+
config?: {
147+
retries: {
148+
limit: number;
149+
delay: WorkflowSleepDuration | "[dynamic]";
150+
backoff?: "constant" | "linear" | "exponential";
151+
};
152+
timeout: WorkflowSleepDuration;
153+
sensitive?: "output";
154+
};
155+
}
156+
| { type: "step_completed"; stepName: string; output?: unknown }
157+
| { type: "step_errored"; stepName: string }
158+
| { type: "attempt_started"; stepName: string; attempt: number }
159+
| { type: "attempt_completed"; stepName: string; attempt: number }
160+
| {
161+
type: "attempt_errored";
162+
stepName: string;
163+
attempt: number;
164+
retryDelayMs?: number;
165+
error: { name: string; message: string };
166+
}
167+
| { type: "sleep_started"; stepName: string; durationMs: number }
168+
| { type: "sleep_completed"; stepName: string }
169+
| { type: "wait_started"; stepName: string; eventType: string }
170+
| { type: "wait_completed"; stepName: string }
171+
| { type: "wait_timed_out"; stepName: string }
172+
| { type: "rollback_started" }
173+
| {
174+
type: "rollback_step_started";
175+
stepName: string;
176+
config?: {
177+
retries: {
178+
limit: number;
179+
delay: WorkflowSleepDuration | "[dynamic]";
180+
backoff?: "constant" | "linear" | "exponential";
181+
};
182+
timeout: WorkflowSleepDuration;
183+
sensitive?: "output";
184+
};
185+
}
186+
| { type: "rollback_step_completed"; stepName: string }
187+
| {
188+
type: "rollback_step_errored";
189+
stepName: string;
190+
error: { name: string; message: string };
191+
}
192+
| { type: "rollback_attempt_started"; stepName: string; attempt: number }
193+
| { type: "rollback_attempt_completed"; stepName: string; attempt: number }
194+
| {
195+
type: "rollback_attempt_errored";
196+
stepName: string;
197+
attempt: number;
198+
retryDelayMs?: number;
199+
error: { name: string; message: string };
200+
}
201+
| { type: "rollback_completed" }
202+
| { type: "rollback_errored" }
203+
);
204+
```
205+
206+
The following tables describe when each event is emitted.
207+
208+
### Workflow lifecycle events
209+
210+
| Event type | Emitted when |
211+
| ---------------------------- | ------------------------------------------------ |
212+
| `workflow_queued` | The instance enters the execution queue |
213+
| `workflow_started` | The instance starts |
214+
| `workflow_running` | The instance starts or resumes execution |
215+
| `workflow_paused` | The instance pauses |
216+
| `workflow_waiting_for_pause` | The instance waits for current work before pause |
217+
| `workflow_waiting` | The instance enters waiting state |
218+
| `workflow_completed` | The instance completes successfully |
219+
| `workflow_errored` | The instance ends with an error |
220+
| `workflow_terminated` | The instance is terminated |
221+
222+
### Step and attempt events
223+
224+
| Event type | Emitted when |
225+
| ------------------- | ---------------------------- |
226+
| `step_started` | A `step.do()` call starts |
227+
| `step_completed` | A `step.do()` call completes |
228+
| `step_errored` | A `step.do()` call errors |
229+
| `attempt_started` | A step attempt starts |
230+
| `attempt_completed` | A step attempt completes |
231+
| `attempt_errored` | A step attempt errors |
232+
233+
### Sleep and wait events
234+
235+
| Event type | Emitted when |
236+
| ----------------- | --------------------------------------------------- |
237+
| `sleep_started` | A `step.sleep()` or `step.sleepUntil()` call starts |
238+
| `sleep_completed` | A sleep finishes |
239+
| `wait_started` | A `step.waitForEvent()` call starts |
240+
| `wait_completed` | A matching event reaches `step.waitForEvent()` |
241+
| `wait_timed_out` | A `step.waitForEvent()` call times out |
242+
243+
### Rollback events
244+
245+
| Event type | Emitted when |
246+
| ---------------------------- | --------------------------------------- |
247+
| `rollback_started` | The Workflow starts a rollback |
248+
| `rollback_step_started` | A rollback handler starts |
249+
| `rollback_step_completed` | A rollback handler completes |
250+
| `rollback_step_errored` | A rollback handler errors |
251+
| `rollback_attempt_started` | A rollback attempt starts |
252+
| `rollback_attempt_completed` | A rollback attempt completes |
253+
| `rollback_attempt_errored` | A rollback attempt errors |
254+
| `rollback_completed` | All required rollback handlers complete |
255+
| `rollback_errored` | The rollback operation errors |
256+
257+
For method signatures and option types, refer to [`WorkflowInstance.subscribe()`](/workflows/build/workers-api/#subscribe).

src/content/docs/workflows/build/trigger-workflows.mdx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,8 @@ The possible values of status are as follows:
187187

188188
If your Workflow registers rollback handlers on `step.do()`, inspect `rollback` after the instance finishes to see whether the compensating steps completed successfully. While rollback is actively running, the Workers API continues to return `status: "running"`.
189189

190+
To receive historical and live execution updates without polling, refer to [Subscribe to events](/workflows/build/subscribe-to-instance-events/).
191+
190192
### Explicitly pause a Workflow
191193

192194
You can explicitly pause a Workflow instance (and later resume it) by calling `pause` against a specific instance ID.

src/content/docs/workflows/build/workers-api.mdx

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,6 @@ export class MyWorkflow extends WorkflowEntrypoint<Env> {
154154
- `name` - the name of the step.
155155
- `duration` - the duration to sleep for, as a `number` in milliseconds or as a `WorkflowDuration`-compatible string.
156156
- Refer to the [documentation on sleeping and retrying](/workflows/build/sleeping-and-retrying/) to learn more about how Workflows are retried.
157-
158157
- <code>step.sleepUntil(name: string, timestamp: Date | number): Promise&lt;void&gt;</code>
159158
- `name` - the name of the step.
160159
- `timestamp` - a JavaScript `Date` object or milliseconds from the Unix epoch to sleep the Workflow instance until.
@@ -610,6 +609,12 @@ declare abstract class WorkflowInstance {
610609
* Returns the current status of the instance.
611610
*/
612611
public status(): Promise<InstanceStatus>;
612+
/**
613+
* Subscribe to events from this Workflow instance.
614+
*/
615+
public subscribe(
616+
options?: WorkflowInstanceSubscribeOptions,
617+
): Promise<WorkflowInstanceSubscription>;
613618
}
614619
```
615620

@@ -760,6 +765,55 @@ You can call `sendEvent` multiple times, setting the value of the `type` propert
760765

761766
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.
762767

768+
### subscribe
769+
770+
Subscribe to historical and live execution events from a Workflow instance.
771+
772+
- <code>subscribe(options?: WorkflowInstanceSubscribeOptions): Promise&lt;WorkflowInstanceSubscription&gt;</code>
773+
- `options` - optional properties that set a starting cursor and filter event types.
774+
775+
The returned subscription provides a `next()` method. Each call returns the next matching `WorkflowInstanceEvent` or waits until one is available. The subscription ends after the instance reaches a terminal state by either completing, erroring, or terminating.
776+
777+
#### WorkflowInstanceSubscribeOptions
778+
779+
```ts
780+
interface WorkflowInstanceSubscribeOptions {
781+
/**
782+
* The event ID to start from.
783+
*/
784+
cursor?: number;
785+
/**
786+
* Emit only events with one of these types.
787+
*/
788+
filter?: WorkflowInstanceEventType[];
789+
}
790+
791+
type WorkflowInstanceEventType = WorkflowInstanceEvent["type"];
792+
```
793+
794+
Call `subscribe()` without options to receive all events. Use `cursor` and `filter` to control which events the subscription returns:
795+
796+
<TypeScriptExample>
797+
798+
```ts
799+
const instance = await env.MY_WORKFLOW.get("abc-123");
800+
801+
// Subscribe to all events.
802+
using allEvents = await instance.subscribe();
803+
804+
// Subscribe to events after event ID 100.
805+
using eventsAfterCursor = await instance.subscribe({ cursor: 100 });
806+
807+
// Subscribe to selected event types.
808+
using filteredEvents = await instance.subscribe({
809+
filter: ["workflow_completed", "workflow_errored"],
810+
});
811+
```
812+
813+
</TypeScriptExample>
814+
815+
For event types, filtering behavior, and cursor usage, refer to [Subscribe to events](/workflows/build/subscribe-to-instance-events/).
816+
763817
### InstanceStatus
764818

765819
Details the status of a Workflow instance.

0 commit comments

Comments
 (0)