Executes JavaScript on the client via SSE.
Appends a <script> tag to the body using datastar-patch-elements.
conn |> execute("alert('Hello!')")
conn |> execute("console.log('debug')", auto_remove: false)
Summary
Functions
Logs a message to the browser console via SSE.
Executes JavaScript on the client by appending a script tag to the body.
Redirects the client to the given URL via JavaScript.
Functions
@spec console_log(Plug.Conn.t(), term(), keyword()) :: Plug.Conn.t()
Logs a message to the browser console via SSE.
Options
:level- Console method::log,:warn,:error,:info,:debug(default::log). Anything else raises — a mistyped level used to fall back to:logsilently, which hid the typo rather than the message.- Plus all options from
execute/3
Examples
conn |> console_log("Debug message")
conn |> console_log("Warning!", level: :warn)
conn |> console_log(%{user: "alice"}, level: :info)
@spec execute(Plug.Conn.t(), String.t(), keyword()) :: Plug.Conn.t()
Executes JavaScript on the client by appending a script tag to the body.
Options
:auto_remove- Remove script tag after execution (default: true):attributes- Map of additional script tag attributes:event_id- Event ID for client tracking:retry- Retry duration in milliseconds
Examples
# Simple script execution
conn |> execute("alert('Hello!')")
# Keep script in DOM
conn |> execute("window.myVar = 42", auto_remove: false)
# ES module script
conn |> execute("import {...} from 'module'", attributes: %{type: "module"})
@spec redirect(Plug.Conn.t(), String.t(), keyword()) :: Plug.Conn.t()
Redirects the client to the given URL via JavaScript.
Uses setTimeout so the browser records a history entry.
Destination policy
Default: same-origin, path-absolute destinations only. Query-only and
fragment-only references (?x=1, #frag) are also allowed — they stay
on the current path. Validation uses parsed URL components (via URI),
not a case-sensitive prefix check, and raises ArgumentError before
any SSE patch is emitted.
Allowed without opt-in:
conn |> redirect("/workspaces")
conn |> redirect("/search?q=1#results")
conn |> redirect("?next=1")
conn |> redirect("#section")
# same-origin absolute URL (scheme/host/port match the request)
conn |> redirect("http://www.example.com/path")Rejected:
javascript:,data:,vbscript:(any case)- those schemes hidden behind leading, control, or Unicode whitespace
(
" DATA:…","JavaScript:…","java\tscript:…") - protocol-relative URLs (
//evil.example, including tab/LF/CR smuggled into a path so browsers parse/ /evilas//evil) - URLs with userinfo (
https://trusted.example@evil.example/) - off-origin
http/https(includinghttps://example.com)
Off-origin http/https needs an explicit opt-in — either
external: true or a host allowlist. Hosts are matched exactly
(case-insensitive) against the parsed host. Dangerous schemes,
protocol-relative URLs, and userinfo are still rejected.
conn |> redirect("https://ok.example/docs", external: true)
conn |> redirect("https://ok.example/docs", allow: ["ok.example"])Jason.encode!/1 (plus the </script neutralizer in execute/3)
prevents the URL from breaking out of the generated JavaScript string.
It does not make the destination itself safe — do not pass an
untrusted return_to (or similar) without deciding the policy. For
raw JavaScript, use execute/3; that remains a trusted-code API.
Options
:external- whentrue, allow anyhttp/httpsURL:allow- list of host strings permitted forhttp/https- plus all options from
execute/3