Think of <Timer> as a durable sleep. When the scheduler reaches this node, the workflow suspends — no polling, no busy-waiting. The Temporal runtime checkpoints the delay and resumes execution once the wall-clock condition is satisfied, even if the worker restarts in the meantime.
Import
import { Timer } from "smithers-orchestrator";
Props
| Prop | Type | Default | Description |
|---|
id | string | (required) | Unique node id within the workflow. |
duration | string | undefined | Relative delay as a human-readable string (e.g. "500ms", "30s", "2h", "7d"). Exactly one of duration or until is required. |
until | string | Date | undefined | Absolute fire time as an ISO 8601 timestamp string or a Date object. Exactly one of duration or until is required. |
skipIf | boolean | false | Skip this node entirely. The node resolves immediately with no delay. |
dependsOn | string[] | undefined | Task IDs that must complete before the timer starts. |
needs | Record<string, string> | undefined | Named deps. Keys become context keys, values are task IDs. |
label | string | timer:<id> | Display label override. |
meta | Record<string, unknown> | undefined | Extra metadata attached to the node record. |
Exactly one of duration or until must be provided. Providing both, or neither, throws at render time.
The every prop (recurring timer) is reserved for a future phase and is not supported. Passing it throws at render time.
Duration strings
The duration prop accepts a concise human-readable format.
| String | Meaning |
|---|
"500ms" | 500 milliseconds |
"1s" / "30s" | 1 second / 30 seconds |
"5m" / "30m" | 5 minutes / 30 minutes |
"1h" / "2h" | 1 hour / 2 hours |
"1d" / "7d" | 1 day / 7 days |
Relative delay
Pause for 30 seconds before proceeding to the next step:
import { Sequence, Task, Timer, Workflow, createSmithers } from "smithers-orchestrator";
import { z } from "zod";
const { smithers, outputs } = createSmithers({
report: z.object({ summary: z.string() }),
});
export default smithers(() => (
<Workflow name="delayed-report">
<Sequence>
<Timer id="cooldown" duration="30s" />
<Task id="report" output={outputs.report} agent={reportAgent}>
Generate the daily summary report.
</Task>
</Sequence>
</Workflow>
));
Absolute timestamp
Use until when the target time is computed at runtime — for example, a deadline stored in the workflow input:
export default smithers((ctx) => (
<Workflow name="scheduled-reminder">
<Sequence>
<Timer id="wait-until-deadline" until={ctx.input.reminderAt} />
<Task id="send-reminder" output={outputs.notification} agent={notifierAgent}>
Send the reminder to the user.
</Task>
</Sequence>
</Workflow>
));
ctx.input.reminderAt can be an ISO string ("2026-06-01T09:00:00Z") or a Date object — both are accepted. If the timestamp is already in the past when the node is reached, the timer fires immediately.
Timer inside a loop
Derive a duration from context at render time to implement a simple backoff:
export default smithers((ctx) => {
const delay = ctx.iteration === 0 ? "5m" : "30m";
return (
<Workflow name="retry-with-backoff">
<Loop
until={ctx.outputMaybe(outputs.result)?.success === true}
maxIterations={5}
>
<Sequence>
<Timer id="backoff" duration={delay} />
<Task id="attempt" output={outputs.result} agent={workerAgent}>
Attempt the operation.
</Task>
</Sequence>
</Loop>
</Workflow>
);
});
Behavior
- When the scheduler reaches a
<Timer> node, it enters waiting-timer status.
- The engine records the timer target — a resolved UTC timestamp — durably in the workflow history.
- The worker thread releases the execution slot. No resources are held during the wait.
- When the target time arrives, Temporal wakes the workflow and the node transitions to
completed. Downstream nodes are then eligible to run.
- If
skipIf is true, the node resolves immediately without any delay.
- Worker restarts or redeployments during the wait do not reset the timer — the checkpoint is stored in the Temporal event history.
- Timers in separate parallel branches wait independently.
Rendering
<Timer> renders as a smithers:timer host element. The scheduler treats it as a leaf node that blocks the sequence until the timer fires.
Notes
<Timer> produces no output. It has no output prop. It is a pure synchronization point.
- Use
dependsOn or needs when the timer should start only after specific upstream tasks, rather than relying on sequence position alone.
- For event-driven delays (wait for an external signal rather than a fixed time), use
<WaitForEvent> instead.
- Timers inside a
<Loop> body reset each iteration because each iteration is a fresh render of the tree.