> ## 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.

# Plain JavaScript

> Install Zenstep without a framework — vanilla JS, jQuery, or any non-framework setup.

## Basic installation

Add the `<script>` tag before `</body>` on every page:

```html theme={null}
<script
  async
  src="https://cdn.getzenstep.com/v1/snippet.js"
  data-zenstep="YOUR_SNIPPET_KEY"
></script>
```

***

## Calling identify

Because the snippet loads asynchronously, call `identify()` with a guard:

```javascript theme={null}
// Safe to call at any point — queued if snippet hasn't loaded yet
if (window.zenstep) {
  window.zenstep.identify("user_123", {
    email: "ada@example.com",
    plan: "grow",
    role: "admin",
  });
} else {
  // Pre-init queue — Zenstep replays these on load
  window.zenstep = window.zenstep || { _q: [] };
  window.zenstep._q.push(["identify", ["user_123", { plan: "grow" }]]);
}
```

In practice, you can call `window.zenstep.identify()` directly — the queue captures it automatically before the snippet has loaded:

```javascript theme={null}
// This works even before the snippet has loaded
window.zenstep = window.zenstep || { _q: [] };
window.zenstep.identify =
  window.zenstep.identify ||
  function (...args) {
    window.zenstep._q.push(["identify", args]);
  };

window.zenstep.identify("user_123", { plan: "grow" });
```

***

## jQuery example

If you're using jQuery and identifying the user on document ready:

```javascript theme={null}
$(document).ready(function () {
  var userId = $("#app").data("user-id");
  var userPlan = $("#app").data("user-plan");

  if (userId) {
    window.zenstep?.identify(userId, { plan: userPlan });
  }
});
```

***

## Multi-page applications

For traditional server-rendered apps (PHP, Rails, Django, etc.), add the snippet in your base layout template and call `identify()` on every page after the authenticated user is available.

```html base_layout.html (example) theme={null}
<!-- In <head> or just before </body> -->
<script
  async
  src="https://cdn.getzenstep.com/v1/snippet.js"
  data-zenstep="{{ ZENSTEP_SNIPPET_KEY }}"
></script>

{% if user.is_authenticated %}
<script>
  window.zenstep?.identify("{{ user.id }}", {
    email: "{{ user.email }}",
    plan: "{{ user.plan }}",
  });
</script>
{% endif %}
```

<Warning>
  Render user data server-side into a `<script>` block only if you trust the output escaping of your template engine. Never inject unsanitised user input into JavaScript.
</Warning>

***

## Verifying installation

Open the browser console. After the snippet loads you'll see:

```
[Zenstep] Loaded N flow(s)
```

If there are no published flows, you'll see no message — that's expected. Check the **Install** page in the dashboard for a "Snippet detected" confirmation.
