JavaScript API reference
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
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.
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
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.
await window.stepskit?.setUserAttributes(
{ id: "user_123", plan: "enterprise" },
{ autoRefresh: true },
);getUserAttributes
getUserAttributes(): Record<string, string | number | boolean> | undefinedReturns 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
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-tourWhen 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
stopTour(): voidStops the currently playing tour. Counts as a dismissal for frequency capping.
isPlaying
isPlaying(): booleanReturns true if a tour is currently on screen.
getTours
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
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
on(event: string, handler: (...args: unknown[]) => void): void
off(event: string, handler: (...args: unknown[]) => void): voidSubscribe and unsubscribe to embed events. Available events include:
announcement_clicked— fired when a banner CTA is clicked.
window.stepskit?.on("announcement_clicked", (payload) => {
console.log("announcement clicked", payload);
});Announcements
dismissAnnouncement
dismissAnnouncement(announcementId: string): voidProgrammatically dismiss a banner — equivalent to the user clicking the close button.
Teardown
destroy
destroy(): voidTears 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
validateEnvironment(): EnvironmentReportReturns 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:
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, orundefinedif noidwas provided.userAttributes— the current attribute bag (orundefined).initialized— whetherinit()has completed.isPlaying— whether a tour is currently on screen.currentTourId— the ID of the currently-playing tour, ornull.toursLoaded/hintsLoaded— counts of tours and hints 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 fromsetUserAttributes).
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_oncealready 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
The @stepskit/embed package ships types at dist/types/index.d.ts.
For most projects you'll only need an ambient declaration for the
global:
type StepsKitAttributes = Record<string, string | number | boolean>;
interface ToursFilteredEntry {
tourId: string;
name: string;
reason:
| "url_pattern_mismatch"
| "screen_width_too_narrow"
| "targeting_failed"
| "frequency_capped"
| "inactive";
detail?: string;
}
interface EnvironmentReport {
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;
hintsLoaded: number;
toursFiltered: ToursFilteredEntry[];
warnings: string[];
}
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(id: string): void;
destroy(): void;
validateEnvironment(): EnvironmentReport;
};
}