---
title: "How to add guides and surveys to a Vue app"
description: "Show Amplitude guides and surveys in a Vue and Vite app: one init, Vue Router guide links, stable pin targets, and a survey opened from a button."
canonical: "https://amplitude.com/en-us/build/vue-guides-surveys"
---

# How to add guides and surveys to a Vue app

Show Amplitude guides and surveys in a Vue and Vite app: one init, Vue Router guide links, stable pin targets, and a survey opened from a button.

Source: https://amplitude.com/en-us/build/vue-guides-surveys

---

# How to add guides and surveys to a Vue app

Turn on guides and surveys in a Vue and Vite app, send guide links and buttons through Vue Router, anchor a pin to a component, and open a survey from your own button. About 20 minutes, all copy-paste.

- Vue 3
- Vite
- Vue Router
- Unified SDK 1.x
- 20 min

Chris Tam · Last updated 9 October 2026

Vue Router swaps views without reloading the page. [Guides and surveys](https://amplitude.com/guides-and-surveys) walk users through those views and ask for feedback in context, and you edit them in Amplitude instead of shipping a deploy.

In this tutorial, you'll turn on guides and surveys, route guide links through Vue Router, anchor a pin, and open a survey from a button. It builds on [the Vue product analytics page](/build/vue-product-analytics).

## What you'll build

A Vue, Vite, and Vue Router app where guides and surveys boot once with the same user and device ID as your analytics, guide links marked **Use router** navigate with `router.push`, a guide button runs an `open_new_project` callback in your code, a pin anchors to `[data-guide="new-project-button"]`, and a "Give feedback" button opens the survey with the key `product-feedback`. By the end you'll see the SDK report a booted user, and `Guide Viewed` and `Survey Submitted` in your own user's event stream.

## Prerequisites

- A Vue 3 app with Vite, TypeScript, and Vue Router using `createWebHistory`, with Amplitude already initialized in `src/lib/amplitude.ts`. If you don't have that yet, start with [How to add product analytics to a Vue app](/build/vue-product-analytics).
- Your Amplitude project API key. In Amplitude, go to Settings > Projects > your project > General. Guides and surveys must use the same key as your analytics.
- A way to know who's signed in on the client, such as the `useAuth` composable and `useAmplitudeIdentity` from the product analytics page.

No Amplitude account yet? A free account is enough for this tutorial (the Free plan includes one guide and one survey, which is what Verify creates). [Sign up for free](https://app.amplitude.com/signup) to get your project API key. Some guides and surveys features, like scheduled nudges, depend on your plan; [compare plans](https://amplitude.com/pricing).

## 1. Install the SDK and turn guides and surveys on

[The Quickstart](https://amplitude.com/docs/get-started/amplitude-quickstart) recommends `@amplitude/unified` for browser apps installed from npm, and the [Browser Unified SDK](https://amplitude.com/docs/sdks/analytics/browser/browser-unified-sdk) includes guides and surveys and initializes them by default. If you followed the product analytics page, it's already installed.

Terminal

```sh
npm install @amplitude/unified
```

.env.local

```sh
VITE_AMPLITUDE_API_KEY=your-project-api-key
```

Vite only exposes env vars prefixed with `VITE_` to browser code. The project API key is public and its scope is limited to the minimum needed to ingest data, so shipping it to the browser is expected, but keep it out of source anyway.

Then turn the product on in Amplitude. The [Guides and Surveys Web SDK docs](https://amplitude.com/docs/sdks/guides-and-surveys/sdk) say to enable guides and surveys in your project settings before anything can display, and the project-level apps setting has to include web so it appears as a target when you create a guide or survey. Open Project Settings > Guides and Surveys and turn on both.

### If your app sends security headers

Skip this if your server doesn't send a `Content-Security-Policy` or `Cross-Origin-Opener-Policy` header; the Vite dev server sets neither. If yours does, the docs require two changes:

Response headers (merge into the policy you already send)

```text
Content-Security-Policy: script-src 'self' https://*.amplitude.com; connect-src 'self' https://*.amplitude.com; img-src 'self' https://*.amplitude.com; media-src 'self' https://*.amplitude.com; style-src 'self' https://*.amplitude.com
Cross-Origin-Opener-Policy: same-origin-allow-popups
```

`Cross-Origin-Opener-Policy: same-origin` blocks the messages Preview mode passes between Amplitude and your page, so previews never open; `same-origin-allow-popups` keeps them working. If your `style-src` blocks inline styles, pass your CSP nonce as `engagement: { nonce }` in the `initAll` options in step 2.

## 2. Initialize once, before the app mounts

Keep initialization in the module the product analytics page created, imported first in `src/main.ts`. The init options match [the session replay page](/build/vue-session-replay), including its masking rules, which the file below keeps. The one addition is a promise that resolves when `initAll` has finished, so code that talks to guides and surveys can wait for it.

src/lib/amplitude.ts

```typescript
import * as amplitude from '@amplitude/unified';

const apiKey = import.meta.env.VITE_AMPLITUDE_API_KEY;

let ready: Promise<void> = Promise.resolve();

if (apiKey) {
  // initAll starts analytics, session replay, Feature Experiment, and
  // guides and surveys. Guides and surveys boot with the analytics user.
  ready = Promise.resolve(
    amplitude.initAll(apiKey, {
      analytics: {
        autocapture: {
          attribution: true,
          pageViews: true,
          sessions: true,
          formInteractions: true,
          fileDownloads: true,
          elementInteractions: true,
        },
      sessionReplay: {
        // Every session while you build; about 1% in production.
        sampleRate: import.meta.env.PROD ? 0.01 : 1,
        debugMode: !import.meta.env.PROD,
        // From /build/vue-session-replay. Keep your masking rules when you paste this file.
        privacyConfig: {
          maskSelector: ['[data-amp-mask]'],
          blockSelector: ['[data-amp-block]'],
          unmaskSelector: ['[data-amp-unmask]'],
        },
    }),
  ).then(() => undefined);
} else {
  console.warn('VITE_AMPLITUDE_API_KEY is not set; analytics is off.');
}

/** Resolves once initAll, including guides and surveys, has finished. */
export function amplitudeReady() {
  return ready;
}

export { amplitude };
```

**Why.** With the Unified SDK, guides and surveys initialize alongside analytics and boot with the same user and session. Don't call `engagement.init()` or `engagement.boot()` yourself, and don't call them from a component's `onMounted`. The docs say calling `boot` more than once causes unexpected behavior, especially for guides and surveys set to appear immediately, and `onMounted` runs again every time that component mounts. A module body runs once per page load, before `createApp`, so it can't double up.

Session replay keeps the product analytics page's rate: every session in development, about 1% in production, which is where Amplitude recommends starting. Set `sampleRate` to `0` to turn session replay off. Don't pass `engagement: { skip: true }`, which stops guides and surveys from loading at all.

`window.engagement` isn't in TypeScript's `Window` type. Read it through a small typed accessor that lists only the methods this page calls:

src/lib/engagement.ts

```typescript
// The window.engagement methods this page calls, as the Guides and Surveys Web SDK docs define them.
type Engagement = {
  setRouter(fn: (url: string) => void): void;
  addCallback(name: string, fn: () => void): void;
  gs: {
    show(key: string, stepIndex?: number): void;
    closeAll(): void;
  };
  _debugStatus?: () => unknown;
};

export function getEngagement(): Engagement | undefined {
  return (window as unknown as { engagement?: Engagement }).engagement;
}
```

The accessor doesn't augment the global `Window` type, so it can't conflict with any declaration the SDK packages ship. Every file below calls `getEngagement()` instead of `window.engagement`.

On Nuxt, initialize in `app/plugins/amplitude.client.ts` (`plugins/` on Nuxt 3) as on [How to add product analytics to a Nuxt app](/build/nuxt-product-analytics), and keep SDK calls out of the server render. Register the router bridge from step 3 in a second client-only plugin that uses the router Nuxt provides, and have the init plugin `provide` the `initAll` promise so the bridge can wait on it (prefix its file name, such as `01.amplitude.client.ts`, so it runs first, or give both plugins a `name` and use `dependsOn`).

## 3. Connect guide buttons to Vue Router

A guide's **Visit link** action reloads the page unless you give the SDK a router. A **Run callback** action does nothing unless your code has registered a function with that name. Both need your router instance, so register them in a small Vue plugin that you install once in `src/main.ts`.

src/plugins/engagement.ts

```typescript
import type { Plugin } from 'vue';
import type { Router } from 'vue-router';
import { amplitudeReady } from '../lib/amplitude';
import { getEngagement } from '../lib/engagement';

export function createEngagementBridge(router: Router): Plugin {
  return {
    install() {
      amplitudeReady()
        .then(() => {
          const engagement = getEngagement();
          if (!engagement) return;

          // Guide links set to "Use router" call this instead of reloading.
          engagement.setRouter((url) => {
            const target = new URL(url, window.location.origin);
            if (target.origin !== window.location.origin) {
              window.location.assign(url);
              return;
            }

            // Drop the app's base path (Vite's `base`) so the router
            // doesn't add it a second time.
            const base = router.options.history.base;
            let path = target.pathname;
            if (base && (path === base || path.startsWith(`${base}/`))) {
              path = path.slice(base.length) || '/';
            }

            router.push(path + target.search + target.hash);
          });

          // Guide buttons with "Run callback" and the name open_new_project.
          engagement.addCallback('open_new_project', () => {
            router.push('/projects/new');
          });
        })
        .catch((err) => console.warn('Guides and surveys bridge not registered', err));
    },
  };
}
```

src/main.ts

```typescript
import './lib/amplitude';
import { createApp } from 'vue';
import App from './App.vue';
import router from './router';
import { createEngagementBridge } from './plugins/engagement';

createApp(App).use(router).use(createEngagementBridge(router)).mount('#app');
```

Then, in the builder, set the URL behavior on every **Visit link** action to **Use router**, and enter `open_new_project` as the name on any **Run callback** action. The docs are explicit: **Same tab** and **New tab** ignore the router you set, and the callback name must match the string passed to `addCallback`.

**Why.** The docs' examples register `setRouter` from a component (a React effect, an Angular root component). A Vue plugin gets the router instance directly, runs once when the app starts, and never unmounts, so the callback stays registered on every route. Waiting for `amplitudeReady()` means `window.engagement` exists when `setRouter` runs, and if `initAll` fails, the `.catch` logs a warning instead of leaving an unhandled promise rejection. The same-origin check keeps links to other sites as normal navigations, since `router.push` only handles routes inside your app. The base path check matters when Vite's `base` is set, for example an app served from `/app/`: the guide sends the full path, and `router.push` would otherwise prepend `/app` again.

## 4. Keep identity stable so guides show once

Amplitude remembers which guides and surveys a user has seen, completed, or dismissed as part of that user's history. The docs warn that if a user's identity changes between visits, Amplitude treats them as new and shows the experience again. `amplitude.reset()` issues a new device ID, so calling it whenever the user is `null`, which is every anonymous page load, would show an anonymous visitor the same welcome guide on every visit.

The `useAmplitudeIdentity` composable from the product analytics page already returns early when there's no user, so keep it as it is. Reset only from the sign-out handler, and close anything still on screen first:

src/auth/signOut.ts

```typescript
import * as amplitude from '@amplitude/unified';
import { getEngagement } from '../lib/engagement';

export async function signOut() {
  // ... your sign-out logic

  getEngagement()?.gs.closeAll(); // close anything still on screen
  amplitude.reset(); // clears the user ID and issues a new device ID
}
```

**Why.** Guides and surveys read the same user as analytics, so targeting sees the user ID and `plan` that the composable sets. The default bucketing unit for rollouts is User ID, so set the user ID before you rely on a percentage rollout.

## 5. Give pins a target that survives a rebuild

Pins, tooltips, and card embeds attach to an element by CSS selector, and the docs recommend data attributes added specifically for guide targeting. A selector the visual picker builds from generated class names, or from the `data-v-` attributes Vue adds for scoped styles, can change when the component changes, and then the pin stops rendering. Tag the elements you plan to point at:

src/views/HomeView\.vue

```html
<template>
  <section data-guide="home">
    <h1>Your projects</h1>
    <RouterLink to="/projects/new" data-guide="new-project-button">
      New project
    </RouterLink>
  </section>
</template>
```

`RouterLink` passes attributes it doesn't recognize through to the `<a>` it renders, so the selector matches the link itself. In the guide builder, add a Pin step and enter `[data-guide="new-project-button"]` as its CSS selector instead of the generated one.

**Why.** The docs say that if the target element isn't in the DOM when the step renders, the pin doesn't display, and the SDK doesn't move it to another position. In a Vue app, anything behind a lazy route component, `defineAsyncComponent`, `<Suspense>`, or a `v-if` that waits for data can render after the guide triggers. For those, set the trigger to **When element appears** with the same selector, so the guide fires when the element renders rather than when the page loads. On a later step in a tour, turn on **Skip if target not found**, which moves on if the target doesn't appear within 250 ms.

## 6. Open a survey from your own button

When a survey should open because someone clicked something, the docs recommend setting its trigger to **None** in the builder and calling `show` from the click handler.

src/components/FeedbackButton.vue

```html
<script setup lang="ts">
import { getEngagement } from '../lib/engagement';

function openFeedback() {
  getEngagement()?.gs.show('product-feedback');
}
</script>

<template>
  <button type="button" @click="openFeedback">Give feedback</button>
</template>
```

Render it in `App.vue`, next to the identity composable from the product analytics page:

src/App.vue

```html
<script setup lang="ts">
import { useAuth } from './auth/useAuth'; // your auth composable
import { useAmplitudeIdentity } from './composables/useAmplitudeIdentity';
import FeedbackButton from './components/FeedbackButton.vue';

const { user } = useAuth();
useAmplitudeIdentity(user);
</script>

<template>
  <header>
    <FeedbackButton />
  </header>
  <RouterView />
</template>
```

Replace `product-feedback` with your survey's key from the builder. `show` ignores targeting rules and limits but still respects page targeting, so the survey's Where settings must allow every route where the button renders.

If you'd rather use the **On event tracked** trigger, the event has to fire in the browser. `Project Created`, tracked with `amplitude.track` on the product analytics page, works. An event your backend sends never reaches the guides and surveys SDK; call `show` from the client after that request resolves instead.

**MCP.** Working in Cursor or Claude Code? Amplitude's MCP server includes `list_guides_surveys` and `get_guide_or_survey`, which list your project's guides and surveys and return each one's page targeting and trigger configuration, so your agent can check that the key it wires into `show` exists and is allowed on that route. [Set up the MCP server](https://amplitude.com/docs/amplitude-ai/amplitude-mcp).

## Verify it worked

Run `npm run dev` and sign in as a test user. In Amplitude, create a guide with a Pin step on `[data-guide="new-project-button"]`, set to **Open by default**, and a button whose action is **Run callback** with `open_new_project`, and a survey with the key `product-feedback` and the trigger **None**. Add your test user under **Test users** and set both to **Testing**, or click **Preview** and enter your local URL. Then, in the app, open the pin, click its button, and submit the survey from **Give feedback**. Check in this order:

1. **Network tab.** Filter for `amplitude.com`. You should see requests to `gs.amplitude.com`, the default guides and surveys host, and the analytics SDK's `POST` to `api2.amplitude.com/2/httpapi` returning `200`.
2. **Ingestion Debugger.** In Amplitude, click Data, select Sources, then open the Ingestion Debugger tab. The successful requests chart should show your uploads. It shows counts, not individual events.
3. **User lookup.** Go to Users > User Profiles and search your `user-` prefixed test ID. Within ten seconds to a minute the event stream shows `Guide Viewed` for the pin (a pin that isn't open by default skips `Guide Viewed` when someone clicks its target), and `Survey Viewed` and `Survey Submitted` for the survey you answered. Guides and surveys events don't count toward your event volume.
4. **Browser console.** `window.engagement` returns an object, not `undefined`. Run `window.engagement._debugStatus()`: `user.user_id` should be your `user-` prefixed ID, `apiKey` should be set, `stateInitialized` and `decideSuccessful` should be `true`, and `num_guides_surveys` should be non-zero on the home page.
5. **Amplitude Chrome extension.** The Guides & Surveys tab shows four setup checks: **Installed**, **Initialized**, **Analytics Connected**, and **Booted**. Let the page finish loading first, since the tab doesn't update live.
6. **In the app.** The pin appeared on **New project**, its button navigated to `/projects/new` without a document request in the Network tab, and **Give feedback** opened the survey.

A guide you already completed or dismissed won't show again. Open your user profile, go to the Guides or Surveys tab, and select **Clear history** for that experience, or click **Reset User History** in the preview bar.

## Troubleshooting

### `window.engagement` is undefined

Guides and surveys never initialized. Confirm `import './lib/amplitude'` is the first line of `src/main.ts`, that `VITE_AMPLITUDE_API_KEY` is set (Vite reads `.env.local` only at startup, so restart the dev server), and that nothing passes `engagement: { skip: true }`.

### A guide set to Immediately shows twice, or not at all

Something calls `engagement.boot()` or `engagement.init()` on top of `initAll`, often in a component's `onMounted` that runs on every mount. Delete those calls.

### Anonymous visitors see the same welcome guide on every visit

`amplitude.reset()` runs while the user is signed out and issues a new device ID, so every visit looks like a new user. Keep the early return in `useAmplitudeIdentity` and call `reset()` only in `signOut()`.

### The pin never appears, but the survey button works

The target element isn't in the DOM when the step renders, or the selector no longer matches. Check `document.querySelector('[data-guide="new-project-button"]')` in the console, and for late-rendered elements use the **When element appears** trigger.

### Guide links reload the whole page

The link's URL behavior isn't **Use router**, or `createEngagementBridge(router)` isn't installed in `src/main.ts`. Run `Object.keys(window.engagement._.callbacks)` in the console; `engagement-router` should be in the list once `setRouter` has run.

### Guide links land on a 404 with the base path twice, like `/app/app/projects/new`

Your app sets Vite's `base`, and the bridge is pushing the full pathname. Keep the base path check from step 3, and create the router with `createWebHistory(import.meta.env.BASE_URL)` so `router.options.history.base` matches.

### A Run callback button does nothing

The name in the builder doesn't match the `addCallback` string. Run `Object.keys(window.engagement._.callbacks)` in the console; `open_new_project` should be in the list, next to internal entries like `engagement-router`.

### Preview never opens on your page

Your server sends `Cross-Origin-Opener-Policy: same-origin`, which blocks the messages Preview mode depends on. Set it to `same-origin-allow-popups`, as in step 1.

## Full code reference

Every file from this tutorial in one block, the version to hand to an agent. It assumes no CSP nonce. 9 files, plus a note on the router you already have. `src/composables/useAmplitudeIdentity.ts` stays as it is on the [Vue product analytics page](/build/vue-product-analytics).

10 files, 163 lines

```typescript
// -- .env.local --
VITE_AMPLITUDE_API_KEY=your-project-api-key

// -- src/lib/amplitude.ts --
import * as amplitude from '@amplitude/unified';

const apiKey = import.meta.env.VITE_AMPLITUDE_API_KEY;

let ready: Promise<void> = Promise.resolve();

if (apiKey) {
  ready = Promise.resolve(
    amplitude.initAll(apiKey, {
      analytics: {
        autocapture: {
          attribution: true,
          pageViews: true,
          sessions: true,
          formInteractions: true,
          fileDownloads: true,
          elementInteractions: true,
        },
      sessionReplay: {
        sampleRate: import.meta.env.PROD ? 0.01 : 1,
        debugMode: !import.meta.env.PROD,
        // From /build/vue-session-replay. Keep your masking rules when you paste this file.
        privacyConfig: {
          maskSelector: ['[data-amp-mask]'],
          blockSelector: ['[data-amp-block]'],
          unmaskSelector: ['[data-amp-unmask]'],
        },
    }),
  ).then(() => undefined);
} else {
  console.warn('VITE_AMPLITUDE_API_KEY is not set; analytics is off.');
}

export function amplitudeReady() {
  return ready;
}

export { amplitude };

// -- src/lib/engagement.ts --
type Engagement = {
  setRouter(fn: (url: string) => void): void;
  addCallback(name: string, fn: () => void): void;
  gs: {
    show(key: string, stepIndex?: number): void;
    closeAll(): void;
  };
  _debugStatus?: () => unknown;
};

export function getEngagement(): Engagement | undefined {
  return (window as unknown as { engagement?: Engagement }).engagement;
}

// -- src/plugins/engagement.ts --
import type { Plugin } from 'vue';
import type { Router } from 'vue-router';
import { amplitudeReady } from '../lib/amplitude';
import { getEngagement } from '../lib/engagement';

export function createEngagementBridge(router: Router): Plugin {
  return {
    install() {
      amplitudeReady()
        .then(() => {
          const engagement = getEngagement();
          if (!engagement) return;

          engagement.setRouter((url) => {
            const target = new URL(url, window.location.origin);
            if (target.origin !== window.location.origin) {
              window.location.assign(url);
              return;
            }

            const base = router.options.history.base;
            let path = target.pathname;
            if (base && (path === base || path.startsWith(`${base}/`))) {
              path = path.slice(base.length) || '/';
            }

            router.push(path + target.search + target.hash);
          });

          engagement.addCallback('open_new_project', () => {
            router.push('/projects/new');
          });
        })
        .catch((err) => console.warn('Guides and surveys bridge not registered', err));
    },
  };
}

// -- src/main.ts --
import './lib/amplitude';
import { createApp } from 'vue';
import App from './App.vue';
import router from './router';
import { createEngagementBridge } from './plugins/engagement';

createApp(App).use(router).use(createEngagementBridge(router)).mount('#app');

// -- src/auth/signOut.ts --
import * as amplitude from '@amplitude/unified';
import { getEngagement } from '../lib/engagement';

export async function signOut() {
  // ... your sign-out logic

  getEngagement()?.gs.closeAll();
  amplitude.reset();
}

// -- src/views/HomeView.vue --
// Example: add the data-guide attributes to your own markup.
<template>
  <section data-guide="home">
    <h1>Your projects</h1>
    <RouterLink to="/projects/new" data-guide="new-project-button">
      New project
    </RouterLink>
  </section>
</template>

// -- src/components/FeedbackButton.vue --
<script setup lang="ts">
import { getEngagement } from '../lib/engagement';

function openFeedback() {
  getEngagement()?.gs.show('product-feedback');
}
</script>

<template>
  <button type="button" @click="openFeedback">Give feedback</button>
</template>

// -- src/App.vue --
<script setup lang="ts">
import { useAuth } from './auth/useAuth';
import { useAmplitudeIdentity } from './composables/useAmplitudeIdentity';
import FeedbackButton from './components/FeedbackButton.vue';

const { user } = useAuth();
useAmplitudeIdentity(user);
</script>

<template>
  <header>
    <FeedbackButton />
  </header>
  <RouterView />
</template>

// -- src/router/index.ts --
// Your existing router. It must use createWebHistory(import.meta.env.BASE_URL)
// and have a /projects/new route.
```

## Hand it to your agent

Prefer to have Cursor, Claude Code, or Codex CLI do the wiring? This prompt points the agent at this page and pins the decisions that matter.

Using Cursor or Claude Code? Paste this prompt

```text
Add Amplitude guides and surveys to this Vue, Vite, and Vue Router app by
following https://amplitude.com/build/vue-guides-surveys exactly. Use
@amplitude/unified and call initAll once at module scope in
src/lib/amplitude.ts, imported first in src/main.ts, keeping any
sessionReplay.privacyConfig already there, and export an amplitudeReady()
promise. Never call engagement.init() or engagement.boot(), and never
initialize in onMounted. Read window.engagement only through a typed
getEngagement() helper in src/lib/engagement.ts; don't augment the global
Window type. Add a Vue plugin, createEngagementBridge(router), installed
in src/main.ts, that waits for amplitudeReady(), catches a rejection with
a console warning, then calls setRouter with router.push for same-origin
URLs (stripping the router's base path) and registers callbacks with
addCallback. Don't replace my router. Call amplitude.reset() only in the
sign-out handler, never when the user is null. Don't replace my views;
add data-guide attributes to existing elements I want guides to point at.
Open surveys from click handlers
with getEngagement()?.gs.show(key). Read VITE_AMPLITUDE_API_KEY from the
environment and don't hardcode it. Tell me session replay records every
session in development and about 1% in production, and how to turn it
off. Report every file you changed.
```

## What this unlocks

- [Session replay](https://amplitude.com/session-replay), on at the sample rate from step 2, lets you watch what users did right after they submitted `product-feedback`.
- [Heatmaps](https://amplitude.com/heatmaps) are built from session replays, so they show whether people click `new-project-button` more after the pin goes live.
- [Feature Experiment](https://amplitude.com/amplitude-experiment), bundled in the same package, can gate the feature a guide announces, so the flag and the guide reach the same users.
- [Product analytics](https://amplitude.com/amplitude-analytics) charts `Guide Viewed` next to `Project Created`, so you can see whether the pin moves project creation.

One integration; everything else reads from the same events.

## Next steps

[Web frameworksHow to add feature flags and experiments to a Vue appGate the feature a guide announces behind a flag.](/build/vue-feature-flags)[Web frameworksHow to add session replay to a Vue appRecord the sessions around a guide or survey, with the masking a Vue app needs.](/build/vue-session-replay)[Web frameworksHow to add heatmaps to a Vue appSee where people click before and after a pin goes live.](/build/vue-heatmaps)

Reference docs

[Guides and Surveys Web SDK →](https://amplitude.com/docs/sdks/guides-and-surveys/sdk)[Browser Unified SDK →](https://amplitude.com/docs/sdks/analytics/browser/browser-unified-sdk)[Setup and Targeting →](https://amplitude.com/docs/guides-and-surveys/setup-and-target)[Testing and Publishing →](https://amplitude.com/docs/guides-and-surveys/testing)[Guide or Survey not showing →](https://amplitude.com/docs/guides-and-surveys/troubleshooting/guide-or-survey-not-showing)[Amplitude MCP server →](https://amplitude.com/docs/amplitude-ai/amplitude-mcp)

Amplitude is the leading [all-in-one analytics platform](https://amplitude.com/) for building better products. Product and engineering teams use Amplitude to connect [product analytics](https://amplitude.com/amplitude-analytics) and [web analytics](https://amplitude.com/web-analytics) with [session replay](https://amplitude.com/session-replay), [heatmaps](https://amplitude.com/heatmaps), [experimentation](https://amplitude.com/amplitude-experiment), [feature flags](https://amplitude.com/feature-management), and [guides and surveys](https://amplitude.com/guides-and-surveys) in one unified workflow, with [AI agents](https://amplitude.com/ai-agents) working across all of it. Learn how Amplitude is building [self-improving products](https://amplitude.com/self-improving-products) with [Wave](https://amplitude.com/wave), helping teams turn product data into PRs.

For agents

[View raw .md](/build/vue-guides-surveys.md)[llms.txt](/llms.txt)

## Ship the instrumentation, then read the data

Once events are flowing, Amplitude turns them into funnels, retention curves, and session replays without any further setup.

[Create a free account](https://app.amplitude.com/signup)[Browse all Build tutorials](https://amplitude.com/build)
