Timing Nodes¶
Nodes that pause the workflow for a duration or until a point in time, resuming automatically. These require the flowdrop_interrupt module and a pipeline-backed orchestrator (e.g. StateGraph) — under the stateless synchronous orchestrator there is no pipeline to resume, so timing nodes cannot be used there.
How resume works: a timing node persists a pending Timer interrupt carrying the scheduled resume time and pauses the pipeline. On each cron run, due timers are resolved automatically and the pipeline continues. Precision is therefore cron-bound: a "5 minute" delay resumes on the first cron run at least 5 minutes after the pause. Sites that need tighter timing should run cron every minute.
Skipping the wait: a pending timer appears in the interrupt inbox as a "Waiting until …" card with a Resume now button. Resolving it there (or through the resume API) skips the remaining wait immediately — the node's outputs report who skipped and by how much.
The maximum accepted delay is governed by the timer_max_delay setting in flowdrop_interrupt.settings (default: 1 year; 0 for unlimited).
Sequencing downstream nodes
To run a node after the wait without passing data, connect the timing node's trigger output to the downstream node's trigger input. Wiring a data output (e.g. resumed_at) into a trigger input is interpreted as a gateway branch condition and the downstream node will be skipped.
Delay¶
Pauses the workflow for a relative duration ("wait 2 hours").
Category: Timing
Configuration¶
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| value | Number | Yes | 5 |
How long to wait |
| unit | String | No | minutes |
One of seconds, minutes, hours, days |
A zero delay passes straight through without pausing.
Output Ports¶
| Port | Type | Description |
|---|---|---|
| scheduled_at | Number | Unix timestamp the workflow was scheduled to resume at |
| resumed_at | Number | Unix timestamp the workflow actually resumed at |
| drift_seconds | Number | Seconds between scheduled and actual resume (negative when skipped early) |
| resolved_by_timer | Boolean | true when the timer fired; false when a user skipped the wait |
| user_id | Number | ID of the user who skipped the wait (empty when the timer fired) |
Schedule¶
Pauses the workflow until a specific date and time ("resume at 2026-06-10 09:00").
Category: Timing
Configuration¶
| Parameter | Type | Required | Description |
|---|---|---|---|
| when | String | Yes | When to resume: ISO 8601 datetime (2026-06-10T09:00:00+02:00), unix timestamp, or a natural phrase like tomorrow 09:00. Numeric values below 1000000000 (bare years, IDs) are rejected rather than misread as 1970-era timestamps |
The when input is connectable — an upstream node (e.g. a content entity's publish_at field) can feed it. A time already in the past passes straight through without pausing.
Timezones
A datetime without an explicit offset (2026-06-10 09:00) is resolved in the timezone of the user who launched the run, since Drupal sets the PHP timezone per user. For deterministic fire times, use ISO 8601 with an offset (2026-06-10T09:00:00+02:00) or a unix timestamp.
Output Ports¶
Same as Delay.
Example: scheduled publishing¶
Load Entity → Schedule (when ← publish_at field) → Publish Entity
The workflow pauses after loading the entity, resumes on the first cron run after the scheduled publish time, and then publishes.
Rate Limit¶
Limits how often execution passes through the node. Runs over the limit pause and retry automatically until a slot frees up.
Unlike Delay and Schedule, the limit is shared across runs: the counter is backed by Drupal's flood service, so all pipelines passing through the same bucket collectively stay under the limit. Use it to be polite to external APIs ("at most 100 calls per hour to this vendor") or to pace bulk operations.
Category: Timing
Configuration¶
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| limit | Number | Yes | 10 |
Maximum passes per window |
| window | Number | Yes | 3600 |
Window length in seconds |
| identifier | String | No | — | Bucket key. Empty = a private bucket per node instance. Set the same key on several nodes (or connect a per-user/per-domain value) to share or partition the limit |
Output Ports¶
The same audit trail as Delay, plus:
| Port | Type | Description |
|---|---|---|
| throttled | Boolean | true when the run had to wait for a slot, false when it passed straight through |
Behaviour notes¶
- A blocked run pauses on a timer paced at
window ÷ limitseconds. On resume it re-checks the bucket — other runs may have taken the freed slot — and re-arms a fresh timer if still over the limit. - Resolving the timer early from the inbox does not bypass the limit; it only triggers an early re-check.
- The limit is best-effort, not a hard guarantee: the check and the count are not atomic, so runs racing the same bucket in the same instant can briefly exceed it. Right for being polite to external APIs; wrong for billing enforcement.
- Long or non-ASCII identifiers are hashed internally (same input → same bucket), so connecting URLs or emails is safe.
- Precision is cron-bound: suitable for per-minute/per-hour limits, not per-second throttling. For sub-minute rates, throttle inside the node making the calls.