# JavaScript API reference

> Every method on window.stepskit: identify, setUserAttributes, playTour, stopTour, refresh, on/off, dismissAnnouncement, destroy, validateEnvironment, and more. Complete reference for the StepsKit embed.

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

Once the StepsKit script has loaded, it exposes a global `window.stepskit`
object with the methods documented below.

## Pre-init queue

Every method below can be called **before** the SDK script finishes
loading. The install snippet creates `window.stepskit` immediately and
installs a shim for each queueable method that pushes the call onto
`window.stepskit._q`. When the embed initializes, it replays the queue
in order against the real instance — so `identify`, `setUserAttributes`,
and friends "just work" without an `<Script onLoad>` callback or
`window.stepskit?.method(...)` null check.

The only exceptions are the synchronous getters — `isPlaying`,
`getTours`, and `getUserAttributes`. These return values immediately, so
they require the SDK to be loaded. Calling them pre-init throws a
`TypeError` because the queue stub doesn't define them.

## User attributes

### identify

```ts
identify(visitor: Record<string, string | number | boolean>): Promise<void>
```

Alias for `setUserAttributes(visitor, { autoRefresh: true })`. The
shape matches Pendo and Intercom-style SDKs, so existing identify
patterns translate directly. Safe to call before the SDK script finishes
loading — the call is queued and replayed at init, in time for the
first tour fetch.

```ts
stepskit.identify({ id: "u_123", email: "a@b.com", plan: "pro" });
```

No `?.` null check needed — the install snippet's queue stub captures
the call even if the real SDK hasn't downloaded yet.

### setUserAttributes

```ts
setUserAttributes(
  attrs: Record<string, string | number | boolean>,
  options?: { autoRefresh?: boolean },
): Promise<void>
```

Merges `attrs` into the visitor's known attributes. Passing
`autoRefresh: true` re-fetches tours and re-evaluates visibility against
the new attributes — call this after login or any change that affects
targeting. `identify` is the preferred shape for the post-login case;
`setUserAttributes` is for partial updates without a refresh.

```ts
await window.stepskit?.setUserAttributes(
  { id: "user_123", plan: "enterprise" },
  { autoRefresh: true },
);
```

### getUserAttributes

```ts
getUserAttributes(): Record<string, string | number | boolean> | undefined
```

Returns the current attributes, or `undefined` if none have been set.
This is a synchronous getter — it requires the SDK to be loaded, so
calling it before the script finishes loading throws.

## Tour control

### playTour

```ts
playTour(tourId: string): Promise<void>
```

Starts the named tour immediately, regardless of targeting rules. Useful
for "Take a tour" buttons.

### Play by link (`?sk=`)

You don't need any JavaScript to trigger a tour from a link. Add a `sk`
query parameter with the tour's id or slug to any page URL on your site:

```
https://app.example.com/dashboard?sk=welcome-tour
```

When the embed loads and sees `?sk=`, it force-plays that tour immediately —
bypassing URL, targeting, screen-width, and frequency rules (it plays even if
the visitor already saw a `show_once` tour). The tour must still be published
to that domain. Great for "check out this new feature" links in changelogs,
emails, or support replies. Point the link at the page where the tour's first
step appears. Copy a ready-made link from the tour's **Publish** page.

### stopTour

```ts
stopTour(): void
```

Stops the currently playing tour. Counts as a dismissal for
[frequency capping](/docs/concepts/frequency-capping.md).

### isPlaying

```ts
isPlaying(): boolean
```

Returns `true` if a tour is currently on screen.

### getTours

```ts
getTours(): Array<{ id: string; name: string }>
```

Returns the list of tours currently loaded for this visitor (i.e., the
tours that passed targeting and frequency rules at the last fetch).

## Re-evaluation

### refresh

```ts
refresh(): Promise<void>
```

Re-fetches tours from the StepsKit API with the current user attributes
and re-evaluates which one (if any) should play now. Call this after
client-side route changes if your app navigates without full page
reloads and you want tours to react to the new page.

`setUserAttributes(..., { autoRefresh: true })` calls `refresh` for you;
you only need `refresh()` directly when attributes haven't changed but
context has.

## Events

### on / off

```ts
on(event: string, handler: (...args: unknown[]) => void): void
off(event: string, handler: (...args: unknown[]) => void): void
```

Subscribe and unsubscribe to embed events. Available events include:

- `announcement_clicked` — fired when a banner CTA is clicked.

```ts
window.stepskit?.on("announcement_clicked", (payload) => {
  console.log("announcement clicked", payload);
});
```

## Announcements

### dismissAnnouncement

```ts
dismissAnnouncement(announcementId: string): void
```

Programmatically dismiss a banner — equivalent to the user clicking the
close button.

## Teardown

### destroy

```ts
destroy(): void
```

Tears down all StepsKit UI (active tours and announcements) and detaches
listeners. Useful in single-page apps when the user signs out and you
want to fully reset the embed.

## Diagnostics

### validateEnvironment

```ts
validateEnvironment(): EnvironmentReport
```

Returns a diagnostic snapshot of the embed's current state — handy when
a tour you expected to fire isn't firing. The method also logs the
filtered-tour list via `console.table` and the full report via
`console.log`, so the simplest workflow is to open devtools and run:

```ts
const report = stepskit.validateEnvironment();
```

The returned `EnvironmentReport` includes:

- `apiKey` — the masked project API key in use.
- `baseUrl` — the API endpoint the embed is targeting.
- `visitorId` — the resolved visitor identifier, or `undefined` if no
  `id` was provided.
- `userAttributes` — the current attribute bag (or `undefined`).
- `initialized` — whether `init()` has completed.
- `isPlaying` — whether a tour is currently on screen.
- `currentTourId` — the ID of the currently-playing tour, or `null`.
- `toursLoaded` / `tooltipsLoaded` — counts of tours and tooltips fetched.
- `toursFiltered` — array of `{ tourId, name, reason, detail? }` for
  every tour that was loaded but filtered out before playback.
- `warnings` — validation messages accumulated since init (e.g. dropped
  attribute keys from `setUserAttributes`).

The `reason` field on `toursFiltered` is one of:

- `url_pattern_mismatch` — the current URL doesn't match the tour's URL
  rules.
- `screen_width_too_narrow` — the viewport is below the tour's minimum
  width.
- `targeting_failed` — the visibility rules didn't match the current
  visitor's attributes.
- `frequency_capped` — `show_once` already fired for this visitor.
- `inactive` — the tour is currently disabled in the dashboard.

This is a pure dev/debug call — safe to invoke any time post-init, but
don't ship it in production code paths.

## TypeScript

If you installed the [`stepskit` npm package](/docs/install.md), types
ship with it — import the API instead of reaching for the global, and
skip the rest of this section:

```ts
import stepskit from "stepskit";
```

On a `<script>` snippet install there's no import to pull types from, so
you need an ambient declaration for the global. This is the same file the
[React install guide](/docs/install/react.md) and the [AI install
prompt](/docs/ai-agents/install-prompt.md) hand out, so it stays in sync
with the runtime:

```ts
// src/stepskit.d.ts
// Only needed for the <script> snippet install. If you installed the "stepskit"
// npm package, delete this file — the package ships its own types.
export {};

type StepsKitAttributes = Record<string, string | number | boolean>;

interface StepsKitToursFilteredEntry {
  tourId: string;
  name: string;
  reason:
    | "url_pattern_mismatch"
    | "screen_width_too_narrow"
    | "targeting_failed"
    | "frequency_capped"
    | "inactive";
  detail?: string;
}

interface StepsKitEnvironmentReport {
  apiKey: string; // masked (e.g. "sk_live_a3f...***")
  baseUrl: string;
  visitorId: string | undefined;
  userAttributes: StepsKitAttributes | undefined;
  initialized: boolean;
  isPlaying: boolean;
  currentTourId: string | null;
  toursLoaded: number;
  tooltipsLoaded: number;
  toursFiltered: StepsKitToursFilteredEntry[];
  warnings: string[];
}

declare global {
  interface Window {
    stepskit?: {
      identify(visitor: StepsKitAttributes): Promise<void>;
      setUserAttributes(
        attrs: StepsKitAttributes,
        options?: { autoRefresh?: boolean },
      ): Promise<void>;
      getUserAttributes(): StepsKitAttributes | undefined;
      playTour(tourId: string): Promise<void>;
      stopTour(): void;
      isPlaying(): boolean;
      getTours(): Array<{ id: string; name: string }>;
      refresh(): Promise<void>;
      on(event: string, handler: (...args: unknown[]) => void): void;
      off(event: string, handler: (...args: unknown[]) => void): void;
      dismissAnnouncement(announcementId: string): void;
      destroy(): void;
      validateEnvironment(): StepsKitEnvironmentReport;
      /** No-op today: warns once and discards. Do not wire product events to it. */
      track(event: string, properties?: Record<string, unknown>): void;
    };
  }
}
```
