SDK installation & integration

Guidely ships one framework-independent runtime (@guidelytech/sdk-core, which renders through @guidelytech/guide-runtime) and thin adapters. All adapters share the same engine, state and API.

PackageUse when
@guidelytech/reactReact 18/19 apps (Vite, CRA, Remix)
@guidelytech/nextjsNext.js App Router or Pages Router (re-exports React + navigation bridge)
@guidelytech/svelteSvelte 4/5 apps
@guidelytech/sveltekitSvelteKit 2
@guidelytech/sdk-coreVanilla JavaScript / any other framework
@guidelytech/react-nativeReact Native and Expo apps (native rendering) — see mobile.md
iOS SDK (mobile/ios, SwiftPM / CocoaPods GuidelyTech)Native iOS apps (SwiftUI, UIKit) — see mobile.md
Android SDK (com.guidelytech:guidely-android)Native Android apps (Compose, Views) — see mobile.md
script tag (/sdk/guidely.js)Apps that cannot install npm packages

Every integration needs the environment public key (pk_dev_… / pk_stg_… / pk_prod_…) from Settings → Environments, and the API URL of your Guidely deployment. Public keys are not secrets: they only unlock published configuration for one environment, restricted by the allowed origins you configure. The dashboard's Settings → Environments page generates these snippets for you.

React

npm install @guidelytech/react
"use client";
import { GuidelyProvider } from "@guidelytech/react";

export function AppProviders({ children }: { children: React.ReactNode }) {
  return (
    <GuidelyProvider
      projectId={process.env.NEXT_PUBLIC_GUIDELY_PROJECT_ID!}
      environment="development"
      apiUrl={process.env.NEXT_PUBLIC_GUIDELY_API_URL}
    >
      {children}
    </GuidelyProvider>
  );
}
import { useGuidely, useGuidelyIdentify, useGuidelyEvent, useGuidelyReady, useChecklistProgress, useGuideState } from "@guidelytech/react";

function App({ user }) {
  const guidely = useGuidely();                       // no-op proxy during SSR / before ready
  const ready = useGuidelyReady();
  useGuidelyIdentify(user.id, { email: user.email, role: user.role, plan: user.plan }, { id: user.orgId, traits: { name: user.orgName } });
  useGuidelyEvent("guide:complete", (guide) => console.log("completed", guide.key));
  const progress = useChecklistProgress("onboarding-checklist");
  const tourState = useGuideState("new-user-onboarding");  // { completed, dismissed, impressions }

  return (
    <>
      <button onClick={() => guidely.startGuide("getting-started")}>Start onboarding</button>
      <button onClick={() => guidely.track("contact_created", { source: "manual" })}>Track</button>
      <button onClick={() => guidely.openResourceCenter()}>Help</button>
      <button onClick={() => guidely.reset()}>Log out</button>
    </>
  );
}

The provider creates the client once (Strict-Mode safe), never touches window during server rendering, does not block rendering, and destroys the client on unmount. Route changes are detected automatically through the History API.

Next.js (App Router)

npm install @guidelytech/nextjs
// app/providers.tsx
"use client";
import { GuidelyProvider, GuidelyNavigationListener } from "@guidelytech/nextjs";

export function Providers({ children }: { children: React.ReactNode }) {
  return (
    <GuidelyProvider projectId={process.env.NEXT_PUBLIC_GUIDELY_PROJECT_ID!} environment="development" apiUrl={process.env.NEXT_PUBLIC_GUIDELY_API_URL}>
      <GuidelyNavigationListener />
      {children}
    </GuidelyProvider>
  );
}

Put NEXT_PUBLIC_GUIDELY_PROJECT_ID, NEXT_PUBLIC_GUIDELY_ENV and NEXT_PUBLIC_GUIDELY_API_URL in .env.local. NEXT_PUBLIC_ variables are inlined into the browser bundle, which is fine because public keys are not secrets. GuidelyNavigationListener calls page() on every App Router navigation (the History patch covers it too; the listener is the explicit bridge). See apps/demo-react for a complete application.

Svelte

npm install @guidelytech/svelte
<script lang="ts">
  import { onMount } from "svelte";
  import { createGuidely, guideTrigger } from "@guidelytech/svelte";

  let guidely: ReturnType<typeof createGuidely> | undefined;
  onMount(() => {
    guidely = createGuidely({
      projectId: import.meta.env.VITE_GUIDELY_PROJECT_ID,
      environment: "development",
      apiUrl: import.meta.env.VITE_GUIDELY_API_URL,
    });
    guidely.identify("user_123", { plan: "pro" });
    return () => guidely?.destroy();
  });
</script>

<button onclick={() => guidely?.startGuide("getting-started")}>Start onboarding</button>
<button use:guideTrigger={"feature-discovery"}>What's new?</button>

Stores: guidely (client), guidelyReady, activeGuide, checklistProgress(key), guideCompleted(key); syncGuidelyUser(user | null) (see SvelteKit below); context helpers setGuidelyContext / getGuidelyContext; actions use:guideTrigger={"key"} and use:guidelyTarget. createGuidely throws a descriptive error when called during SSR — call it inside onMount.

SvelteKit

npm install @guidelytech/sveltekit
<!-- src/routes/+layout.svelte -->
<script lang="ts">
  import { env } from "$env/dynamic/public";
  import { initGuidely, guidelyEnv, syncGuidelyUser } from "@guidelytech/sveltekit";

  let { data, children } = $props();
  initGuidely(guidelyEnv(env)); // PUBLIC_GUIDELY_PROJECT_ID, PUBLIC_GUIDELY_ENV, PUBLIC_GUIDELY_API_URL

  // `data.user` comes from +layout.server.ts; null when signed out.
  $effect(() => {
    syncGuidelyUser(data.user ? { id: data.user.id, traits: { role: data.user.role }, account: { id: data.user.tenantId } } : null);
  });
</script>
{@render children()}

initGuidely is a no-op on the server ($app/environment.browser), calls page() from afterNavigate (one page_view per client-side navigation), and tears the client down when the layout is destroyed. guidelyEnv accepts $env/static/public, $env/dynamic/public or import.meta.env (VITE_GUIDELY_*).

Page views are automatic; users are not. Until you identify, activity is anonymous. syncGuidelyUser(user) mirrors your session into Guidely: it identifies when the id, traits or account change, does nothing when they don't, and calls reset() on sign-out (null). That includes an identity the SDK restored from localStorage after the server session ended. Pass undefined while the user is still loading. On Svelte 4 use $: syncGuidelyUser(...). See apps/demo-svelte.

Vanilla JavaScript / script tag

<script src="https://YOUR-GUIDELY-HOST/sdk/guidely.js" async></script>
<script>
  window.addEventListener("load", function () {
    Guidely.init({ projectId: "pk_prod_xxx", environment: "production", apiUrl: "https://YOUR-GUIDELY-HOST" });
  });
</script>

or declaratively: <script src="/sdk/guidely.js" data-project-id="pk_…" data-api-url="https://…"></script>. Guidely.get() returns the active client; the API is identical to createGuidely() from @guidelytech/sdk-core (npm install @guidelytech/sdk-core for bundled apps).

Identify users

guidely.identify(userId, traits?, account?)
guidely.setAccount({ id, traits })
guidely.updateTraits({ plan: "enterprise" })
guidely.reset()   // on logout: new anonymous id, server state cleared

Traits are what targeting rules see (user.attribute, user.role, user.plan, account.attribute). Only send what you need; keys like password, token, card are dropped. Anonymous activity before identify is merged into the identified profile.

Track events

guidely.track("deal_created", { value: 1200 })

Custom events drive event triggers, advanceOn: "event" steps, checklist task completion (completion: "event") and appear in the Events explorer. page() is automatic.

Replays, page analytics and errors

Page views (including the first one), page leave, clicks, rage clicks, heatmap click positions and uncaught errors are captured automatically, and session replay records sessions once an admin enables it in Settings → Privacy & data. No code is needed. See product-analytics.md.

guidely.captureException(error, { area: "checkout" })  // report a handled error (works with captureErrors: false; not when the project turns error tracking off)
guidely.startSessionRecording()   // record this session even if it was not sampled
guidely.stopSessionRecording()
guidely.isRecording()
guidely.getSessionId()            // the id shared by events and the recording

Mark up your markup for privacy: data-guidely-mask (text masked in recordings and click autocapture), data-guidely-block (not recorded at all), data-guidely-ignore (clicks not autocaptured), data-guidely-track (autocapture clicks on a custom element).

Guides, checklists, surveys, resource center

startGuide(key, { stepId? }), stopGuide(), dismissGuide(), getActiveGuide(), getGuides(), isGuideCompleted(key), getChecklistProgress(key), completeChecklistTask(key, taskId), openChecklist(key) / closeChecklist(key), openResourceCenter() / closeResourceCenter(), refresh(), on(event, cb) / off, destroy(). Events: ready, config, guide:start, guide:step, guide:complete, guide:dismiss, guide:skip, checklist:task, checklist:complete, survey:submit, resource-center:open, messenger:open, conversation:message, authoring, capture, error. Full reference: packages/sdk-core/README.md.

Scout messenger

When a project turns on Scout, every SDK shows the chat bubble automatically — no code changes. Optional controls: openMessenger(tab?) ("home", "messages", "help"), closeMessenger(), showNewMessage(text?), and the conversation:message event. With the messenger on, openResourceCenter() opens its Help tab. Call reset() on logout so the next user doesn't see the previous user's conversations.

How-to capture

How-tos are step-by-step guides with screenshots that you record by clicking through your own app. In the dashboard, Record opens your app in a new tab with ?guidely_capture=<token>; the SDK sees the parameter, removes it from the URL and shows a recorder toolbar at the bottom centre (Start recording, Record without screenshots, then Pause, Done and Cancel). While recording it shrinks to a small pill with the step count, so it barely shows in screenshots; hover it (or tab to it) for the buttons. Each click, typed value, dropdown choice and page change becomes a step with its screenshot, a highlight of the element and selectors (so a step can later become a tour step). Your app keeps working normally while you record: Guidely never blocks or changes your clicks. Guides, widgets, the messenger, session replay and autocapture pause for that tab while recording.

  • **SDK version:** capture needs an SDK that includes it (the first release after 0.6.0 of the

    @guidelytech/* packages, or the current script-tag build). Older SDKs ignore the parameter. Script-tag installs load the recorder from guidely-capture.js next to guidely.js, only in a capture session; allow it in a strict CSP like guidely-recorder.js.

  • **Sharing the tab:** **Start recording** asks the browser to share the screen. Choose *this tab*.

    Chrome and Edge suggest the current tab; in Safari and Firefox pick the tab yourself. If you share a window or the whole screen, the recorder asks you to share the tab instead, or to record without screenshots.

  • **Page loads:** browsers end screen sharing on a full page load (not on in-app navigation in a

    single-page app). Recording continues after the reload, but screenshots pause until you press Screenshots paused — Resume and share the tab again. A step whose screenshot was still uploading when the page unloaded is uploaded from the next page.

  • **Automatic redaction:** screenshots are redacted in the browser before upload, so the hidden

    pixels never leave it. Blurred are: password fields and other sensitive fields (payment and one-time-code autocomplete values; name/id containing pass, card, cvv, cvc, ssn, secret, token or otp), elements matching your mask selectors (data-guidely-mask, maskSelectors, the project setting) or data-guidely-redact, and visible text or field values that look like email addresses, card numbers, US Social Security numbers or phone numbers. Typed values are not recorded for sensitive fields or when they look like a card or SSN number, and masked elements are described without their text.

  • **Redact more:** add `data-guidely-redact` to anything that should be blurred in how-to

    screenshots (it also keeps the element's text out of step descriptions):

<section data-guidely-redact>…customer billing details…</section>

Options

projectId, environment, apiUrl, debug, analytics (false = guides without analytics), consent (granted | pending | denied), storage (local | session | memory), pollIntervalMs, configRefreshMs, maskSelectors, onError, theme, user, disableAuthoring (also turns off how-to capture), autocapture (false or { pageviews?, pageLeave?, clicks?, rageClicks?, heatmaps?, mouseMovement? }), captureErrors, sessionReplay (false or { maskAllText?, blockSelectors?, recordNetwork?, recordNetworkPayloads?, maskNetworkRequest? }), release (your app version or commit, matched against uploaded source maps). These three can only switch off what the project settings switch on.

Resilience

Configuration is cached in localStorage with an ETag, so guides keep working when the API is down; events are queued (bounded), batched, retried with backoff and flushed with sendBeacon on page hide. The SDK never throws into your application: errors surface via onError / the error event. Everything renders inside a Shadow DOM host (#guidely-root) with isolated styles.

Bundle size

~44 kB min+gzip for the npm runtime (sdk-core + runtime + floating-ui, including autocapture and error tracking). Two parts are separate chunks that load only when needed:

  • The Scout messenger (~9 kB), when a project enables it.
  • The session-replay recorder (~27 kB: rrweb, its console plugin and the upload controller), only

    for sessions that are recorded.

  • The how-to capture recorder (toolbar, screenshots, redaction and uploads), only in a capture session.

The script-tag build (guidely.js, ~79 kB) inlines everything except the two recorders, which it loads from guidely-recorder.js and guidely-capture.js in the same directory. See pnpm --filter @guidelytech/sdk-core size.