Vue

Add waitlists to your Vue apps the progressive way

Simple, reactive, and developer-friendly. Works with Vue 3 Composition API, Options API, and Nuxt. Drop in a component or build custom forms with Vue's powerful reactivity. Vite-ready and production-tested.

Vue + Waitlister

Trusted by 2,000+
businesses & entrepreneurs

Data Hokage logo
Data Hokage
Fink Academy logo
Fink Academy
stagewise logo
stagewise
Sirius AI logo
Sirius AI
BLADNA logo
BLADNA
PagePal logo
PagePal
ChatAce.io logo
ChatAce.io
Instanote logo
Instanote
DirectoryDeck logo
DirectoryDeck
landman® logo
landman®
datapro logo
datapro
NATRU logo
NATRU
Pop Date logo
Pop Date
Aspire logo
Aspire
WalletX logo
WalletX
quickblogs logo
quickblogs
Data Hokage logo
Data Hokage
Fink Academy logo
Fink Academy
stagewise logo
stagewise
Sirius AI logo
Sirius AI
BLADNA logo
BLADNA
PagePal logo
PagePal
ChatAce.io logo
ChatAce.io
Instanote logo
Instanote
DirectoryDeck logo
DirectoryDeck
landman® logo
landman®
datapro logo
datapro
NATRU logo
NATRU
Pop Date logo
Pop Date
Aspire logo
Aspire
WalletX logo
WalletX
quickblogs logo
quickblogs
“Waitlister provides a one-of-a-kind builder platform to design your own waitlist page that actually stands out from competitors. Once the page is ready and deployed, we never worry about uptime or data reliability.”
KeiKo logo
Abhishek Mangudkar
Founder, KeiKo
Use Cases

What you can build

Popular ways Vue users implement waitlists

SaaS Dashboard Launches

Build waitlist flows into your Vue-powered admin dashboards and SaaS applications with reactive state.

Example: In-app waitlist modal for beta features with Composition API

Marketing Sites with Nuxt

Server-rendered landing pages with Nuxt 3 for SEO-perfect pre-launch campaigns.

Example: Nuxt static site with waitlist and social proof sections

Progressive Enhancement

Add Vue incrementally to existing sites with waitlist components that just work.

Example: Enhance legacy site by sprinkling Vue waitlist component on landing page

Mobile Apps with Quasar

Build cross-platform mobile apps with waitlist functionality using Quasar Framework.

Example: Mobile-first PWA with native-feeling waitlist signup flow

Admin Panels & Internal Tools

Add feature request waitlists or beta program signups to internal Vue admin panels.

Example: Employee portal with internal tool waitlist and approval workflow

Component Libraries

Build reusable waitlist components with Vue's powerful composition and props system.

Example: Design system waitlist component with variants and customization
Benefits

Why Waitlister for Vue?

Built to work seamlessly with Vue's capabilities

Reactive by Default

Vue's reactivity system makes form state management effortless. Automatic updates, computed properties, and watchers work seamlessly with waitlist forms.

Composition API Ready

Modern composables pattern with <script setup>. Reusable waitlist logic with useWaitlist() composable. Works with Options API too for backward compatibility.

Single-File Components

Everything in one .vue file - template, logic, and styles. Better DX than JSX for many developers. Scoped styles prevent CSS conflicts.

Vite-Powered Speed

Lightning-fast HMR with Vite. Instant feedback during development. Optimized production builds with automatic code splitting.

Nuxt 3 Compatible

Full support for Nuxt 3 with SSR, SSG, and auto-imports. Perfect for SEO-optimized landing pages and marketing sites.

State Management Built-in

Works beautifully with Pinia, Vuex, or Vue's built-in reactivity. Share waitlist state across your entire app effortlessly.

Choose Your Method

Which integration is
right for you?

Compare both methods to find the best fit for your Vue project

FeatureForm ActionEmbeddable Widget
Setup Time~10 minutes~5 minutes
ReactivityFull Vue reactivityInternal
Composition APIFull supportN/A
Options APIFull supportN/A
TypeScriptFull type safetyBasic
CustomizationComplete controlDashboard only
Best ForProduction appsQuick launches

Choose Form Action if...

  • You want full control with Vue reactivity
  • You're using Composition API or Options API
  • You need custom validation or complex logic
  • You want to integrate with Pinia or Vuex
  • You're building a production Vue application
  • You need type-safe forms with TypeScript

Choose Embeddable Widget if...

  • You want the fastest possible setup
  • You're building a simple landing page
  • You don't need deep Vue integration
  • You prefer managing form design externally
  • You're prototyping or testing an idea
Step-by-Step Guide

How to integrate

Follow these Vue-specific instructions

1

Set up environment variables

Create a .env file in your project root:

# .env
VITE_WAITLIST_KEY=your_waitlist_key_here

# For Nuxt:
# NUXT_PUBLIC_WAITLIST_KEY=your_waitlist_key_here
Pro tip
Vite uses VITE_ prefix, Nuxt uses NUXT_PUBLIC_ prefix for client-accessible variables.
2

Create waitlist composable (Composition API)

Build a reusable composable for waitlist logic:

// composables/useWaitlist.ts
import { ref } from 'vue';

export interface WaitlistData {
  email: string;
  name?: string;
}

export const useWaitlist = () => {
  const loading = ref(false);
  const success = ref(false);
  const error = ref<string | null>(null);

  const submitToWaitlist = async (data: WaitlistData) => {
    loading.value = true;
    error.value = null;

    try {
      const response = await fetch(
        `https://waitlister.me/s/${import.meta.env.VITE_WAITLIST_KEY}`,
        {
          method: 'POST',
          headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
          body: new URLSearchParams(data as any)
        }
      );

      if (!response.ok) {
        throw new Error('Submission failed');
      }

      success.value = true;
      return { success: true };
    } catch (err) {
      error.value = 'Failed to join waitlist. Please try again.';
      return { success: false, error: error.value };
    } finally {
      loading.value = false;
    }
  };

  const reset = () => {
    loading.value = false;
    success.value = false;
    error.value = null;
  };

  return {
    loading,
    success,
    error,
    submitToWaitlist,
    reset
  };
};
Pro tip
Composables are reusable across your entire Vue app. Import and use in any component.
3

Create waitlist form component

Build a reactive form component using the composable:

<!-- components/WaitlistForm.vue -->
<template>
  <div v-if="success" class="success-message">
    <h3>You're on the list! 🎉</h3>
    <p>We'll notify you when we launch.</p>
  </div>

  <form v-else @submit.prevent="handleSubmit" class="waitlist-form">
    <input
      v-model="formData.email"
      type="email"
      placeholder="Your email"
      required
    />
    <input
      v-model="formData.name"
      type="text"
      placeholder="Your name (optional)"
    />
    <button type="submit" :disabled="loading">
      {{ loading ? 'Joining...' : 'Join Waitlist' }}
    </button>
    <div v-if="error" class="error-message">{{ error }}</div>
  </form>
</template>

<script setup>
import { reactive } from 'vue';
import { useWaitlist } from '@/composables/useWaitlist';

const { loading, success, error, submitToWaitlist } = useWaitlist();

const formData = reactive({
  email: '',
  name: ''
});

const handleSubmit = async () => {
  await submitToWaitlist(formData);
  if (success.value) {
    formData.email = '';
    formData.name = '';
  }
};
</script>

<style scoped>
.waitlist-form {
  max-width: 500px;
  margin: 0 auto;
}

.waitlist-form input {
  width: 100%;
  padding: 12px 16px;
  margin-bottom: 12px;
  border: 1px solid #ddd;
  border-radius: 4px;
  font-size: 16px;
}

.waitlist-form button {
  width: 100%;
  padding: 14px 24px;
  background: #42b883;
  color: white;
  border: none;
  border-radius: 4px;
  font-size: 16px;
  font-weight: 600;
  cursor: pointer;
  transition: background 0.3s;
}

.waitlist-form button:hover:not(:disabled) {
  background: #33a06f;
}

.waitlist-form button:disabled {
  opacity: 0.6;
  cursor: not-allowed;
}

.error-message {
  margin-top: 12px;
  padding: 12px;
  background: #fee;
  color: #c33;
  border-radius: 4px;
}

.success-message {
  text-align: center;
  padding: 2rem;
}
</style>
Pro tip
v-model creates two-way binding automatically. reactive() makes the entire object reactive.
4

Options API version

If you prefer Options API, here's the equivalent:

<!-- components/WaitlistForm.vue -->
<template>
  <div v-if="success" class="success-message">
    <h3>You're on the list! 🎉</h3>
    <p>We'll notify you when we launch.</p>
  </div>

  <form v-else @submit.prevent="handleSubmit" class="waitlist-form">
    <input
      v-model="email"
      type="email"
      placeholder="Your email"
      required
    />
    <input
      v-model="name"
      type="text"
      placeholder="Your name (optional)"
    />
    <button type="submit" :disabled="loading">
      {{ loading ? 'Joining...' : 'Join Waitlist' }}
    </button>
    <div v-if="error" class="error-message">{{ error }}</div>
  </form>
</template>

<script>
export default {
  name: 'WaitlistForm',
  data() {
    return {
      email: '',
      name: '',
      loading: false,
      success: false,
      error: null
    };
  },
  methods: {
    async handleSubmit() {
      this.loading = true;
      this.error = null;

      try {
        const response = await fetch(
          `https://waitlister.me/s/${import.meta.env.VITE_WAITLIST_KEY}`,
          {
            method: 'POST',
            headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
            body: new URLSearchParams({
              email: this.email,
              name: this.name
            })
          }
        );

        if (!response.ok) throw new Error('Submission failed');

        this.success = true;
        this.email = '';
        this.name = '';
      } catch (err) {
        this.error = 'Failed to join waitlist. Please try again.';
      } finally {
        this.loading = false;
      }
    }
  }
};
</script>
Pro tip
Options API is more familiar to developers coming from Vue 2 or other frameworks.
5

Integrate with Pinia store

For global state management, use Pinia:

// stores/waitlist.ts
import { defineStore } from 'pinia';
import { ref } from 'vue';

export const useWaitlistStore = defineStore('waitlist', () => {
  const signups = ref<string[]>([]);
  const loading = ref(false);
  const error = ref<string | null>(null);

  async function submit(email: string, name?: string) {
    loading.value = true;
    error.value = null;

    try {
      const response = await fetch(
        `https://waitlister.me/s/${import.meta.env.VITE_WAITLIST_KEY}`,
        {
          method: 'POST',
          headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
          body: new URLSearchParams({ email, name: name || '' })
        }
      );

      if (!response.ok) throw new Error('Failed');

      signups.value.push(email);
      return true;
    } catch (err) {
      error.value = 'Failed to join waitlist';
      return false;
    } finally {
      loading.value = false;
    }
  }

  return { signups, loading, error, submit };
});

// Use in component:
// import { useWaitlistStore } from '@/stores/waitlist';
// const waitlist = useWaitlistStore();
// await waitlist.submit(email, name);
Pro tip
Pinia stores are reactive and can be used across your entire app. Perfect for shared state.
6

Nuxt 3 server API route

For Nuxt 3, create a server route for additional backend logic:

// server/api/waitlist.post.ts
export default defineEventHandler(async (event) => {
  const body = await readBody(event);
  const { email, name } = body;

  // Validation
  if (!email || !email.includes('@')) {
    throw createError({
      statusCode: 400,
      message: 'Invalid email'
    });
  }

  // Forward to Waitlister
  try {
    const response = await $fetch(
      `https://waitlister.me/s/${process.env.WAITLIST_KEY}`,
      {
        method: 'POST',
        headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
        body: new URLSearchParams({ email, name })
      }
    );

    return { success: true, message: 'Joined waitlist' };
  } catch (error) {
    throw createError({
      statusCode: 500,
      message: 'Failed to join waitlist'
    });
  }
});

// Call from component:
// const { data, error } = await useFetch('/api/waitlist', {
//   method: 'POST',
//   body: { email, name }
// });
Pro tip
Server API routes in Nuxt run on the server, perfect for sensitive operations or custom logic.
7

Add form validation with VeeValidate

For production-grade validation, integrate VeeValidate:

// npm install vee-validate yup
<template>
  <form @submit="onSubmit" class="waitlist-form">
    <input
      v-model="email"
      type="email"
      placeholder="Your email"
      :class="{ error: errors.email }"
    />
    <span v-if="errors.email" class="error">{{ errors.email }}</span>
    
    <input
      v-model="name"
      type="text"
      placeholder="Your name"
    />
    
    <button type="submit" :disabled="isSubmitting">
      {{ isSubmitting ? 'Joining...' : 'Join Waitlist' }}
    </button>
  </form>
</template>

<script setup>
import { useForm } from 'vee-validate';
import * as yup from 'yup';
import { useWaitlist } from '@/composables/useWaitlist';

const schema = yup.object({
  email: yup.string().required().email(),
  name: yup.string()
});

const { errors, handleSubmit, defineField, isSubmitting } = useForm({
  validationSchema: schema
});

const [email] = defineField('email');
const [name] = defineField('name');

const { submitToWaitlist } = useWaitlist();

const onSubmit = handleSubmit(async (values) => {
  await submitToWaitlist(values);
});
</script>
Pro tip
VeeValidate provides robust validation with great DX. Integrates perfectly with Vue's reactivity.

Need more details?

Check out our complete form action endpoint documentation.

View full documentation
Troubleshooting

Common issues & solutions

Quick fixes for Vue-specific problems

Ensure script is in public/index.html before </body> tag. Vite serves public folder as-is. Check browser Network tab to verify script loads. For Nuxt, add script to nuxt.config.ts app.head.script array.

Vite requires VITE_ prefix for client-accessible variables. Nuxt uses NUXT_PUBLIC_ prefix. Restart dev server after adding new variables. Check .env is in project root and in .gitignore.

Make sure you're using reactive data (ref, reactive, computed). For Options API, check data properties are defined correctly. Vue 3 reactivity requires proper use of ref() or reactive().

Verify the div has exact class name "waitlister-form". Check browser console for errors. Ensure script loaded before component mounted. The widget needs the script to be present on page load.

Add your domain to Waitlister's whitelisted domains. Include localhost:5173 (or your Vite port) for development, localhost:3000 for Nuxt. Check browser console for specific blocked domain.

Ensure proper type definitions for composables. Use TypeScript's satisfies operator or explicit return types. Check tsconfig.json includes your composables directory.

Nuxt auto-imports components from components/ directory. Ensure file is in the right place. Restart Nuxt dev server. Check .nuxt/types for generated types.

All reactive data must be defined in data() function. Don't assign properties outside data(). Use this.$set for adding properties dynamically in Vue 2 (not needed in Vue 3).

Pinia stores are reactive but not persistent by default. Install pinia-plugin-persistedstate for localStorage persistence, or manually save to localStorage in your submit method.

FAQ

Common questions

About Vue integration

Yes! Works with both Vue 3 (Composition API and Options API) and Vue 2 (Options API). Vue 3 is recommended for new projects with better TypeScript support and performance.

Both! We provide examples for both styles. Composition API with <script setup> is more modern and concise. Options API is familiar and still fully supported in Vue 3.

Absolutely! Full Nuxt 3 support with SSR, SSG, and auto-imports. Works with Nuxt's file-based routing, server API routes, and useRuntimeConfig for env variables.

Yes! Vue 3 + Vite is the recommended setup. Lightning-fast HMR, optimized builds, and great DX. All our examples use Vite conventions (import.meta.env, etc.).

Create a Pinia store for waitlist state management. Use the store across your entire app for consistent state. Perfect for tracking signup status, showing success messages, or managing multiple waitlists.

Yes! Quasar is built on Vue, so all integration methods work. Use Quasar components for beautiful mobile-first forms. Perfect for building cross-platform apps with waitlists.

Absolutely! Full TypeScript support with proper types for composables, props, and events. Use defineProps<T>() and defineEmits<T>() for type-safe components.

Use VeeValidate for robust validation, or build custom validation with computed properties and watchers. Vue's reactivity makes validation elegant and straightforward.

Yes! Works perfectly with Vue Router. Add waitlist forms to any route, create dedicated /launch pages, or use route meta for conditional displays. All router integrations work out of the box.

Yes! Both methods work with UI frameworks. For custom forms, use their form components and connect to Waitlister. For embed, place it anywhere in your component tree.

Use @vue/test-utils for unit testing. Mock the fetch calls in your tests. Test form submission, validation, error states, and success flows. All standard Vue testing practices apply.

Yes! The embed widget works with SSR. For custom forms, use client-only components or useClientOnly() if needed. Server API routes work great for backend logic in Nuxt.

Explore

More integrations

Explore other platforms

Get started for free

Start collecting sign ups for your product launch in minutes — no coding required.