JavaScript SDK

One script tag. No backend integration, no build step, no framework adapter. Everything below is what the bundle actually does — including what it refuses to do.

v1.0.0 14.8 KB gzipped rrweb 2.2.0 No dependencies

Install

Paste one tag before </head>. There is no server-side SDK and nothing to call from your own code — tracking starts on its own.

layout.html
<script
  src="http://localhost/cliksora/sdk/v1/cliksora.min.js"
  data-project="YOUR_PROJECT_PUBLIC_KEY"
  data-host="http://localhost/cliksora"
  async></script>

rrweb is not in that 14.8 KB. The recorder is fetched separately and only when session recording is switched on for the project, so a project with recording off never pays its ~78 KB.

Loading through a tag manager

Calls made before the bundle arrives are not lost. Define a stub that pushes into ClikSora.q and the SDK adopts the queue on load, replaying it in order.

window.ClikSora = window.ClikSora || { q: [] };
ClikSora.q.push(['identify', 'USER_1001']);

Script attributes

Attribute Required Purpose
data-project Yes The project's public key. Shown on the install page for each website you add.
data-host No Collector origin. Defaults to the origin that served the SDK, so a self-hosted deployment needs nothing extra.
data-debug No Set to "true" to log the SDK's decisions to the browser console — why it did or did not start recording, in particular.

Collected automatically

On load the SDK resolves the project, fetches its configuration, verifies the domain, establishes visitor and session identity, evaluates consent, and only then starts collecting. A project it cannot reach collects nothing — the defaults are off, and the remote configuration is what turns features on.

Page views

Including SPA route changes, with duplicate suppression.

Clicks

On interactive elements, with a stable selector, coordinates and viewport.

Scroll depth

Milestones at 25 / 50 / 75 / 90 / 100%, plus a per-page summary.

Form engagement

Start, submit, abandonment and per-field interaction counts — never field values.

JavaScript errors

Thrown errors, unhandled rejections and failed resource loads.

Page performance

TTFB, FCP, LCP, CLS and DOM timings.

Session recording

Via rrweb, when the project enables it.

User journeys

Page-to-page paths, derived from the above rather than separately tracked.

JavaScript API

Every method is safe to call before the SDK finishes initialising — calls are queued and replayed in order, up to 200 of them. An identify() in your page head runs before our configuration request returns, and dropping it would silently break identified-user recording.

ClikSora.identify(userId, properties?)

Attaches your own user id to the visitor. Optional — everything else works without it. Back-fills the id onto the visitor's existing sessions, so a session that began anonymously is findable by the user it turned out to be. If the project records identified users only, this is what makes recording start.

ClikSora.identify('USER_1001', { role: 'customer', plan: 'premium' });
ClikSora.setUserProperties(properties)

Additive — setting one property does not clear the others. Scalars only: at most 50 keys, values up to 255 characters. Nested objects are dropped rather than flattened.

ClikSora.setUserProperties({ plan: 'growth', seats: 12 });
ClikSora.track(name, properties?)

Records a custom event. It goes through the normal event batch, so custom events share batching, retry and offline recovery rather than each costing a request.

ClikSora.track('purchase_completed', { orderId: 'ORD_1001', amount: 1500 });
ClikSora.startRecording() / ClikSora.stopRecording()

Start or stop rrweb recording explicitly. startRecording() can override sampling and a disabled project default, but cannot override consent, a visitor opt-out or a browser privacy signal — it refuses and logs the reason.

ClikSora.startRecording();
ClikSora.reset()

Clears visitor id, session and identity, then starts a fresh session. Call it at logout, so the next person on a shared device is not recorded as the previous one.

ClikSora.reset(); // on logout
ClikSora.endSession()

Flushes everything buffered, stops recording and closes the session.

ClikSora.endSession();
ClikSora.optOut() / ClikSora.optIn()

Visitor-level suppression, persisted across sessions. optOut() stops recording immediately and is honoured by the collector regardless of project settings.

ClikSora.optOut();
ClikSora.grantConsent() / ClikSora.revokeConsent()

For projects configured with consent required. Nothing is collected until grantConsent() is called.

ClikSora.grantConsent();
ClikSora.shutdown()

Stops every tracker and the recorder and detaches the lifecycle listeners. Unlike endSession() it does not flush or close the session — it is for tearing the SDK down, such as before a hard teardown of a single-page app.

ClikSora.shutdown();
ClikSora.init(options?)

Boot the SDK by hand when you are not using the data attributes. Idempotent: concurrent or repeated calls share one attempt, which matters because a careless install or a tag manager can include the snippet twice. Resolves to true when collection started.

await ClikSora.init({ projectKey: 'YOUR_KEY', debug: true });
ClikSora.getState()

A diagnostic snapshot, and the quickest way to answer "is it working". The dashboard's installation verifier reads the same object.

ClikSora.getState();
// {
//   initialised:       true,
//   project_id:        '…',
//   visitor_id:        '…',
//   session_id:        '…',
//   user_id:           'USER_1001',
//   recording:         true,
//   recording_allowed: true,
//   pending_events:    0,
//   segments_uploaded: 3,
//   version:           '1.0.0'
// }

ClikSora.version returns the bundle version on its own.

Privacy markup

Masking happens in the browser, before transmission. Masked values never exist in the event stream — not merely in what is sent.

Enforced regardless of project settings

  • Every input, textarea and select value is masked by default.
  • Password, hidden, cc-*, current-password, new-password and one-time-code fields are blocked outright — they never enter the recording at all.
  • Stripe and Razorpay payment iframes are blocked by name.
  • URL fragments are stripped unconditionally: SPAs and OAuth implicit flows both put tokens there.
  • Sensitive query parameters are replaced with [redacted].
  • Emails and runs of 9 or more digits are redacted from click labels and error messages.
  • mailto: and tel: hrefs are reduced to their scheme.
  • navigator.doNotTrack and globalPrivacyControl are respected.

Markers you can add to your own pages

Marker Effect
class="cliksora-block" The subtree is never recorded.
class="cliksora-mask" Text is replaced with a placeholder.
class="cliksora-ignore" Recorded, but produces no interaction events.
data-cliksora-id="checkout-cta" A stable selector for analytics, immune to CSS refactors.
data-cliksora-track Marks a non-standard element as click-worthy.

cliksora-block and cliksora-mask also work as data-cliksora-block and data-cliksora-mask, for templates where adding a class is awkward. Privacy always wins over recording permission: a visitor who has opted out cannot be recorded by any combination of project settings.

Frameworks

There is no adapter to install for any of these. SPA route changes are detected by patching the History API, so page views and user journeys are correct without router integration.

Plain HTML / PHP

Paste the tag into your layout or every page's head.

Laravel

Into the head of your root Blade layout.

WordPress

Into the theme header, or any header-script plugin.

React / Next.js

Into the document head — app/layout.tsx or pages/_document.tsx.

Vue / Nuxt

Into index.html, or app.head in nuxt.config.

Angular

Into the head of src/index.html.

Reliability

A tracking script must never break the host page. Every listener is wrapped, rrweb failures are swallowed and logged, and one broken tracker does not stop the others.

  • Events leave in bounded batches, not one request per interaction.
  • Bodies are gzipped where the browser supports CompressionStream.
  • Retries use exponential backoff with full jitter, so a recovering collector is not hit by every browser at once.
  • Undeliverable batches are persisted to localStorage and replayed on the next page load, bounded to 20 entries and 6 hours.
  • The unload path uses sendBeacon with a fetch(keepalive) fallback, and writes to the outbox first so neither surviving still loses the batch.
  • 4xx responses other than 408 and 429 are never retried.
  • Mouse movement is sampled at 50 ms and scroll at 150 ms — imperceptible on playback, materially smaller on the wire.

Verify an install

The install page for each website waits for your first event and confirms it live. To check from the browser console instead:

ClikSora.getState().initialised   // true once collection started
ClikSora.getState().recording     // true when rrweb is running

ClikSora is undefined

The tag did not load. Check the src URL resolves and that no content blocker or CSP rule is dropping it.

initialised is false

The project key was rejected, or the page's domain is not on the project's allow-list. Add data-debug="true" and the console says which.

recording is false but initialised is true

Recording is off for the project, the session fell outside the sampling rate, consent has not been granted, or the visitor has opted out. The debug log names the reason.

Events appear but no recording

Expected on a project with recording disabled — analytics and replay are separate switches.

Ready to install it?

Add a website, paste the tag, and watch the install page confirm your first event as it arrives.

Create a free account