Install StepsKit

The tour engine is a ~10 KB script served from a CDN and always current — you never version it. What you install is the small loader that fetches it.

Install the package

If your project has a package.json and a bundler, this is the install to use.

npm install stepskit

The package is a typed loader, not the tour engine. It installs a pre-load call queue, fetches the engine from cdn.stepskit.com, and gives you a fully typed API. The version you install pins the loader — the engine streams from the CDN and is always current, so you never bump this package to pick up an engine fix.

React, Next.js, and Remix render the component once, at the root of your tree. It renders nothing, loads StepsKit once (StrictMode-safe), and identifies the user when the user prop appears or its values change — passing a new object with the same values does not re-identify.

import { StepsKit } from "stepskit/react";

// Render once, at the root of your app.
<StepsKit apiKey="YOUR_API_KEY" />

It already carries "use client", so in the Next.js App Router you can drop it straight into app/layout.tsx without turning the layout into a client component:

import { StepsKit } from "stepskit/react";

export default function RootLayout({ children }) {
return (
  <html lang="en">
    <body>
      {children}
      <StepsKit
        apiKey="YOUR_API_KEY"
        user={{ id: user.id, email: user.email, plan: user.plan }}
      />
    </body>
  </html>
);
}

Pick one. Running the package and the snippet loads two embed instances.

Identify the visitor

Adding the loader is the first half. Identifying the visitor is the second, and it is not optional.

import stepskit from "stepskit";

stepskit.identify({ id: user.id, email: user.email, plan: user.plan });

id is the only required key. Call it once, when your auth state resolves — calls made before the engine finishes loading, or even before init(), are queued and replayed in order, so identity is always set before the first tours fetch.

Without an id, StepsKit can't tell one visitor from the next. Show-once capping degrades to a per-tab check that dies with the tab, and an audience set to "filtered" is hidden outright — the matcher gives up before it evaluates a single rule, so nothing matches, not even a negative one.

The install still verifies as connected. It is still broken. Every guide below treats identify() as a numbered step for that reason. See visitor identification for the full picture.

Options

stepskit.init("YOUR_API_KEY", {
  apiUrl: "https://stepskit.your-domain.com", // self-hosted / local API origin
  debug: true, // [StepsKit] info logs
  user: { id: "user_123" }, // identify as part of init
  nonce: cspNonce, // CSP nonce for the injected <script>
});

The React component takes the same options as props: apiKey, user, apiUrl, debug, nonce. Only user is re-read after mount.

Content Security Policy

If your app sets a CSP, allow https://cdn.stepskit.com in script-src and https://stepskit.com in connect-src. style-src needs 'unsafe-inline' — the engine injects its entire stylesheet as a <style> element, so without it tours render completely unstyled. Pass nonce to stamp your own nonce onto the script the loader injects.

TypeScript

Types ship with the package — there is nothing to declare. If you previously pasted a src/stepskit.d.ts global declaration for the snippet install, delete it; a hand-written declaration will conflict with the real one.

Installing with the snippet instead? StepsKit only attaches itself to window, so you need the ambient declaration.

Migrating from the script snippet

Remove the <script> snippet when you add the package. The snippet injects unconditionally, so leaving both in place loads two embed instances.

Everything else carries over unchanged: same API key, same project, same tours. Calls you already make against window.stepskit keep working — the package writes to the same global — but prefer the typed import in new code.

CommonJS

The package ships both ESM and CJS. In CommonJS, use the named export:

const { stepskit } = require("stepskit");

What's on the API

Everything on window.stepskit is on the stepskit import, fully typed. The methods that can't be queued return a neutral value until the engine loads: isPlaying() is false, getTours() is [], and getUserAttributes() / validateEnvironment() are undefined. Everything else queues.

See the JavaScript API reference for what each method does.

Pick your framework

Every guide installs the same thing. They differ in where it goes, how that framework wants a third-party script loaded, and which of its quirks will silently break the install.

Not listed? Any stack works. If it bundles, install the package and call init() at its single global entry point. If it doesn't, the script tag guide is plain, non-module HTML that works almost everywhere.

Let an AI agent do it

If you use Claude Code, Cursor, or a similar coding agent, it can read your codebase, work out which of the guides above applies, and install StepsKit for you — including the identify call.

Going deeper