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.

Installation

Add the snippet to your root HTML file (public/index.html for Create React App, or the equivalent entry HTML for Vite).
public/index.html
<!DOCTYPE html>
<html>
  <head>
    ...
  </head>
  <body>
    <div id="root"></div>
    <!-- Add before </body> -->
    <script
      async
      src="https://cdn.getzenstep.com/v1/snippet.js"
      data-zenstep="YOUR_SNIPPET_KEY"
    ></script>
  </body>
</html>

Calling identify

Call identify() inside a useEffect that runs when the authenticated user is available. The snippet’s queue system means this is safe to call before or after the script loads.
src/components/ZenstepProvider.tsx
import { useEffect } from "react";
import { useAuth } from "./your-auth-hook"; // replace with your auth provider

export function ZenstepProvider({ children }: { children: React.ReactNode }) {
  const { user } = useAuth();

  useEffect(() => {
    if (!user) return;

    window.zenstep?.identify(user.id, {
      email: user.email,
      plan: user.plan,
      role: user.role,
      createdAt: user.createdAt,
    });
  }, [user?.id]);

  return <>{children}</>;
}
Wrap your app root with this provider:
src/main.tsx
import { ZenstepProvider } from "./components/ZenstepProvider";

ReactDOM.createRoot(document.getElementById("root")!).render(
  <StrictMode>
    <ZenstepProvider>
      <App />
    </ZenstepProvider>
  </StrictMode>,
);

TypeScript types

Add a global type declaration so TypeScript knows about window.zenstep:
src/global.d.ts
interface ZenstepAPI {
  identify: (
    userId: string,
    attributes?: Record<string, string | number | boolean>,
  ) => void;
  track: (event: string, data?: Record<string, unknown>) => void;
}

interface Window {
  zenstep?: ZenstepAPI;
}

SPA navigation

Zenstep automatically detects client-side navigation using a History API pushState observer. When the URL changes, it re-evaluates all targeting rules and shows any newly-matching flows. No additional setup is required.

Conditional loading (development)

If you want to disable Zenstep in your local development environment:
<!-- Only load in production -->
<script>
  if (window.location.hostname !== "localhost") {
    const s = document.createElement("script");
    s.src = "https://cdn.getzenstep.com/v1/snippet.js";
    s.dataset.zenstep = "YOUR_SNIPPET_KEY";
    s.async = true;
    document.body.appendChild(s);
  }
</script>