Skip to content

@poll directive

@poll dispatches an event handler on an interval. It is a PyWire kernel primitive and works on both stateful and stateless tiers.

<button @poll.every-500={refresh_job} $if={running}>
Refresh every 500 ms
</button>

Use only one @poll attribute per element. The first interval is 1000 ms by default. .every-<int> sets the interval in milliseconds, with a minimum of 100; non-integer values or intervals below 100 are compile errors. The client-side default fallback only applies to hand-authored DOM attributes. On FaaS and edge runtimes, remember that a 100 ms floor can still create 10 requests per second per polled element, so choose the longest interval that still meets the product’s freshness requirement.

The compiled attributes are data-pw-poll and data-pw-poll-every. Every tick uses the normal event dispatch path, including unwrapped argument lifting for expressions such as @poll={refresh(item.id)}.

  • Timers are rescanned after every morph, navigation, and response.
  • Conditional render is the stop rule: remove the polled element when the work is terminal.
  • There is no .while condition in v1.
  • A per-element in-flight flag skips ticks while that element’s request is pending.
  • The guard is best effort: any response clears in-flight flags globally. An empty update only clears those flags; it does not trigger a poll rescan.
---
job_id = wire(None)
status = wire("queued")
---
<button @click={start_job}>Start</button>
<div $if={job_id.value is not None}>
<progress $show={status.value not in ("done", "failed")}
@poll.every-1000={refresh_job}></progress>
<span>{status}</span>
</div>

start_job durably enqueues work; refresh_job reads the job store. create_task() is valid on stateless when it does not attempt server push. See the long-running actions guide for Cloudflare D1 + Queues, Firestore + Cloud Tasks, and local PostgreSQL patterns.