> ## Documentation Index
> Fetch the complete documentation index at: https://docs.surveyowl.co/llms.txt
> Use this file to discover all available pages before exploring further.

# Next.js

> Add an Endpoint form to a Next.js app with a small client component.

This guide uses the App Router. The same component also works in the Pages Router—only the file locations change.

## 1. Save your Public Key

Open the Endpoint form in the dashboard, go to the **General** tab, and copy the Public Key (the last part of the **Form endpoint URL**).

Add it to `.env.local` in the root of your project:

```bash .env.local theme={null}
NEXT_PUBLIC_SURVEYOWL_KEY=YOUR_FORM_PUBLIC_KEY
```

The key is meant to be public, so the `NEXT_PUBLIC_` prefix is correct. It can only add responses to this one form. Restart `next dev` after adding the file—Next.js reads environment files at startup.

## 2. Create the form component

Create `app/contact/ContactForm.tsx`. The `'use client'` line is required because the component uses state and an event handler.

```tsx app/contact/ContactForm.tsx theme={null}
'use client';

import { useState, type FormEvent } from 'react';

const ENDPOINT = `https://api.surveyowl.co/v1/submit/${process.env.NEXT_PUBLIC_SURVEYOWL_KEY}`;

export default function ContactForm() {
  const [status, setStatus] = useState('');
  const [sending, setSending] = useState(false);

  async function onSubmit(event: FormEvent<HTMLFormElement>) {
    event.preventDefault();
    const form = event.currentTarget;
    setSending(true);
    setStatus('');

    try {
      const response = await fetch(ENDPOINT, {
        method: 'POST',
        body: new FormData(form),
      });
      const result = await response.json();

      if (!response.ok) {
        const message =
          typeof result.error === 'string' ? result.error : result.error?.message;
        throw new Error(message || 'Could not send the form.');
      }

      form.reset();
      setStatus('Thanks — your message was sent.');
    } catch (error) {
      setStatus(error instanceof Error ? error.message : 'Something went wrong.');
    } finally {
      setSending(false);
    }
  }

  return (
    <form onSubmit={onSubmit}>
      <label htmlFor="name">Name</label>
      <input id="name" name="name" required />

      <label htmlFor="email">Email</label>
      <input id="email" name="email" type="email" required />

      <label htmlFor="message">Message</label>
      <textarea id="message" name="message" required />

      <button type="submit" disabled={sending}>
        {sending ? 'Sending…' : 'Send message'}
      </button>
      <p role="status">{status}</p>
    </form>
  );
}
```

## 3. Show it on a page

```tsx app/contact/page.tsx theme={null}
import ContactForm from './ContactForm';

export default function ContactPage() {
  return (
    <main>
      <h1>Contact us</h1>
      <ContactForm />
    </main>
  );
}
```

Open `http://localhost:3000/contact`, send a test message, and check **Submissions** in the dashboard.

## Good to know

* Each input's `name` becomes the answer label in Submissions. Rename `message` to `question` and the dashboard will show `question`.
* Because the request is sent from the browser, you do not need an API route or a server action.
* Add `localhost` to your form's allowed domains—or turn on **Allow localhost**—if you use [domain restriction](/developer-api/submit-a-response#domain-restriction).
* Need uploads or CAPTCHA? Both build on this same `FormData` request. See [Files and CAPTCHA](/developer-api/files-and-captcha).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.