Change SDK behavior without redeploying
The RUM SDK does not carry its capture rules in your bundle. It fetches them from Site Qwality’s CDN on every page load, and again when a tab comes back after 5 minutes, so you can change what gets captured without shipping a new build.
What is served
Section titled “What is served”The SDK reads a static file from the CDN, with no client token, no cookies and no preflight:
GET https://cdn.siteqwality.com/rum/config/v2/<application_id>.json{ "v": 2, "application_id": "<uuid>", "revision": 42, "status": "active", "observe": { "sample_rate": 1 }, "rules": [ { "id": "<uuid>", "capture": "replay", "sample_rate": 1, "conditions": [{ "kind": "error" }], "min_duration_ms": 0, "require_interaction": false } ], "privacy": { "level": "balanced", "mask_inputs": true, "mask_text": false, "hide_action_text": false, "pii_patterns": ["email", "card", "digits9"], "honor_gpc": true }, "capture": { "resource_exclusions": ["/api/heartbeat", "https://analytics.example.com"], "frustration_ignore_selectors": [".carousel-next"] }, "limits": { "idle_pause_ms": 300000, "session_max_ms": 14400000, "dnr": false }}The example leaves out some keys; the schema is SdkConfigV2 in the API reference. Rules carry the rate in force, and privacy settings arrive with the privacy level already applied. A deleted application’s file becomes {"v":2,"status":"paused"}, and the SDK then sends nothing.
What you can change remotely
Section titled “What you can change remotely”| Change | Effect |
|---|---|
| Add, edit or disable a recording rule | Which sessions record Analyze detail and replay |
| Set the privacy level and masking | How replays and click names are masked, and which personal data patterns are scrubbed |
| Edit excluded resources | Which resource timings are not recorded |
| Edit frustration ignore selectors | Which elements never get rage or dead clicks |
| Edit allowed origins | Which pages may send data |
| Edit network capture | Which request headers and bodies the SDK records, and which requests carry traceparent |
Flush intervals, batch sizes and session timeouts are fixed in the SDK. Endpoint hosts can only be set locally in init(). To stop detail and replay for an application, disable its rules; to stop everything, remove the init() call.
Where you set it
Section titled “Where you set it”In the dashboard, RUM → Settings holds the settings on this page, and RUM → Recording rules the recording rules. Through the API:
PATCH /rum/{app_id}withsettingsholding only the keys you change. Nested objects merge key by key, arrays and single values replace, andnullclears a nullable key such asprivacy.level. Unknown keys are refused, so a typo cannot pass as a save. The same request takesallowed_originsandreplay_monthly_target.PUT /rum/{app_id}withsettingsmerges the same way but ignores unknown keys.- Recording rules: the
/rum/{app_id}/rulesendpoints.
Changing privacy, retention, capture.network.body_urls or capture.network.header_allowlist needs an account admin, or an API key with no scope restriction. Any other change needs write access.
Every change is published before the request returns; if it cannot be published, the request fails and nothing is changed. Changes are recorded in the audit log.
A new application starts on the Balanced privacy level, honors Global Privacy Control, and has the default recording rules.
How long changes take to land
Section titled “How long changes take to land”The CDN caches the file for at most 1 minute and the browser for at most 5, so a change reaches new page loads within 6 minutes. An open tab fetches the config again when it becomes visible with a copy older than 5 minutes; a tab that stays visible keeps its config until its next page load.
Activation is one-way within a session. Once Analyze or replay turns on for a session, it stays on for the rest of that session, in every tab and across page loads. So a rule change that stops capture applies to new sessions only, while a new rule can activate a session in progress once a tab picks up the config. Privacy and capture changes do reach a running recorder: it restarts from a fresh snapshot under the new settings, and compaction applies the current privacy settings again on the server.
Privacy levels
Section titled “Privacy levels”settings.privacy.level is strict, balanced or relaxed. With no level (an older application, shown as Custom), the individual flags apply as stored.
| Strict | Balanced | Relaxed | Custom | |
|---|---|---|---|---|
| Input values | Masked | Masked | Masked | mask_inputs |
| Page text | Masked | Visible | Visible | mask_text |
| Click names | Selector only | Text | Text | hide_action_text |
| Emails, card numbers, runs of 9+ digits | Scrubbed | Scrubbed | Kept | Kept |
| Images and video | Blocked | Recorded | Recorded | Recorded |
| Console levels | error | error, warn | as configured | error, warn |
A level overrides the flags it governs, and saving a level writes them. A flag can still add privacy on top of a level: hide_action_text on a Balanced application hides click text. pii_patterns is written from the level. Ingest scrubs those patterns from free text and applies hide_action_text as well.
The SDK also reads mask_selectors, unmask_selectors (Strict only), block_selectors, ignore_input_selectors, never_record_urls, require_consent, honor_gpc, cookieless and capture_user_email. Selector lists hold up to 50 CSS selectors each, which must be valid CSS. Pseudo-elements, namespaces and unknown pseudo-classes are refused, as are * alone in a mask or block list and a selector ending in * in the unmask list.
Excluded resources
Section titled “Excluded resources”List requests you do not want recorded as resource events, such as polling endpoints or analytics beacons. Only that application is affected.
A rule takes one of two forms:
| Form | Example | Excludes |
|---|---|---|
Path, starting with / | /api/ | That path and everything under it on the page’s own origin |
Absolute, starting with http:// or https:// | https://analytics.example.com | Everything on that origin, or under the given path on it |
How a resource URL is matched:
- The origin must match: scheme, host and port. Host case is ignored and default ports are normalized, so
https://API.example:443/v1matcheshttps://api.example/v1/x. - The path must equal the rule’s path or continue it after a
/./api/pollmatches/api/poll,/api/poll?t=1and/api/poll/2, but not/api/polls. A trailing slash on the rule is ignored, and/excludes the whole origin. - Query strings and fragments are ignored.
Each application takes up to 50 rules of up to 512 characters each. A rule may not contain ?, # or whitespace. Wildcards are not supported, so list each host separately.
The SDK skips matching resource timings, so they are never sent. Within a minute of saving, ingest also drops matching resource events and successful fetch and XHR requests to a matching URL. A failed request to a matching URL is still kept.
Network capture
Section titled “Network capture”settings.capture.network controls what the SDK records for each request. Method, status, timing and sizes are always recorded.
| Key | Default | Description |
|---|---|---|
header_allowlist | ["content-type", "x-request-id"] | Lower-case header names recorded on requests and responses. Names that can carry credentials (authorization, cookie, set-cookie, proxy-authorization, or containing token, secret, key or auth) are refused. |
body_urls | [] | URL prefixes whose JSON or text bodies are recorded, up to max_body_bytes. Ingest keeps bodies only for these URLs and scrubs sensitive keys. |
trace_urls | [] | URL prefixes whose requests get a traceparent header, linking them to your traces. |
max_body_bytes | 10240 | 1 to 65,536. |
body_urls and trace_urls are URL prefixes, never patterns, so they cannot reach a third party by accident. Each entry is a path starting with /, which matches only requests to the page’s own origin, or an http:// or https:// URL with an optional path. They match like excluded resources: same origin, and the path equal to the prefix or continuing it after a /. Entries that read as a regular expression (/api/, /(orders|carts)), bare words (api.example.com) and wildcards are refused; write /api or https://api.example.com/v1.
Allowed origins
Section titled “Allowed origins”allowed_origins lists the pages that may send an application’s data. Set it with PATCH /rum/{app_id}; an empty list allows every origin.
| Entry | Allows |
|---|---|
https://shop.example.com | That origin, on the default port |
https://*.example.com | Any subdomain of example.com, not example.com itself |
example.com | example.com over http and https |
http://localhost:3000, https://shop.example.com:* | That port, or any port |
Entries are lower-cased and deduplicated, up to 100. A path, query, username or a wildcard anywhere but the first label is refused.
When the list is not empty, ingest refuses requests from other origins with 403, and requests without an Origin header too unless settings.allow_requests_without_origin is on (for server-side senders).
Hiding element text in click names
Section titled “Hiding element text in click names”Auto-captured clicks are recorded with a name, taken from the clicked element’s data-sq-action-name, aria-label or text (up to 64 characters), and a selector such as button.btn.primary. On pages that render what users wrote, the name can be personal data.
Turn on Hide action text under RUM → Settings → Privacy (or set settings.privacy.hide_action_text to true) and clicks are stored with their selector only:
| Sent | Stored |
|---|---|
name Send message, selector button.btn.primary | selector button.btn.primary |
name Home, selector nav > a.nav | selector nav > a.nav |
Ingest stops storing names within a minute of saving, whatever the browser sends, and once a tab picks up the new config the SDK sends the data-sq-action-name value or the selector as the name instead. Clicks already stored keep their names. Strict turns this on.
To give an element a stable, readable name, add data-sq-action-name. It applies to clicks on the element and anything inside it:
<button class="btn primary" data-sq-action-name="Send message">Send</button>The value is trimmed, cut to 64 characters, and its square brackets become parentheses. Keep personal data out of it. With hiding on, no name is stored, so the selector identifies the click: it uses the element’s id, or else a data-testid or data-sq-* attribute, when the element has one.
Names passed to addAction() are never changed by this setting.
Ignoring elements for frustration signals
Section titled “Ignoring elements for frustration signals”Some controls draw rapid clicks by design (a carousel arrow, a stepper) or legitimately change nothing on screen. To stop their clicks being marked as rage or dead clicks, list CSS selectors under RUM → Settings → Frustration, one per line, or set settings.frustration_ignore_selectors:
"frustration_ignore_selectors": [".carousel-next", "[data-role=stepper] button"]A click on an element matching any selector, or inside one, gets no rage or dead click. A click followed by an error is still an error click. Up to 50 selectors of 1 to 256 printable characters each, which must be valid CSS: pseudo-elements, namespaces and unknown pseudo-classes are refused. The SDK picks up changes with its config; see how long changes take.
To exclude an element in your markup instead, add the data-sq-no-frustration attribute to it or an ancestor. See frustration signals for how each signal is defined.