WordPress
Install StepsKit on WordPress
Two paths below: a proper wp_enqueue_script install for developers, and a
header-and-footer-scripts plugin for everyone else. Both work; the first
survives theme changes.
A note on fit. WordPress "users" are usually admins, editors and authors —
not product users. StepsKit is most useful here on a membership site, a
LearnDash/WooCommerce customer area, or inside wp-admin to onboard editors. On
a purely anonymous marketing site you can still run tours, but you won't get
per-user targeting or reliable show-once capping.
Prerequisites
- Your project API key, from the Integrations page in the dashboard.
- Ability to add a plugin, or edit a child theme's
functions.php.
Don't paste into header.php
The tempting move is to open Appearance → Theme File Editor and drop the snippet
into the active theme's header.php or footer.php. Don't:
- it's lost the next time the theme updates,
- it's lost if you switch themes,
- and it's invisible to whoever inherits the site.
Use one of the two options below instead.
1. Add the loader
Option A — a site-specific plugin (recommended)
Create wp-content/plugins/stepskit/stepskit.php:
<?php
/**
* Plugin Name: StepsKit
* Description: Loads the StepsKit product tour SDK.
*/
if (!defined('ABSPATH')) {
exit;
}
const STEPSKIT_API_KEY = 'YOUR_API_KEY';
add_action('wp_enqueue_scripts', function () {
wp_enqueue_script(
'stepskit',
'https://cdn.stepskit.com/stepskit.latest.js',
[],
null, // no ?ver= query string on a CDN URL
true // load in the footer
);
});
// wp_enqueue_script cannot set arbitrary attributes, so add data-api-key here.
add_filter('script_loader_tag', function ($tag, $handle) {
if ($handle !== 'stepskit') {
return $tag;
}
return str_replace(
' src=',
' async data-api-key="' . esc_attr(STEPSKIT_API_KEY) . '" src=',
$tag
);
}, 10, 2);Activate it under Plugins. A plugin rather than a child theme because it keeps working when the site is re-themed.
Two details worth knowing:
nullfor$veromits the?ver=6.xquery string WordPress otherwise appends. On a CDN URL that only fragments caching.script_loader_tagis the only way to adddata-api-key.wp_enqueue_scripthas no parameter for arbitrary attributes — the newer$argsarray coversstrategy(async/defer) andin_footer, not data attributes.
Option B — a header/footer scripts plugin
If you'd rather not write PHP, install any "insert headers and footers" plugin and paste the HTML snippet into the footer field. It works, and it survives theme updates because the plugin owns it.
You'll still need Option A's approach — or that plugin's own PHP hooks — to identify logged-in users.
2. Identify the user
This is half the install, not a nice-to-have. Without an id, show-once
capping degrades to a per-tab check and any tour with a "filtered" audience is
hidden outright.
WordPress is a multi-page app, so user attributes don't survive a page load — the identify call has to run on every request, not just after login. Attaching it to the enqueued handle does exactly that.
Add this to the same plugin, inside the wp_enqueue_scripts action, after the
wp_enqueue_script call:
if (is_user_logged_in()) {
$user = wp_get_current_user();
wp_add_inline_script('stepskit', sprintf(
'window.stepskit = window.stepskit || {}; window.stepskit._q = window.stepskit._q || []; window.stepskit._q.push(["identify", %s]);',
wp_json_encode([
'id' => (string) $user->ID,
'email' => $user->user_email,
'plan' => implode(',', $user->roles),
])
), 'after');
}Pushing onto _q rather than calling window.stepskit.identify() directly is
deliberate: wp_add_inline_script(..., 'after') prints the block immediately
after the <script src> tag, which is before the async SDK has finished
downloading. The queue is what StepsKit drains on startup, so the call survives
the gap.
wp_json_encode is what makes this safe to emit inside a <script> block —
never build the object by concatenating fields.
Swap plan for whatever you actually want to target on. On a membership site
that's usually the membership level rather than the WordPress role.
3. Verify it worked
Load any page of your site while logged in, then:
- Dashboard — the project's Integrations page flips to Connected.
- Console — run
window.stepskit.validateEnvironment(). You want a non-nullvisitorIdand a populateduserAttributes. A nullvisitorIdmeans step 2 never ran. - Network —
cdn.stepskit.com/stepskit.latest.jsreturns200.
validateEnvironment() also returns a toursFiltered table that says exactly
why any given tour isn't showing.
WordPress notes
Running tours inside wp-admin. wp_enqueue_scripts only fires on the
front end. To onboard editors in the admin, hook admin_enqueue_scripts as well
(or instead) — same enqueue call, same filter.
Caching plugins. WP Rocket, LiteSpeed Cache and friends can defer, combine
or delay third-party scripts. If tours stop firing after you enable one, exclude
cdn.stepskit.com from JavaScript optimisation. This is the most common cause
of "it worked yesterday".
Block themes and headless WordPress. A block theme enqueues the same way. Headless WordPress is a different install — follow the guide for whatever framework renders the front end.
Content Security Policy. WordPress doesn't set one by default; if a security plugin does, StepsKit needs:
script-src:https://cdn.stepskit.com, plus'unsafe-inline'for the inline identify blockconnect-src:https://stepskit.comstyle-src:'unsafe-inline'— required. The embed injects its entire stylesheet as a<style>element when the script evaluates. Without this the tour engine renders completely unstyled rather than merely off-brand.img-src:https://*.supabase.co— only if your steps use images.
Troubleshooting
Dashboard still says "Not connected". Check the plugin is activated, and
view source for cdn.stepskit.com. If the tag is missing, another plugin is
dequeuing it.
Script tag is present but has no data-api-key. Your script_loader_tag
filter isn't matching. The $handle must be exactly the string you passed to
wp_enqueue_script — 'stepskit' in the example above.
Connected, but no tour plays. Usually identity. Run
validateEnvironment(): a null visitorId means the inline script isn't
rendering — confirm is_user_logged_in() is true on that page.
Worked, then stopped after installing a cache plugin. Exclude
cdn.stepskit.com from JS optimisation. See above.
Tour renders as unstyled text. Your CSP is missing
style-src 'unsafe-inline'.
Next steps
- JavaScript API reference — every method on
window.stepskit. - User attributes — what you can target on.
- Frequency capping — how "show once" works.