Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.getzenstep.com/llms.txt

Use this file to discover all available pages before exploring further.

Overview

Zenstep uses a queue pattern (similar to the GA4 dataLayer) to handle calls made before the snippet bundle has evaluated. This means you can safely call window.zenstep.identify() and window.zenstep.track() anywhere in your page — even in <head> scripts that run before <body>.

How it works

When the snippet’s <script> tag is added to the page, the browser begins loading snippet.js asynchronously. In the meantime, the snippet installs a lightweight stub on window.zenstep:
// This stub is installed synchronously by the snippet script tag
window.zenstep = {
  identify: (...args) => {
    _queue.push(["identify", args]);
  },
  track: (...args) => {
    _queue.push(["track", args]);
  },
  _q: [],
};
When snippet.js finishes loading, it:
  1. Reads window.zenstep._q
  2. Replays each queued call with the real implementation
  3. Replaces the stub with the full API

Why this matters

You do not need to wait for the snippet to load before calling identify(). Call it wherever the user data is available — in a layout component, on DOMContentLoaded, or even inline in <head>:
<head>
  <script>
    // Runs before the snippet loads — safe
    window.zenstep = window.zenstep || { _q: [] };
    window.zenstep.identify =
      window.zenstep.identify ||
      function (...a) {
        window.zenstep._q.push(["identify", a]);
      };
    window.zenstep.identify("user_123", { plan: "grow" });
  </script>
</head>
<body>
  ...
  <script
    async
    src="https://cdn.getzenstep.com/v1/snippet.js"
    data-zenstep="KEY"
  ></script>
</body>

Queue inspection

You can inspect the queue at any time via window.zenstep._q. Each entry is a tuple of [methodName, argsArray]:
console.log(window.zenstep._q);
// [["identify", ["user_123", { plan: "grow" }]], ["track", ["feature_used"]]]
After the snippet initialises, _q is an empty array (all calls have been replayed).

Order guarantee

Calls are replayed in FIFO order. If you call identify() before track(), the queue processes them in that order. This matters because track() uses the identity set by the most recent identify() call.

Edge cases

Snippet fails to load — if the CDN is unreachable, snippet.js never evaluates. Calls remain in _q but are never replayed. Your page continues to function normally — Zenstep fails silently. Multiple identify() calls — if you call identify() twice before the snippet loads, both are queued. The second call merges attributes into the first (Zenstep does not replace the full identity object on re-identification).