# Waitlist signup form template

A one-field waitlist form for collecting emails before launch. Copy the HTML, CSS and JS, or use it in React, Next.js, Vue, Svelte or Astro.

This form has a single email field and a button. Use it to collect a launch list on a landing page, a coming soon page or a product demo.

Live preview, field editor, four themes and styled versions for every framework: https://nisuform.com/templates/waitlist-form

## Fields

| Field | Name | Type | Required |
| --- | --- | --- | --- |
| Email | `email` | Email | Yes |

## How to use it

1. Create a free form at https://app.nisuform.com and copy its form key.
2. Paste the code where the form should appear and replace `YOUR_FORM_KEY` with your key.
3. Submit the form once. The submission appears in your Nisuform inbox and you get an email.

## Styled HTML, CSS and JavaScript

The form with its styles and a small script that sends it without a page reload and shows the success message.

```html
<form id="nf-waitlist-form" class="nf-form" action="https://api.nisuform.com/s/YOUR_FORM_KEY" method="POST">
  <style>
#nf-waitlist-form {
  --nf-accent: #e60058;
  --nf-on-accent: #ffffff;
  --nf-text: #171717;
  --nf-muted: #525252;
  --nf-field: #f5f5f5;
  --nf-field-text: #171717;
  --nf-placeholder: #6b6b6b;
  --nf-control: #8a8a8a;
  --nf-error: #b91c1c;
  color-scheme: light;
  box-sizing: border-box;
  width: 100%;
  max-width: 480px;
  margin: 0;
  padding: 28px;
  border: 1px solid rgba(0, 0, 0, 0.08);
  border-radius: 16px;
  background: linear-gradient(180deg, #ffffff, #ffffff);
  box-shadow: 0 16px 40px -16px rgba(0, 0, 0, 0.12);
  color: var(--nf-text);
  font: inherit;
  font-size: 16px;
  line-height: 1.5;
  text-align: left;
}
#nf-waitlist-form *, #nf-waitlist-form *::before, #nf-waitlist-form *::after { box-sizing: border-box; }
#nf-waitlist-form [hidden] { display: none !important; }
#nf-waitlist-form .nf-title { margin: 0; font-size: 1.25em; font-weight: 650; line-height: 1.25; letter-spacing: -0.01em; color: var(--nf-text); }
#nf-waitlist-form .nf-desc { margin: 6px 0 0; font-size: 0.9375em; color: var(--nf-muted); }
#nf-waitlist-form .nf-fields { display: grid; gap: 18px; margin-top: 22px; }
#nf-waitlist-form .nf-head + .nf-fields { margin-top: 22px; }
#nf-waitlist-form > .nf-fields:first-child { margin-top: 0; }
#nf-waitlist-form .nf-field { margin: 0; padding: 0; border: 0; min-width: 0; }
#nf-waitlist-form .nf-label { display: block; margin: 0 0 6px; padding: 0; font-size: 0.875em; font-weight: 600; color: var(--nf-text); }
#nf-waitlist-form .nf-req { color: var(--nf-muted); font-weight: 400; }
#nf-waitlist-form .nf-help { margin: 6px 0 0; font-size: 0.8125em; color: var(--nf-muted); }
#nf-waitlist-form .nf-input { display: block; width: 100%; margin: 0; padding: 10px 12px; border: 1px solid transparent; border-radius: 10px; background: var(--nf-field); color: var(--nf-field-text); font: inherit; font-size: 1em; line-height: 1.4; outline: 2px solid transparent; outline-offset: 1px; transition: border-color 150ms, box-shadow 150ms; }
#nf-waitlist-form input.nf-input, #nf-waitlist-form select.nf-input { height: 2.75em; padding-top: 0; padding-bottom: 0; }
#nf-waitlist-form textarea.nf-input { min-height: 112px; padding-top: 10px; padding-bottom: 10px; resize: vertical; }
#nf-waitlist-form .nf-input::placeholder { color: var(--nf-placeholder); opacity: 1; }
#nf-waitlist-form .nf-input:hover { border-color: color-mix(in srgb, var(--nf-text) 14%, transparent); }
#nf-waitlist-form .nf-input:focus { border-color: var(--nf-accent); box-shadow: 0 0 0 3px color-mix(in srgb, var(--nf-accent) 24%, transparent); }
#nf-waitlist-form .nf-input:user-invalid { border-color: var(--nf-error); }
#nf-waitlist-form .nf-input:user-invalid:focus { box-shadow: 0 0 0 3px color-mix(in srgb, var(--nf-error) 24%, transparent); }
#nf-waitlist-form .nf-input:-webkit-autofill { -webkit-text-fill-color: var(--nf-field-text); caret-color: var(--nf-field-text); box-shadow: inset 0 0 0 100px var(--nf-field); }
#nf-waitlist-form .nf-input:-webkit-autofill:focus { box-shadow: inset 0 0 0 100px var(--nf-field), 0 0 0 3px color-mix(in srgb, var(--nf-accent) 24%, transparent); }
#nf-waitlist-form input[type="number"].nf-input { -moz-appearance: textfield; appearance: textfield; }
#nf-waitlist-form input[type="number"].nf-input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
#nf-waitlist-form input[type="number"].nf-input::-webkit-outer-spin-button { -webkit-appearance: none; margin: 0; }
#nf-waitlist-form input[type="date"].nf-input { min-width: 0; text-align: left; }
#nf-waitlist-form input[type="date"].nf-input::-webkit-date-and-time-value { text-align: left; }
#nf-waitlist-form input[type="date"].nf-input.nf-empty:not(:focus) { color: var(--nf-placeholder); }
#nf-waitlist-form input[type="date"].nf-input::-webkit-calendar-picker-indicator { margin: 0; padding: 4px; border-radius: 6px; opacity: 0.55; cursor: pointer; transition: opacity 150ms; }
#nf-waitlist-form input[type="date"].nf-input::-webkit-calendar-picker-indicator:hover { opacity: 1; }
#nf-waitlist-form select.nf-input { -webkit-appearance: none; appearance: none; padding-right: 40px; background-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20stroke%3D%22%23525252%22%20stroke-width%3D%222%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Cpath%20d%3D%22M6%209l6%206l6%20-6%22%2F%3E%3C%2Fsvg%3E"); background-repeat: no-repeat; background-position: right 12px center; background-size: 16px 16px; cursor: pointer; }
#nf-waitlist-form select.nf-input:has(option[value=""]:checked) { color: var(--nf-placeholder); }
#nf-waitlist-form select.nf-input option { color: var(--nf-field-text); }
#nf-waitlist-form .nf-options { display: grid; gap: 8px; }
#nf-waitlist-form .nf-option { display: flex; align-items: center; gap: 10px; min-height: 2.75em; margin: 0; padding: 10px 12px; border: 1px solid color-mix(in srgb, var(--nf-text) 14%, transparent); border-radius: 10px; background: transparent; color: var(--nf-text); font-size: 0.9375em; line-height: 1.4; cursor: pointer; transition: border-color 150ms, background-color 150ms; }
#nf-waitlist-form .nf-option:hover { border-color: color-mix(in srgb, var(--nf-text) 28%, transparent); }
#nf-waitlist-form .nf-option:has(input:checked) { border-color: var(--nf-accent); background: color-mix(in srgb, var(--nf-accent) 8%, transparent); }
#nf-waitlist-form .nf-option span { min-width: 0; overflow-wrap: anywhere; }
#nf-waitlist-form .nf-option input { -webkit-appearance: none; appearance: none; flex: none; width: 18px; height: 18px; margin: 0; border: 1.5px solid var(--nf-control); border-radius: 50%; background: transparent; outline: 2px solid transparent; cursor: pointer; transition: border-color 150ms, box-shadow 150ms; }
#nf-waitlist-form .nf-option input:checked { border-color: var(--nf-accent); background: radial-gradient(circle, var(--nf-on-accent) 0 3px, var(--nf-accent) 3.5px); }
#nf-waitlist-form .nf-option input:focus-visible { box-shadow: 0 0 0 3px color-mix(in srgb, var(--nf-accent) 24%, transparent); }
#nf-waitlist-form .nf-check { display: flex; align-items: flex-start; gap: 10px; margin: 0; font-size: 0.9375em; line-height: 1.5; color: var(--nf-text); cursor: pointer; }
#nf-waitlist-form .nf-check input { -webkit-appearance: none; appearance: none; flex: none; width: 20px; height: 20px; margin: 1px 0 0; border: 1.5px solid var(--nf-control); border-radius: 6px; background: var(--nf-field) center / 14px 14px no-repeat; outline: 2px solid transparent; cursor: pointer; transition: background-color 150ms, border-color 150ms, box-shadow 150ms; }
#nf-waitlist-form .nf-check input:checked { border-color: var(--nf-accent); background-color: var(--nf-accent); background-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20stroke%3D%22%23ffffff%22%20stroke-width%3D%223.5%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Cpath%20d%3D%22M5%2012l5%205l10%20-10%22%2F%3E%3C%2Fsvg%3E"); }
#nf-waitlist-form .nf-check input:focus-visible { box-shadow: 0 0 0 3px color-mix(in srgb, var(--nf-accent) 24%, transparent); }
#nf-waitlist-form .nf-check input:user-invalid { border-color: var(--nf-error); }
#nf-waitlist-form .nf-rating { display: flex; flex-wrap: wrap; margin: 0 0 0 -4px; }
#nf-waitlist-form .nf-rating label { display: inline-flex; padding: 4px; border-radius: 8px; color: var(--nf-control); color: color-mix(in srgb, var(--nf-control) 45%, transparent); cursor: pointer; transition: color 150ms; }
#nf-waitlist-form .nf-rating svg { width: 30px; height: 30px; fill: currentColor; stroke: currentColor; stroke-width: 1.5; stroke-linejoin: round; transition: transform 150ms; }
#nf-waitlist-form .nf-rating input:checked + label, #nf-waitlist-form .nf-rating label:has(~ input:checked) { color: var(--nf-accent); }
#nf-waitlist-form .nf-rating:hover input + label { color: var(--nf-control); color: color-mix(in srgb, var(--nf-control) 45%, transparent); }
#nf-waitlist-form .nf-rating:hover label:hover, #nf-waitlist-form .nf-rating:hover label:has(~ label:hover) { color: var(--nf-accent); }
#nf-waitlist-form .nf-rating label:active svg { transform: scale(0.88); }
#nf-waitlist-form .nf-rating input:focus-visible + label { outline: 2px solid var(--nf-accent); outline-offset: 0; }
#nf-waitlist-form .nf-file { position: relative; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 4px; min-height: 112px; padding: 16px; border: 1.5px dashed var(--nf-control); border-color: color-mix(in srgb, var(--nf-control) 55%, transparent); border-radius: 10px; background: var(--nf-field); color: var(--nf-muted); text-align: center; transition: border-color 150ms, background-color 150ms, box-shadow 150ms; }
#nf-waitlist-form .nf-file-input { position: absolute; top: 0; left: 0; width: 100%; height: 100%; margin: 0; opacity: 0; cursor: pointer; }
#nf-waitlist-form .nf-file svg { width: 22px; height: 22px; margin-bottom: 4px; }
#nf-waitlist-form .nf-file-text { margin: 0; max-width: 100%; font-size: 0.875em; overflow-wrap: anywhere; }
#nf-waitlist-form .nf-file-cta { font-weight: 600; color: var(--nf-text); }
#nf-waitlist-form .nf-file-types { margin: 0; font-size: 0.75em; color: var(--nf-placeholder); }
#nf-waitlist-form .nf-file:hover { border-color: var(--nf-control); }
#nf-waitlist-form .nf-file:focus-within { border-color: var(--nf-accent); box-shadow: 0 0 0 3px color-mix(in srgb, var(--nf-accent) 24%, transparent); }
#nf-waitlist-form .nf-file.nf-file-over { border-color: var(--nf-accent); background: color-mix(in srgb, var(--nf-accent) 8%, var(--nf-field)); }
#nf-waitlist-form .nf-file.nf-file-chosen { border-style: solid; border-color: var(--nf-accent); }
#nf-waitlist-form .nf-file.nf-file-chosen .nf-file-text { font-weight: 600; color: var(--nf-text); }
#nf-waitlist-form .nf-file:has(.nf-file-input:user-invalid) { border-color: var(--nf-error); }
#nf-waitlist-form .nf-sr { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; }
#nf-waitlist-form .nf-hp { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }
#nf-waitlist-form .nf-captcha { margin-top: 18px; }
#nf-waitlist-form .nf-submit { display: inline-flex; align-items: center; justify-content: center; width: 100%; min-height: 44px; margin: 22px 0 0; padding: 10px 18px; border: 0; border-radius: 10px; background: var(--nf-accent); color: var(--nf-on-accent); font: inherit; font-size: 1em; font-weight: 600; cursor: pointer; transition: filter 150ms, transform 150ms; }
#nf-waitlist-form .nf-submit:hover { filter: brightness(0.94); }
#nf-waitlist-form .nf-submit:active { transform: scale(0.98); }
#nf-waitlist-form .nf-submit:focus-visible { outline: 2px solid var(--nf-accent); outline-offset: 3px; }
#nf-waitlist-form .nf-submit:disabled { opacity: 0.6; cursor: default; }
#nf-waitlist-form .nf-error { margin: 14px 0 0; padding: 10px 12px; border-radius: 10px; background: color-mix(in srgb, var(--nf-error) 10%, transparent); font-size: 0.875em; color: var(--nf-error); }
#nf-waitlist-form.nf-sent { display: flex; flex-direction: column; justify-content: center; min-height: min(var(--nf-height, 0px), 440px); }
#nf-waitlist-form .nf-success { display: flex; flex-direction: column; align-items: center; padding: 20px 0; text-align: center; outline: none; }
#nf-waitlist-form .nf-success-icon { display: flex; flex: none; align-items: center; justify-content: center; width: 56px; height: 56px; margin: 0 0 20px; border-radius: 50%; background: var(--nf-accent); color: var(--nf-on-accent); box-shadow: 0 0 0 8px color-mix(in srgb, var(--nf-accent) 12%, transparent); }
#nf-waitlist-form .nf-success-icon svg { display: block; width: 28px; height: 28px; margin: 0; stroke-dasharray: 1; }
#nf-waitlist-form .nf-success-title { margin: 0; max-width: 24em; font-size: 1.125em; font-weight: 650; line-height: 1.35; letter-spacing: -0.01em; color: var(--nf-text); text-wrap: balance; overflow-wrap: anywhere; }
#nf-waitlist-form .nf-success-text { margin: 0; max-width: 30em; font-size: 0.9375em; line-height: 1.5; color: var(--nf-text); text-wrap: pretty; overflow-wrap: anywhere; }
#nf-waitlist-form .nf-success-title + .nf-success-text { margin-top: 6px; color: var(--nf-muted); }
#nf-waitlist-form.nf-sent .nf-success { animation: nf-success-in 400ms cubic-bezier(0.16, 1, 0.3, 1) both; }
#nf-waitlist-form.nf-sent .nf-success-icon { animation: nf-success-pop 500ms cubic-bezier(0.34, 1.56, 0.64, 1) both; }
#nf-waitlist-form.nf-sent .nf-success-icon path { animation: nf-success-draw 360ms 160ms cubic-bezier(0.65, 0, 0.35, 1) both; }
@keyframes nf-success-in { from { opacity: 0; transform: translateY(6px); } }
@keyframes nf-success-pop { from { transform: scale(0.6); } }
@keyframes nf-success-draw { from { stroke-dashoffset: 1; } }
@media (prefers-reduced-motion: reduce) { #nf-waitlist-form * { transition: none !important; animation: none !important; } }
  </style>
  <div class="nf-head">
    <h2 class="nf-title">Join the waitlist</h2>
    <p class="nf-desc">Be the first to know when we launch.</p>
  </div>
  <div class="nf-fields">
    <div class="nf-field">
      <label class="nf-label" for="nf-waitlist-form-email">Email<span class="nf-req" aria-hidden="true"> *</span></label>
      <input class="nf-input" id="nf-waitlist-form-email" name="email" type="email" autocomplete="email" placeholder="you@example.com" required>
    </div>
  </div>
  <div class="nf-hp" aria-hidden="true"><input type="text" name="_gotcha" tabindex="-1" autocomplete="off"></div>
  <button class="nf-submit" type="submit">Count me in</button>
  <p class="nf-error" role="alert" hidden></p>
  <div class="nf-success" role="status" tabindex="-1" hidden>
    <span class="nf-success-icon" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" focusable="false"><path d="M5 12l5 5l10 -10" pathLength="1"/></svg></span>
    <p class="nf-success-title">You’re on the list.</p>
    <p class="nf-success-text">We’ll be in touch.</p>
  </div>
</form>
<script>
(function () {
  var form = document.getElementById("nf-waitlist-form")
  if (!form || !window.fetch) return
  var started = Date.now()
  var button = form.querySelector('.nf-submit')
  var error = form.querySelector('.nf-error')
  var success = form.querySelector('.nf-success')
  form.addEventListener('submit', function (event) {
    event.preventDefault()
    var data = new FormData(form)
    data.set('_elapsed', String(Date.now() - started))
    var label = button.textContent
    button.disabled = true
    button.textContent = 'Sending...'
    error.hidden = true
    fetch(form.action, {
      method: 'POST',
      headers: { Accept: 'application/json' },
      body: new URLSearchParams(data),
    })
      .then(function (response) {
        return response.json().catch(function () { return {} }).then(function (body) {
          if (!response.ok || !body.ok) throw new Error(body.message || 'Something went wrong. Please try again.')
        })
      })
      .then(function () {
        var redirect = form.getAttribute('data-redirect')
        if (redirect) {
          window.location.href = redirect
          return
        }
        form.style.setProperty('--nf-height', form.offsetHeight + 'px')
        Array.prototype.forEach.call(form.children, function (child) {
          if (child !== success && child.tagName !== 'STYLE') child.hidden = true
        })
        form.classList.add('nf-sent')
        success.hidden = false
        success.focus()
      })
      .catch(function (reason) {
        error.textContent = reason && reason.message ? reason.message : 'Something went wrong. Please try again.'
        error.hidden = false
      })
      .finally(function () {
        button.disabled = false
        button.textContent = label
        if (window.turnstile) window.turnstile.reset()
      })
  })
})()
</script>
```

## Unstyled HTML

Bare markup that picks up your own CSS. It posts without JavaScript and keeps the hidden `_gotcha` field that stops bots.

```html
<form action="https://api.nisuform.com/s/YOUR_FORM_KEY" method="POST">
  <label>
    Email
    <input name="email" type="email" placeholder="you@example.com" required>
  </label>
  <input type="text" name="_gotcha" tabindex="-1" autocomplete="off" style="display:none">
  <button type="submit">Count me in</button>
</form>
```

## Framework components

### React

```jsx
import { useRef, useState } from 'react'

export function WaitlistForm() {
  const renderedAt = useRef(Date.now())
  const [status, setStatus] = useState('idle')

  async function onSubmit(event) {
    event.preventDefault()
    setStatus('sending')
    const data = Object.fromEntries(new FormData(event.currentTarget))
    data._elapsed = String(Date.now() - renderedAt.current)
    const response = await fetch("https://api.nisuform.com/s/YOUR_FORM_KEY", {
      method: 'POST',
      headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
      body: JSON.stringify(data),
    })
    setStatus(response.ok ? 'sent' : 'error')
  }

  if (status === 'sent') return <p>You’re on the list. We’ll be in touch.</p>

  return (
    <form onSubmit={onSubmit}>
      <label>
        Email
        <input name="email" type="email" placeholder="you@example.com" required />
      </label>
      <input type="text" name="_gotcha" tabIndex={-1} autoComplete="off" style={{ display: 'none' }} />
      <button type="submit" disabled={status === 'sending'}>Count me in</button>
      {status === 'error' && <p role="alert">Something went wrong. Please try again.</p>}
    </form>
  )
}
```

### Next.js

```jsx
'use client'

import { useRef, useState } from 'react'

export function WaitlistForm() {
  const renderedAt = useRef(Date.now())
  const [status, setStatus] = useState('idle')

  async function onSubmit(event) {
    event.preventDefault()
    setStatus('sending')
    const data = Object.fromEntries(new FormData(event.currentTarget))
    data._elapsed = String(Date.now() - renderedAt.current)
    const response = await fetch("https://api.nisuform.com/s/YOUR_FORM_KEY", {
      method: 'POST',
      headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
      body: JSON.stringify(data),
    })
    setStatus(response.ok ? 'sent' : 'error')
  }

  if (status === 'sent') return <p>You’re on the list. We’ll be in touch.</p>

  return (
    <form onSubmit={onSubmit}>
      <label>
        Email
        <input name="email" type="email" placeholder="you@example.com" required />
      </label>
      <input type="text" name="_gotcha" tabIndex={-1} autoComplete="off" style={{ display: 'none' }} />
      <button type="submit" disabled={status === 'sending'}>Count me in</button>
      {status === 'error' && <p role="alert">Something went wrong. Please try again.</p>}
    </form>
  )
}
```

### Vue

```vue
<script setup>
import { ref } from 'vue'

const shownAt = Date.now()
const status = ref('idle')

async function onSubmit(event) {
  status.value = 'sending'
  const data = new FormData(event.target)
  data.set('_elapsed', String(Date.now() - shownAt))
  const response = await fetch("https://api.nisuform.com/s/YOUR_FORM_KEY", {
    method: 'POST',
    headers: { Accept: 'application/json' },
    body: data,
  })
  status.value = response.ok ? 'sent' : 'error'
}
</script>

<template>
  <p v-if="status === 'sent'">You’re on the list. We’ll be in touch.</p>
  <form v-else @submit.prevent="onSubmit">
    <label>
      Email
      <input name="email" type="email" placeholder="you@example.com" required>
    </label>
    <input type="text" name="_gotcha" tabindex="-1" autocomplete="off" style="display:none">
    <button type="submit" :disabled="status === 'sending'">Count me in</button>
    <p v-if="status === 'error'" role="alert">Something went wrong. Please try again.</p>
  </form>
</template>
```

### Svelte

```svelte
<script>
  const shownAt = Date.now()
  let status = $state('idle')

  async function onsubmit(event) {
    event.preventDefault()
    status = 'sending'
    const data = new FormData(event.currentTarget)
    data.set('_elapsed', String(Date.now() - shownAt))
    const response = await fetch("https://api.nisuform.com/s/YOUR_FORM_KEY", {
      method: 'POST',
      headers: { Accept: 'application/json' },
      body: data,
    })
    status = response.ok ? 'sent' : 'error'
  }
</script>

{#if status === 'sent'}
  <p>You’re on the list. We’ll be in touch.</p>
{:else}
  <form {onsubmit}>
    <label>
      Email
      <input name="email" type="email" placeholder="you@example.com" required>
    </label>
    <input type="text" name="_gotcha" tabindex="-1" autocomplete="off" style="display:none">
    <button type="submit" disabled={status === 'sending'}>Count me in</button>
    {#if status === 'error'}
      <p role="alert">Something went wrong. Please try again.</p>
    {/if}
  </form>
{/if}
```

### Astro

```astro
---
const endpoint = "https://api.nisuform.com/s/YOUR_FORM_KEY"
---

<form action={endpoint} method="POST">
  <label>
    Email
    <input name="email" type="email" placeholder="you@example.com" required>
  </label>
  <input type="text" name="_gotcha" tabindex="-1" autocomplete="off" style="display:none">
  <button type="submit">Count me in</button>
</form>
```

## When to use it

- A coming soon page before your product launches
- Beta and early access signups
- A "notify me" form for a product that is sold out or not available yet
- Gauging interest in a course, event or ebook before you make it

## Customize it

### Know where signups came from

Add `<input type="hidden" name="source" value="homepage">` to tell forms on different pages apart. Nisuform also records the page every submission was sent from.

### Cap the list

Set a maximum number of submissions in the form settings, like 200 beta spots, and the form closes by itself when it is full.

### Move signups into your email tool

Export the list as CSV at any time, or on Pro send each signup to Zapier, Make or a webhook as it arrives.

## Frequently asked questions

### How do I export my waitlist?

Open the form in your Nisuform dashboard and export the submissions as a CSV file. It opens in Excel and Google Sheets and imports into most email tools. Export is included on every plan.

### How do I stop fake or bot signups?

The template includes a hidden honeypot field and sends how long the form was open, so most bots are dropped without counting toward your limit. Rate limits and duplicate checks catch the rest. If spam still gets through, turn on Cloudflare Turnstile.

### Can I show how many people already joined?

Not directly. Nisuform does not publish a live counter, but your server can read the count from the Nisuform API and show it on your page.
