1. Copy your line
In the Skate dashboard, open Settings › Sites and copy the line for your site. It looks like this:
<script src="https://cdn.useskate.com/skate.js" data-site="s_0123456789abcdef0123" async></script>
data-site is your site id. Each site is one web address (https://app.acme.com). The address in the browser’s bar must match it exactly. If the same app also opens at another address (https://acme.com and https://www.acme.com), add that address under Settings › Sites & install › Other addresses for this app. The same line then works on both, with the same settings and conversations.
2. Paste it before </head>
In the HTML that every page uses (your layout or template), paste the line just before </head>. Then deploy.
It is safe anywhere in the page: in <head> or <body>, with async or defer, or added by a tag manager. It loads after your page, never blocks it, and runs once even if the line is on the page twice.
Not your job? Use Send to my developer on the same page. It emails them the line and these steps.
3. Check it
Open your app in a new tab. The Skate launcher appears in the corner. The dashboard shows Skate is on your site within a few seconds.
Nothing showed up? Open the browser console (right-click, Inspect, Console). Skate prints one line that starts with [Skate] and names the fix. See the table below.
Testing your install. Add ?skate_test=1 to your page’s address (https://app.acme.com/?skate_test=1), by hand or in your QA scripts. Skate works exactly the same, but every request in that tab is marked as a test: it is listed under Inbox › Conversations as Your test and left out of every number.
Your framework
Paste the line where your app keeps its page template.
How these were tested: each framework row on a fresh app from the framework’s own starter, with the line exactly where the row says, built for production and opened in a real browser. Each showed one launcher, still one after moving to another page through the app’s own links, and right-to-left on a Hebrew page. The site builders (Webflow, Framer, Wix, Squarespace, Shopify) were tested on live sites built with them, with the line placed before </head>, where their head-code field puts it; their menu steps come from their help pages. Laravel was tested with the Livewire starter kit and a plain Blade layout; the React and Vue starter kits keep <head> in resources/views/app.blade.php. Rails was checked by hand on a fresh Rails 8.1 app (Turbo navigation, and its Content-Security-Policy with and without the nonce), but it is not in the repeatable run yet.
| You use | Where the line goes |
|---|---|
| Next.js App Router | app/layout.tsx, see below |
| Next.js Pages Router | pages/_document.tsx, inside <Head> |
| React (Vite) | index.html, before </head> |
| Vue (Vite) | index.html, before </head> |
| Nuxt | nuxt.config.ts, see below |
| SvelteKit | src/app.html, before %sveltekit.head% |
| Angular | src/index.html, before </head> |
| Rails | app/views/layouts/application.html.erb, before </head>. With a Content-Security-Policy, see below |
| Django, Flask | templates/base.html (the template your pages extend), before </head> |
| Laravel | The Blade file with your <head>: resources/views/partials/head.blade.php (Livewire starter kit), resources/views/app.blade.php (React and Vue starter kits), or your own layout |
| Plain HTML, PHP | The header every page includes, before </head> |
| Google Tag Manager | A Custom HTML tag with the line, trigger All Pages. A Skate tag template for the gallery is coming. |
| WordPress | A header-scripts plugin such as WPCode, Header box, or your theme’s header (Appearance › Theme File Editor › header.php, before </head>). A Skate plugin for the WordPress directory is coming. |
| Webflow | Site settings › Custom code › Head code. Save, then Publish: Webflow adds it only to the published site |
| Framer | Site settings › Custom code, add a script at the end of <head> on all pages, then Publish |
| Wix | Settings › Custom code › Add custom code: all pages, place it in Head. Needs a published site with a connected domain |
| Squarespace | The Code injection panel, the Header field, then Save. Needs the Core plan or above |
| Shopify | Stores: coming soon. The line works in a theme (Online Store › ⋯ › Edit code, layout/theme.liquid, before </head>), but store tasks such as choosing options and checking out are not ready yet |
Next.js App Router (app/layout.tsx):
import Script from 'next/script';
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
{children}
<Script src="https://cdn.useskate.com/skate.js" data-site="s_0123456789abcdef0123" strategy="afterInteractive" />
</body>
</html>
);
}Nuxt (nuxt.config.ts):
export default defineNuxtConfig({
app: { head: { script: [{ src: 'https://cdn.useskate.com/skate.js', 'data-site': 's_0123456789abcdef0123', async: true }] } },
});Rails with its Content-Security-Policy initializer on (config/initializers/content_security_policy.rb), so the line carries the page’s nonce:
<script src="https://cdn.useskate.com/skate.js" data-site="s_0123456789abcdef0123" nonce="<%= content_security_policy_nonce %>" async></script>
Single-page apps, Turbo, Hotwire and Livewire’s wire:navigate need nothing extra. Skate follows route changes and stays on the page when the body is swapped.
Tell Skate who is signed in (optional)
After your own sign-in, call Skate.identify(). Skate then shows who asked, and you can target people by plan or role. See identify.md.
From your own code (optional)
Once the line is on the page, window.Skate has these calls. None of them returns a value.
| Call | What it does |
|---|---|
Skate.open() | Opens the panel. Skate.open('help') or Skate.open('news') opens it on that tab. |
Skate.close() | Closes the panel. |
Skate.toggle() | Opens the panel if it is closed, closes it if it is open. |
Skate.prefill('Export my invoices') | Opens the panel with these words in the box, not sent. The person presses Enter. |
Skate.help.open() | Opens the Help tab. |
Skate.help.search('billing') | Opens the Help tab with this search typed. |
Skate.walkthrough('wt_0123456789ab') | Opens the Start card of a published walkthrough, the same card its share link opens. Nothing plays until the person presses Start. With { mode: 'do' }, Do it for me is the first button. |
⌘K on a Mac, or Ctrl+K elsewhere, opens Skate too, unless your page already uses that key (then your page keeps it); turn it off under Assistant › Widget, Open with ⌘K / Ctrl+K.
The walkthrough id is on its share link in the dashboard (Guide › Walkthroughs, Copy link: the part after #skate=). An id that is not a live walkthrough of this site shows nothing to the visitor. The console says why, in a line that starts with [Skate].
A single-page app can also set the share link’s hash itself (location.hash = 'skate=wt_0123456789ab'). Skate opens the same Start card.
If your code runs before the widget loads, queue the call. The widget runs it when it boots:
window.SkateQueue = window.SkateQueue || []; SkateQueue.push(['walkthrough', 'wt_0123456789ab']);
The other calls, such as Skate.identify(), Skate.on() and Skate.registerTool(), have their own pages: identify.md, events.md, register-tool.md.
Content-Security-Policy
If your site sends a Content-Security-Policy header, add Skate’s two hosts:
script-src https://cdn.useskate.com; connect-src https://api.useskate.com;
If your policy uses nonces, put the same nonce on the Skate line (Rails: nonce="<%= content_security_policy_nonce %>"). Skate passes it on. Without it Skate still looks right, but the browser console shows style-src errors for walk.js. No style-src, img-src, frame-src or unsafe-eval change is needed.
If something is still blocked, the console says which directive and which host: [Skate] Your Content-Security-Policy blocks Skate: add https://api.useskate.com to connect-src.
If it does not show
| The console says | What to do |
|---|---|
[Skate] Site s_… is registered for https://acme.com; this request came from https://www.acme.com. | The page’s address is not the one you registered. If it is the same app, add it under Settings › Sites & install › Other addresses for this app. If it is another app, add it as its own site and use that site’s line on it. |
[Skate] No site is registered as s_…. | The id on the line is wrong. Copy the line again from the dashboard. |
[Skate] data-site="…" is not a site id. | The id is missing or cut short. Copy the line again. |
[Skate] Your Content-Security-Policy blocks Skate: add … to … | Add that host to that directive (see CSP). |
[Skate] Could not load https://cdn.useskate.com/…. | A proxy, a firewall or a CSP script-src stopped the file. Allow cdn.useskate.com. |
[Skate] The account that owns s_… has no task credits left. | Add credits on the Billing page. Until then visitors see “not available right now”. |
| Nothing at all | The line is not on this page yet. View the page source and search for skate.js. |
Ad blockers. We test Skate’s files and requests against the default lists of uBlock Origin and Adblock Plus (EasyList, EasyPrivacy, uBlock filters and privacy). None of them block Skate. Those lists do block background “beacon” pings to other sites; Skate sends its events as a normal request first and uses a beacon only as a fallback, so nothing is lost.
Remove it. Delete the line and deploy. To remove it from a page without a reload, call Skate.uninstall().
Older lines keep working
A line from before October 2026 (walk-bootstrap.js with data-walk-site, data-walk-server, data-walk-mode) still works as it is. You do not need to change it.