---
name: forminit-nuxtjs
description: Integrates Forminit headless form backend into Nuxt.js applications using the npm SDK with server-side API key proxy. Creates a server route with createForminitNuxtHandler and a Vue form component with typed blocks (sender, text, email, phone, file, rating, date, select, radio, checkbox, country, number). Use when adding forms to Nuxt.js/Vue apps, setting up form submission with server-side proxy, or collecting data in Nuxt projects. Do not use for HTML/static sites (use forminit-html) or Next.js (use forminit-nextjs).
metadata:
  author: forminit
  version: "1.0"
---

# Forminit — Nuxt.js Integration

> Forminit is a headless form backend API. You build the form UI, Forminit handles submission, validation, storage, and notifications. This skill uses the npm SDK with a server-side proxy to keep API keys secure.

## Constraints

- Never expose API keys in client-side code. API keys must only be used on the server.
- User must set authentication mode to "Protected" in Form settings before creating an API token.
- User must add their API token to `.env` — remind them if they haven't.
- All field names use prefix pattern: `fi-{type}-{name}`
- Sender block: max 1 per form. Use for submitter's contact info.
- Default to `fi-sender-firstName` + `fi-sender-lastName`. Use `fi-sender-fullName` only if user wants a single name field.
- For non-submitter emails/phones/countries, use field blocks: `fi-email-invitee`, `fi-phone-emergency`, `fi-country-shipping`
- File uploads require FormData (not JSON)
- Max 50 blocks per form, max 25 MB total files
- Show success message on page after submit (no redirect unless requested)

## Install

```bash
npm install forminit@latest
```

## Environment Variable

```
# .env
FORMINIT_API_KEY="YOUR-SECRET-API-TOKEN"
```

Configure runtime config in `nuxt.config.ts`:

```ts
export default defineNuxtConfig({
  runtimeConfig: {
    forminitApiKey: process.env.FORMINIT_API_KEY,
  },
});
```

## Server Route

```ts
// server/api/forminit.post.ts
import { createForminitNuxtHandler } from 'forminit/nuxt';

const config = useRuntimeConfig();

const forminitHandler = createForminitNuxtHandler({
  apiKey: config.forminitApiKey,
});

export default defineEventHandler(forminitHandler);
```

## Form Component (FormData)

```vue
<script setup lang="ts">
import { ref } from 'vue';
import { Forminit } from 'forminit';

const status = ref<'idle' | 'loading' | 'success' | 'error'>('idle');
const errorMessage = ref<string | null>(null);
const formRef = ref<HTMLFormElement | null>(null);
const forminit = new Forminit({ proxyUrl: '/api/forminit' });

async function handleSubmit() {
  if (!formRef.value) return;

  status.value = 'loading';
  errorMessage.value = null;

  const formData = new FormData(formRef.value);
  const { data, redirectUrl, error } = await forminit.submit('FORM_ID', formData);

  if (error) {
    status.value = 'error';
    errorMessage.value = error.message;
    return;
  }

  status.value = 'success';
  formRef.value.reset();
}
</script>

<template>
  <form ref="formRef" @submit.prevent="handleSubmit">
    <input type="text" name="fi-sender-firstName" placeholder="First name" required />
    <input type="text" name="fi-sender-lastName" placeholder="Last name" required />
    <input type="email" name="fi-sender-email" placeholder="Email" required />
    <textarea name="fi-text-message" placeholder="Message" required />

    <p v-if="status === 'error'">{{ errorMessage }}</p>
    <p v-if="status === 'success'">Message sent!</p>

    <button type="submit" :disabled="status === 'loading'">
      {{ status === 'loading' ? 'Sending...' : 'Send' }}
    </button>
  </form>
</template>
```

Replace `FORM_ID` with the form ID from https://app.forminit.com.

## Form Blocks Reference

### Sender Block — `fi-sender-{property}`

One per form. Collects submitter info. At least one property required.

```
fi-sender-email        fi-sender-firstName    fi-sender-lastName     fi-sender-fullName
fi-sender-phone        fi-sender-company      fi-sender-position     fi-sender-title
fi-sender-userId       fi-sender-address      fi-sender-city         fi-sender-country
```

### Field Blocks — `fi-{type}-{name}`

Each name must be unique. Up to 50 total blocks.

```
text      fi-text-{name}       e.g. fi-text-message
number    fi-number-{name}     e.g. fi-number-quantity
email     fi-email-{name}      e.g. fi-email-invitee
phone     fi-phone-{name}      e.g. fi-phone-emergency          Validation: E.164 (+12025550123)
url       fi-url-{name}        e.g. fi-url-website
date      fi-date-{name}       e.g. fi-date-appointment         Validation: ISO 8601 (YYYY-MM-DD)
rating    fi-rating-{name}     e.g. fi-rating-satisfaction      Validation: integer 1–5
select    fi-select-{name}     e.g. fi-select-plan              Supports multi-select (string[])
radio     fi-radio-{name}      e.g. fi-radio-priority           Single choice (string)
checkbox  fi-checkbox-{name}   e.g. fi-checkbox-features        Supports multi-choice (string[])
file      fi-file-{name}       e.g. fi-file-resume              Add [] for multiple: fi-file-photos[]
country   fi-country-{name}    e.g. fi-country-shipping         Validation: ISO 3166-1 alpha-2
```

## JSON Submission (alternative to FormData)

Use when building payloads programmatically. Cannot be used with file uploads.

```ts
const { data, redirectUrl, error } = await forminit.submit('FORM_ID', {
  blocks: [
    { type: 'sender', properties: { email: '...', firstName: '...', lastName: '...' } },
    { type: 'text', name: 'message', value: '...' },
    { type: 'rating', name: 'satisfaction', value: 4 },
    { type: 'select', name: 'plan', value: 'pro' }
  ]
});
```

Sender uses `{ type, properties }`. All other blocks use `{ type, name, value }`.

## SDK Response

```ts
const { data, redirectUrl, error } = await forminit.submit(formId, payload);
```

Success: `data.hashId` (submission ID), `data.date`, `data.blocks` (submitted values), `redirectUrl` (thank-you page URL).

Error: `error.error` (code like `FI_SCHEMA_FORMAT_EMAIL`), `error.code` (HTTP status), `error.message` (human-readable).

## Common Mistakes

- Using `name="email"` instead of `name="fi-sender-email"` — all fields must use the `fi-` prefix or the submission will be empty.
- Using `name="fi-sender-name"` — there is no `name` property on sender. Use `fi-sender-firstName` + `fi-sender-lastName` or `fi-sender-fullName`.
- Putting the API key in client-accessible `runtimeConfig.public` — use `runtimeConfig` (server-only), not `runtimeConfig.public`.
- Forgetting the `runtimeConfig` in `nuxt.config.ts` — without it, `useRuntimeConfig().forminitApiKey` is undefined.
- Forgetting to create the server route at `server/api/forminit.post.ts` — without it, the proxy URL `/api/forminit` returns 404.
- Setting form authentication to "Public" instead of "Protected" — Nuxt.js integration requires Protected mode with an API token.
- Sending phone without E.164 format — `5551234567` will be rejected, must be `+15551234567`.
- Using JSON with file uploads — files only work with FormData.

## Spam Protection (only if user requests)

- reCAPTCHA: https://forminit.com/docs/recaptcha/
- hCaptcha: https://forminit.com/docs/hcaptcha/
