Plain HTML / Static Sites
The recorder is plain JavaScript — it doesn’t need a Ruby backend on the pages it records. Any static page (hand-written HTML, a Jekyll/Bridgetown build, a site served by Caddy or nginx) can record sessions into a Sentiero instance you host elsewhere, as long as the browser can reach Sentiero’s events endpoint.
This guide assumes Sentiero is mounted under /sentiero/ on the same origin as your pages (see Sinatra / Rack for mounting). For a different origin, add your site to config.cors_origins on the Sentiero side.
The simplest integration: one script tag
Sentiero serves the recorder bundle at a stable URL, <dashboard mount>/recorder.js, as a sibling of the events endpoint. With the standard mount that means one tag before </body> and no configuration at all:
<script src="/sentiero/recorder.js"></script>
When there is no config block, the recorder derives the events endpoint from its own URL by swapping the last path segment for events — /sentiero/recorder.js → /sentiero/events. With the standard mount (map("/sentiero/events") + map("/sentiero")), that’s exactly right.
Don’t use the hashed asset URL for this. The same derivation applied to
/sentiero/assets/recorder-XXXX.jsyields/sentiero/assets/events, which doesn’t exist. If you load the recorder from/assets/, you must provide a config block (below).
Configuring: the two-tag snippet
To set any option, add a JSON config block with the id sentiero-config. The recorder auto-boots from it — no init call needed:
<script type="application/json" id="sentiero-config">
{"eventsUrl": "/sentiero/events", "captureClicks": true}
</script>
<script src="/sentiero/recorder.js"></script>
This is exactly what the Ruby ScriptTag.render helper emits — two tags, nothing framework-specific.
eventsUrl is the only required key. When a config block is present the URL derivation described above is skipped, so eventsUrl must be set. Everything else has client-side defaults.
Configuration reference
| Key | Type | Default | What it does |
|---|---|---|---|
eventsUrl |
string | — (required) | Endpoint the recorder POSTs event batches to. |
flushIntervalMs |
number | 10000 |
Flush the buffer every N ms. |
flushEventThreshold |
number | 50 |
Flush early once N events are buffered. |
crossTabSessions |
boolean | true |
Share one session id across tabs. |
sessionIdleTimeoutMs |
number | 21600000 (6h) |
Start a new session after this much inactivity. |
sessionMaxAgeMs |
number | 604800000 (7d) |
Hard cap on a session’s age. |
recorderOptions |
object | {} |
Options passed to rrweb. Privacy defaults are merged in: maskAllInputs: true, and password masking cannot be disabled. |
redaction |
object | built-in defaults | Client-side redaction rules; matches the server’s config.redaction.to_client_hash. |
captureClicks |
boolean | false |
Record click events for heatmaps/frustration analytics. |
trackNavigation |
boolean | false |
Record page navigations. |
trackForms |
boolean | false |
Record form submissions (redacted). |
captureErrors |
boolean | false |
Capture JS errors into the session timeline. |
captureWebVitals |
boolean | false |
Capture Core Web Vitals. |
captureMetadata |
boolean | false |
Send page metadata (URL, referrer, viewport) with batches. |
trackCustomEvents |
boolean | false |
Track events declared via data-sentiero-track-* element attributes. |
optOutCookieName |
string | unset | Cookie name checked for user opt-out. |
respectGpc |
boolean | false |
Don’t even start recording for visitors sending the Global Privacy Control signal. Note the server honors GPC by default regardless (config.respect_gpc), dropping their batches at ingest — this key just avoids the wasted requests. |
The booleans mirror the Ruby Sentiero.configure flags (capture_clicks, track_navigation, …) — the Ruby helper just serializes them into this same JSON block.
Alternative: resolving the hashed bundle from the manifest
The /recorder.js alias is served with a short cache (5 minutes) so upgrades roll out quickly. If you’d rather serve the immutably-cached, content-hashed bundle (/sentiero/assets/recorder-XXXX.js), don’t hardcode the hash — it changes on every build. manifest.json is public and maps logical names to hashed filenames, so a tiny loader stays correct across upgrades:
<script type="application/json" id="sentiero-config">{"eventsUrl": "/sentiero/events"}</script>
<script>
fetch("/sentiero/assets/manifest.json")
.then(function (r) { return r.json(); })
.then(function (m) {
var s = document.createElement("script");
s.src = "/sentiero/assets/" + m.recorder;
s.async = true;
document.body.appendChild(s);
})
.catch(function () {});
</script>
Note the config block is mandatory here (the /assets/ URL breaks the sibling derivation).