On this page:
1.1 Requests
read-signals
datastar-request?
1.2 SSE
1.2.1 Server Setup
datastar-tcp@
1.2.2 SSE Generator
datastar-sse
sse?
close-sse
sse-closed?
1.2.3 Locking
call-with-sse-lock
with-sse-lock
1.2.4 Signals
patch-signals
remove-signals
1.2.5 Elements
patch-elements
patch-elements/  xexprs
element-patch-mode/  c
element-namespace/  c
remove-elements
1.2.6 Scripts
execute-script
redirect
console-log
console-error
replace-url
1.2.7 Compression
1.3 One-Shot Responses (Non-SSE)
response/  datastar-elements
response/  datastar-elements/  xexprs
response/  datastar-signals
response/  datastar-script

1 HTTP🔗ℹ

 (require datastar/http) package: datastar-lib

Provides Datastar backend HTTP modules for reading incoming signals and producing SSE or one-shot responses.

  • Requests (datastar/http/request) — Parse Datastar action request signals from incoming HTTP requests.

  • SSE event streams (datastar/http/sse) — text/event-stream responses for Datastar events, from single immediate updates to long-lived streams.

  • One-shot HTTP responses (datastar/http/response) — non-SSE responses with Datastar-aware headers using text/html, application/json, or text/javascript.

For protocol context, see Datastar Backend Requests and Datastar Response Handling.

1.1 Requests🔗ℹ

 (require datastar/http/request) package: datastar-lib

Read Datastar request payloads from incoming backend action calls. For request/transport details, see Datastar Backend Requests.

procedure

(read-signals request)  jsexpr?

  request : request?
Parses incoming signal data from the browser. For GET and DELETE requests, extracts data from the datastar query parameter. For other methods, parses the request body as JSON.

procedure

(datastar-request? request)  boolean?

  request : request?
Returns #t if the request has a Datastar-Request: true header, meaning it came from a Datastar action. The check is case-insensitive.

1.2 SSE🔗ℹ

 (require datastar/http/sse) package: datastar-lib

Send Datastar SSE events from backend actions. See the Datastar SSE events reference for event formats and data-line semantics.

1.2.1 Server Setup🔗ℹ

Use dispatch/servlet from web-server/servlet-dispatch to convert your servlet into a dispatcher, then choose a serve configuration that matches your stream behavior.

Decision guide

  • Short-lived SSE (send one/few events, then return): defaults usually work; these transport knobs are optional.

  • Long-lived or mostly idle streams (subscriptions, optional CQRS read-model updates): configure #:safety-limits so response timeouts do not terminate idle handlers (for example, +inf.0, or periodic chunks/heartbeats).

  • Blocked on-open loop + prompt disconnect cleanup: use both #:connection-close? #t and #:tcp@ datastar-tcp@.

Minimal setup

(serve #:dispatch (dispatch/servlet handler))

Robust setup (long-lived streams / prompt blocked-loop disconnect cleanup)

(serve #:dispatch (dispatch/servlet handler)
       #:tcp@ datastar-tcp@
       #:connection-close? #t
       #:safety-limits (make-safety-limits #:response-timeout +inf.0
                                           #:response-send-timeout +inf.0))

A tcp^ unit for serve’s #:tcp@ parameter. It enables faster disconnect detection for SSE connections.

When provided, datastar-sse monitors the underlying TCP input port and interrupts on-open when the client disconnects, so on-close can run sooner.

1.2.2 SSE Generator🔗ℹ

Create SSE responses and send events through an sse? generator.

procedure

(datastar-sse on-open #:on-close on-close)  response?

  on-open : (-> sse? any)
  on-close : (-> sse? any)
Creates an HTTP response with proper SSE headers. Calls on-open with a fresh sse? generator that can be used to send events to the client. When on-open returns (or raises an exception), the connection is closed and on-close is called if provided.

Configure serve using Server Setup: minimal setup for short-lived streams, robust setup for long-lived streams or fast disconnect handling.

procedure

(sse? v)  boolean?

  v : any/c
Returns #t if v is an SSE generator value supported by this SDK (including values produced by datastar-sse and the testing helpers in datastar/testing).

procedure

(close-sse sse)  void?

  sse : sse?
Explicitly closes the SSE connection. This is called automatically when the on-open callback returns, but can be called earlier if needed. Safe to call multiple times.

procedure

(sse-closed? sse)  boolean?

  sse : sse?
Returns #t if the SSE connection is closed, either because close-sse was called or because the underlying output port was closed (e.g., client disconnected). This is a non-destructive check that does not attempt to write to the connection.

1.2.3 Locking🔗ℹ

Coordinate concurrent senders with explicit SSE locking. All send functions are thread-safe: multiple threads can send events through the same generator and delivery order is serialized. If multiple threads share a single generator, use with-sse-lock to send a group of events without interleaving.

procedure

(call-with-sse-lock sse thunk)  any

  sse : sse?
  thunk : (-> any)
Holds the SSE generator’s lock for the duration of thunk, preventing concurrent sending of SSE events. This ensures that multiple sends are delivered as an atomic batch without events from other threads interleaving.

Use this only when multiple threads send through the same sse? generator. If each generator is used by a single thread (the common case), individual sends are already thread-safe and this locking is unnecessary.

The lock is re-entrant: all send functions (patch-elements, patch-signals, etc.) use call-with-sse-lock internally, so calling them inside a locked region does not deadlock.

(call-with-sse-lock sse
                    (lambda ()
                      (patch-elements/xexprs sse '(div ((id "a")) "part 1"))
                      (patch-elements/xexprs sse '(div ((id "b")) "part 2"))
                      (patch-signals sse (hash 'status "updated"))))

If an exception is raised inside thunk, the lock is released via dynamic-wind, so subsequent sends can still proceed.

syntax

(with-sse-lock sse body ...)

Syntax form that wraps body ... in a call to call-with-sse-lock.

(with-sse-lock sse
               (patch-elements/xexprs sse '(div ((id "a")) "part 1"))
               (patch-elements/xexprs sse '(div ((id "b")) "part 2"))
               (patch-signals sse (hash 'status "updated")))

1.2.4 Signals🔗ℹ

Send signal patch events to add, update, or remove client-side signals.

procedure

(patch-signals sse    
  signals    
  #:event-id event-id    
  [#:only-if-missing? only-if-missing?]    
  #:retry-duration retry-duration)  void?
  sse : sse?
  signals : (or/c string? jsexpr?)
  event-id : string?
  only-if-missing? : boolean? = #f
  retry-duration : exact-positive-integer?
Sends a datastar-patch-signals SSE event that patches signals into the existing signals on the page. The #:only-if-missing? option determines whether to update each signal only if a signal with that name does not yet exist.

Serializer behavior omits default-equivalent wire fields: data: onlyIfMissing ... is emitted only when #:only-if-missing? is #t, and retry: ... is emitted only when #:retry-duration is non-default.

procedure

(remove-signals sse    
  signal-path-or-paths    
  #:event-id event-id    
  #:retry-duration retry-duration)  void?
  sse : sse?
  signal-path-or-paths : (or/c string? (listof string?))
  event-id : string?
  retry-duration : exact-positive-integer?
Convenience wrapper around patch-signals for removing one or more signal paths. Builds a patch object with each provided path set to 'null, then sends a datastar-patch-signals event.

Dot notation paths are expanded to nested objects, so "user.name" becomes {"user":{"name":null}}.

1.2.5 Elements🔗ℹ

Send element patch events to morph, insert, replace, or remove DOM content.

procedure

(patch-elements sse 
  elements 
  #:selector selector 
  #:mode mode 
  #:namespace namespace 
  [#:use-view-transitions? use-view-transitions?] 
  #:event-id event-id 
  #:retry-duration retry-duration) 
  void?
  sse : sse?
  elements : (or/c string? #f)
  selector : string?
  mode : element-patch-mode/c
  namespace : element-namespace/c
  use-view-transitions? : boolean? = #f
  event-id : string?
  retry-duration : exact-positive-integer?
Sends a datastar-patch-elements SSE event that patches one or more elements in the DOM. By default, Datastar morphs elements by matching top-level elements based on their ID.

The #:mode parameter controls how elements are patched. Use symbol values; see element-patch-mode/c for allowed values.

Serializer behavior omits default-equivalent wire fields: data: mode ... is omitted for 'outer, data: namespace ... is omitted for 'html, data: useViewTransition ... is omitted for #f, and retry: ... is omitted for the default retry duration.

(patch-elements sse "<div id=\"out\">hello</div>")
(patch-elements sse "<svg>...</svg>" #:namespace 'svg)
(patch-elements sse "<li>item</li>" #:selector "#list" #:mode 'append)

procedure

(patch-elements/xexprs 
  sse 
  xexpr-or-xexprs 
  #:selector selector 
  #:mode mode 
  #:namespace namespace 
  [#:use-view-transitions? use-view-transitions?] 
  #:event-id event-id 
  #:retry-duration retry-duration) 
  void?
  sse : sse?
  xexpr-or-xexprs : (or/c xexpr/c (listof xexpr/c))
  selector : string?
  mode : element-patch-mode/c
  namespace : element-namespace/c
  use-view-transitions? : boolean? = #f
  event-id : string?
  retry-duration : exact-positive-integer?
Like patch-elements, but accepts either a single x-expression or a list of x-expressions instead of a raw HTML string. Converts each x-expression via xexpr->string, concatenates resulting HTML fragments, and delegates to patch-elements. If an empty list is provided, a datastar-patch-elements event is still emitted, but with no elements data lines.

(patch-elements/xexprs sse '(div ((id "out")) "hello"))
(patch-elements/xexprs sse '(svg "...") #:namespace 'svg)
(patch-elements/xexprs sse
                       '((li "one")
                         (li "two"))
                       #:selector "#list"
                       #:mode 'append)

Contracts used by patch-elements and patch-elements/xexprs option arguments:

Contract for valid patch modes: 'outer, 'inner, 'remove, 'replace, 'prepend, 'append, 'before, or 'after.

Contract for valid namespaces: 'html, 'svg, or 'mathml.

procedure

(remove-elements sse    
  selector    
  #:event-id event-id    
  #:retry-duration retry-duration)  void?
  sse : sse?
  selector : string?
  event-id : string?
  retry-duration : exact-positive-integer?
Removes elements from the DOM by CSS selector. Convenience function that calls patch-elements with 'remove.

1.2.6 Scripts🔗ℹ

Run browser-side scripts from SSE responses. See Datastar script execution guidance for expression semantics.

procedure

(execute-script sse    
  script    
  [#:auto-remove? auto-remove?]    
  #:attributes attributes    
  #:event-id event-id    
  #:retry-duration retry-duration)  void?
  sse : sse?
  script : string?
  auto-remove? : boolean? = #t
  attributes : (or/c (hash/c symbol? any/c) (listof string?))
  event-id : string?
  retry-duration : exact-positive-integer?
Sends a datastar-patch-elements SSE event that appends a <script> element to body. The script element is automatically removed after execution unless auto-remove? is #f.

procedure

(redirect sse location)  void?

  sse : sse?
  location : string?
Redirects the browser to a new location using window.location. This is a convenience function that calls execute-script.

procedure

(console-log sse message)  void?

  sse : sse?
  message : string?
Logs a message to the browser console via console.log. The message is automatically quoted as a JavaScript string. This is a convenience function that calls execute-script.

procedure

(console-error sse message)  void?

  sse : sse?
  message : string?
Logs a message to the browser console via console.error. This is a convenience function that calls execute-script.

procedure

(replace-url sse location)  void?

  sse : sse?
  location : string?
Updates the browser URL without navigating, using window.history.replaceState. This is a convenience function that calls execute-script.

1.2.7 Compression🔗ℹ

Compress SSE responses by wrapping your app handler with wrap-compress from web-server-compress:

(require datastar
         web-server-compress
         web-server/dispatch
         web-server/servlet-dispatch
         web-server/web-server)
 
(define (events-handler req)
  (datastar-sse (lambda (sse) ...)))
 
(define-values (app _uri) (dispatch-rules ...))
 
(serve #:dispatch (dispatch/servlet (wrap-compress app)) ...)

By default, wrap-compress negotiates with the client’s Accept-Encoding header using server-priority order: zstd first, then Brotli. If the client doesn’t support either, responses pass through uncompressed. Each SSE event is flushed to the client immediately.

To customize the encoding priority or tune per-encoding parameters:

(wrap-compress app
  #:encodings '(br zstd)
  #:brotli-quality 9
  #:zstd-level 6)

For single-encoding use, wrap-brotli-compress and wrap-zstd-compress are also available. See the web-server-compress docs for the full API.

1.3 One-Shot Responses (Non-SSE)🔗ℹ

 (require datastar/http/response) package: datastar-lib

Build Datastar-compatible non-SSE responses when a regular HTTP body is sufficient. The Tao of Datastar recommends using SSE event streams by default; see Datastar Response Handling for client-side behavior.

Non-SSE responses can still be useful when:

  • you are integrating with an existing non-SSE endpoint

  • a single immediate update is sufficient and you want a regular HTTP body/headers response

These helpers build regular HTTP responses for Datastar backend actions without opening an SSE stream.

procedure

(response/datastar-elements 
  elements 
  #:selector selector 
  #:mode mode 
  #:namespace namespace 
  [#:use-view-transitions? use-view-transitions? 
  #:status status] 
  #:headers headers) 
  response?
  elements : (or/c string? bytes?)
  selector : string?
  mode : element-patch-mode/c
  namespace : element-namespace/c
  use-view-transitions? : boolean? = #f
  status : exact-positive-integer? = 200
  headers : (hash/c string? string?)
Builds a text/html response interpreted by Datastar as an elements patch. The response body may contain one or more top-level elements.

Optional Datastar headers:
  • datastar-selector

  • datastar-mode

  • datastar-namespace

  • datastar-use-view-transition

Serializer behavior matches SSE omission defaults: datastar-mode is omitted for 'outer, datastar-namespace is omitted for 'html, and datastar-use-view-transition is omitted for #f.

procedure

(response/datastar-elements/xexprs 
  xexpr-or-xexprs 
  #:selector selector 
  #:mode mode 
  #:namespace namespace 
  [#:use-view-transitions? use-view-transitions? 
  #:status status] 
  #:headers headers) 
  response?
  xexpr-or-xexprs : (or/c xexpr/c (listof xexpr/c))
  selector : string?
  mode : element-patch-mode/c
  namespace : element-namespace/c
  use-view-transitions? : boolean? = #f
  status : exact-positive-integer? = 200
  headers : (hash/c string? string?)
Like response/datastar-elements, but takes x-expressions and renders them to HTML first.

procedure

(response/datastar-signals 
  signals 
  [#:only-if-missing? only-if-missing? 
  #:status status] 
  #:headers headers) 
  response?
  signals : (or/c string? bytes? jsexpr?)
  only-if-missing? : boolean? = #f
  status : exact-positive-integer? = 200
  headers : (hash/c string? string?)
Builds an application/json response interpreted by Datastar as a signals patch.

Serializer behavior matches SSE omission defaults: datastar-only-if-missing is emitted only when #:only-if-missing? is #t (explicit #f is omitted).

procedure

(response/datastar-script script    
  #:attributes attributes    
  [#:status status]    
  #:headers headers)  response?
  script : (or/c string? bytes?)
  attributes : (hash/c (or/c symbol? string?) any/c)
  status : exact-positive-integer? = 200
  headers : (hash/c string? string?)
Builds a text/javascript response interpreted by Datastar as executable script.

When provided, #:attributes is JSON-encoded into the datastar-script-attributes response header.