# Install StepsKit

> Install StepsKit with npm, pnpm, yarn, or bun — or one script tag. init() options, CSP, TypeScript, plus guides for React, Next.js, Vue, Rails, WordPress and more.

*Source: https://stepskit.com/docs/install*

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.

```bash
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.

```tsx
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:

```tsx
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.

```ts
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](#pick-your-framework) 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>`:

```html
<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.**

```ts
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](/docs/concepts/visitor-identification.md) for the full
picture.

## Options

```ts
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](/docs/api.md#typescript).

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

```js
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](/docs/api.md) 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.

- [React](/docs/install/react.md) — Vite, Create React App, or plain React.
- [Next.js](/docs/install/nextjs.md) — App Router and Pages Router.
- [Vue](/docs/install/vue.md) — Vue 3 with Vite or Vue CLI.
- [Nuxt](/docs/install/nuxt.md) — a client-only plugin, because Nuxt renders on the server.
- [Angular](/docs/install/angular.md) — and why `angular.json` is the wrong place.
- [SvelteKit](/docs/install/sveltekit.md) — plus its CSP config, which can block the embed.
- [Astro](/docs/install/astro.md) — and which of its script directives you need.
- [Remix / React Router 7](/docs/install/remix.md) — `app/root.tsx`.

**Server-rendered apps**

- [JavaScript / script tag](/docs/install/js.md) — any site where you can edit the markup.
- [Laravel & PHP](/docs/install/laravel.md) — Blade layouts, and how to escape user data safely.
- [Ruby on Rails](/docs/install/rails.md) — jsbundling vs importmap, Turbo Drive, and CSP nonces.
- [WordPress](/docs/install/wordpress.md) — `wp_enqueue_script` from a plugin, not `header.php`.

**No code deploy**

- [Google Tag Manager](/docs/install/gtm.md) — a Custom HTML tag on All Pages.

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](/docs/install/js.md) 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](/docs/ai-agents/connect.md) — the agent gets your
  API key and can verify its own work.
- [Copy-paste install prompt](/docs/ai-agents/install-prompt.md) — no MCP setup
  needed.

## Going deeper

- [JavaScript API reference](/docs/api.md) — every method on `window.stepskit`.
- [User attributes](/docs/concepts/user-attributes.md) — what you can target on.
- [Visitor identification](/docs/concepts/visitor-identification.md) — why `id` matters.
- [Frequency capping](/docs/concepts/frequency-capping.md) — how "show once" is enforced.
