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 stepskitThe 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>
);
}Vue, Angular, Svelte, Astro, and any other bundled app call init() once,
at the entry point. It's SSR-safe (a no-op on the server) and idempotent —
calling it twice injects nothing.
import stepskit from "stepskit";
// Call once, at your app's entry point. SSR-safe and idempotent.
stepskit.init("YOUR_API_KEY");Where that entry point is differs per framework — main.ts for Vue, a
client-only plugin for Nuxt, app.config.ts for Angular. The
framework guides below name the exact file, and the
quirks of each that silently break the install.
WordPress, a CMS theme, Google Tag Manager, plain HTML, or a server-rendered
app with no JavaScript build get the <script> snippet instead. There is
nothing to bundle a dependency into on those, so don't try — add this to the
base layout template, before </body>:
<script>
(function () {
var stepskit = window.stepskit = window.stepskit || {};
stepskit._q = stepskit._q || [];
// Queue API calls made before the SDK finishes loading.
var methods = ['identify', 'setUserAttributes', 'track', 'refresh', 'playTour', 'stopTour', 'dismissAnnouncement', 'on', 'off', 'destroy', 'validateEnvironment'];
methods.forEach(function (method) {
stepskit[method] = stepskit[method] || function () {
stepskit._q.push([method].concat([].slice.call(arguments)));
};
});
// Load the StepsKit SDK asynchronously.
var script = document.createElement('script');
script.async = true;
script.src = 'https://cdn.stepskit.com/stepskit.latest.js';
script.setAttribute('data-api-key', 'YOUR_API_KEY');
var first = document.getElementsByTagName('script')[0];
first.parentNode.insertBefore(script, first);
})();
</script>It loads the same engine and behaves identically at runtime. The snippet queues
API calls made before the engine arrives, so stepskit.identify() on the next
line is safe.
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.
JavaScript frameworks — these use the package.
- ReactVite, Create React App, or plain React.
- Next.jsApp Router and Pages Router.
- VueVue 3 with Vite or Vue CLI.
- Nuxta client-only plugin, because Nuxt renders on the server.
- Angularand why
angular.jsonis the wrong place. - SvelteKitplus its CSP config, which can block the embed.
- Astroand which of its script directives you need.
- Remix / React Router 7
app/root.tsx.
Server-rendered apps
No code deploy
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.
- Connect your agent over MCP — the agent gets your API key and can verify its own work.
- Copy-paste install prompt — no MCP setup needed.
Going deeper
- JavaScript API reference — every method on
window.stepskit. - User attributes — what you can target on.
- Visitor identification — why
idmatters. - Frequency capping — how "show once" is enforced.