Framework Guide

Nuxt Contact Form

Add a contact form to a Nuxt app without a server route

Last updated

To add a contact form to a Nuxt app without a backend, post the form data from a component to https://api.staticforms.dev/submit with your form's API key in an apiKey field. Static Forms emails each submission to you, so you need no server route, and the same component works in a server-rendered app or a static site built with nuxt generate. It works on the free plan.

1. Add your API key to runtime config

Keys under runtimeConfig.public are available in the browser. Declare the key in nuxt.config.ts:

nuxt.config.ts

Then set it with the matching environment variable. Nuxt reads .env during development, build and generate:

.env

Find the key on the General tab of your form in the app. It reaches the browser, which is expected: the form API key cannot read submissions. A built server does not read .env, so in production set NUXT_PUBLIC_STATIC_FORMS_KEY in your host's environment settings.

2. Create the form component

Nuxt auto-imports components, Vue APIs such as ref, and composables such as useRuntimeConfig, so the component needs no imports. The paths below use the Nuxt 4 app/ folder; in a Nuxt 3 project, drop the app/ prefix.

app/components/ContactForm.vue

The visitor's email field becomes the reply-to address on the notification, and every other named field appears in the email. The API answers { "success": true, "message": "Form submitted successfully" }, or { "success": false, "error": "..." } with the reason, which the component shows; a submission that fails the form's field validation answers with an errors list, which it shows too. Leave out redirectTo here: a redirect would replace the JSON result.

3. Add a contact page

app/pages/contact.vue

4. Run and test

Start nuxt dev, open /contact and send a test message. It arrives by email and is listed in the Inbox. Deploy as usual, for example to Vercel, Netlify or Cloudflare Pages, and send one more message from the live site.

More options

The Vue guide has the Options API version and validation, and the plain HTML guide lists the CAPTCHA widgets and their field names. A longer walkthrough with reCAPTCHA and a reusable composable is in the Nuxt tutorial.

Troubleshooting

What you seeWhy, and what to do
400 "API key is required (provide either apiKey or accessKey)"The runtime config value is empty. Check that staticFormsKey is declared under runtimeConfig.public and that NUXT_PUBLIC_STATIC_FORMS_KEY is set where the app is built or run.
401 "Invalid API key"The key is set but does not match the one on the form's General tab. Copy it again, with no extra spaces.
A field is missing from the emailGive the input a name attribute; FormData only sends named fields.
403 "CAPTCHA verification failed" (reCAPTCHA), or "Turnstile", "hCaptcha" or "Altcha verification failed"A CAPTCHA is turned on for the form, but the submission had no valid token. Add the widget, or turn the CAPTCHA off.

FAQ

Does this work on the free plan?

Yes. The snippet only uses Free plan features: 250 submissions every 30 days, 1 form endpoint, email delivery, the built-in spam filter, a honeypot field and reCAPTCHA v2. No credit card is needed.

How do I keep spam out?

Every submission is checked by the built-in spam filter on every plan. The snippet adds a hidden honeypot field (any field whose name contains "honeypot"); bots fill it in and those submissions are dropped. reCAPTCHA v2 is available on Free. Cloudflare Turnstile, hCaptcha, ALTCHA and reCAPTCHA v3 are available from the Starter plan.

Is it safe to put the API key in my HTML?

Yes. The form API key is meant to be public. It cannot read your submissions or sign in to your dashboard. From the Starter plan you can also restrict submissions to your own domains.

Do I need a Nuxt server route or Nitro endpoint?

No. The component posts from the browser to https://api.staticforms.dev/submit, so a static site built with nuxt generate works as well as a server-rendered app.