Forms

Publish forms your staff edit without a deploy, and store, email or forward every submission.

Forms lets you publish contact forms, sign-ups and applications without building a page for each one. Staff build a form in the admin area, or you define it in code. Every submission is stored, emailed or passed to your own code. Use it when visitors or users should send you structured answers. For settings a user keeps on their own account, use Preferences instead.

The package is @frigear-nu/forms. Each form is a definition stored in the database.

What you get

  • Pages (opt-in): a public page per form at /forms/:slug. <FormRenderer> and a Markdown embed place a form on any other page.
  • Admin area (opt-in, with Admin): the form list at /admin/forms, a visual builder for each form, and its submissions.
  • Server routes (on by default): read and submit a published form, and a staff API for forms, submissions and access. The staff API is guarded by the forms:manage and forms:credentialspermissions, which the admin role holds.
  • Composables and helpers (on by default): defineForm() to write a form in code, and a hook that receives every submission.
  • MCP tools (opt-in): list and submit forms, and manage them as staff, through MCP.
  • Emails (opt-in per form): each submission is emailed through Notify to the recipients the form names. Your app picks the Notify driver; until it does, emails are not sent.
ServerClientMobile
Installed for you:Notify, Auth, Users, MCP, UI and Module Kit. You add:@nuxthub/core (Forms turns on its key-value storage for rate limiting and blob storage for uploads), and @nuxt/ui for the pages. Optional:Admin for the admin pages, and Menu for the admin sidebar item.

Quick setup

Install Forms

pnpm add @frigear-nu/forms @nuxthub/core

Add it to nuxt.config.ts

nuxt.config.ts
export default defineNuxtConfig({
  modules: ['@nuxthub/core', '@frigear-nu/forms'],
  forms: {
    settings: {
      pages: { form: true, admin: true, adminForm: true, adminSubmissions: true, adminSubmission: true },
    },
  },
})

Generate the migration

Without a migration the form tables do not exist, and creating or submitting a form fails.

Forms adds six tables. Generate the migration and commit it:

pnpm db:generate

Decide who manages forms

Staff routes check permissions through Authorization. Without a role that holds forms:manage, nobody can manage forms. The admin role holds it by default.

To let another role manage forms, grant the permissions in your config. forms:* covers forms:manage, forms:credentials and forms:submit, but not forms:pii, which you grant by name:

nuxt.config.ts
export default defineNuxtConfig({
  authorization: {
    roles: {
      manager: { label: 'Coordinator', grant: ['forms:*', 'users:read', 'users:list'], inherit: ['user'] },
    },
  },
})
What you should see: signed in as staff, /admin/forms lists your forms. A published form answers at /forms/<slug>.

Options

Options go under the forms key.

OptionTypeDefaultPurpose
settings.pages.formstring | booleanoffThe public form page. true is /forms/:slug.
settings.pages.adminstring | booleanoffThe admin form list. true is /admin/forms.
settings.pages.adminFormstring | booleanoffThe builder. true is /admin/forms/:id.
settings.pages.adminSubmissionsstring | booleanoffA form's submissions. true is /admin/forms/:id/submissions.
settings.pages.adminSubmissionstring | booleanoffOne submission. true is /admin/forms/:id/submissions/:submissionId.
settings.rateLimitnumber10Submissions per client and form in one window. 0 turns rate limiting off.
settings.rateLimitWindowSecondsnumber600The length of the window.
settings.mcpbooleanfalseAdds the module's MCP tools.
seed.enabledbooleanfalseInserts seed.forms at startup.
seed.formsSeedForm[][]{ slug, name, status?, definition }. status defaults to draft.
captcha.<provider>.siteKeystring''The public site key.
captcha.<provider>.secretKeystring''The secret key, used only on the server.
captcha.defaultProviderCaptchaProvider'turnstile'The provider of a captcha field that names none.
encryptionKeystring''The key that encrypts per-form secrets.

settings.pages.* paths keep the parameters of the default, such as :slug. Admin pages need Admin.

settings.rateLimit also caps wrong password attempts, on a separate counter with the same window. 0 turns both off.

captcha.<provider> is turnstile, recaptcha or hcaptcha.

encryptionKey falls back to NUXT_ENCRYPTION_KEY, then to the session password.

Runtime config

Env varOption
NUXT_FORMS_SEED_ENABLEDseed.enabled
NUXT_PUBLIC_FORMS_CAPTCHA_TURNSTILE_SITE_KEY, …_RECAPTCHA_SITE_KEY, …_HCAPTCHA_SITE_KEYcaptcha.<provider>.siteKey
NUXT_FORMS_CAPTCHA_TURNSTILE_SECRET_KEY, …_RECAPTCHA_SECRET_KEY, …_HCAPTCHA_SECRET_KEYcaptcha.<provider>.secretKey
NUXT_PUBLIC_FORMS_CAPTCHA_DEFAULT_PROVIDERcaptcha.defaultProvider
NUXT_FORMS_ENCRYPTION_KEYencryptionKey

Permissions

Forms registers these permissions with Authorization. A staff route calls authorize(event, 'forms:manage').

PermissionFlagsHeld by defaultWhat it allows
forms:manageadminCreate and edit forms, read their submissions and files, and deliver them again.
forms:credentialssessionOnlyadminCreate and remove form access tokens, passwords and secrets. Cookie sessions only.
forms:piisensitiveadminSensitive (MCP only): gates submission answers and file names in MCP tool output. The admin HTTP API, submissions included, is gated by forms:manage. A * or forms:* grant never covers it.
forms:submitimplicitevery signed-in userSubmit forms as you, and read forms that need a signed-in user.
  • Scope: forms (label "Submit forms") expands to forms:submit. A token or OAuth client with it can submit as the user, and nothing else.
  • Roles, requirements and policies: the module ships none. The admin role receives the staff permissions through the module's default grants. If you replace the admin role with replace: true, grant them again yourself.
  • Why forms:credentials is separate: minting or removing a way into a form is a credential change, so it is session only. A role that grants forms:manage by name must grant forms:credentials too.

See roles and requirements for how roles are built and merged.

Module pages

PageWhat it coversFor
DefinitionWhat a form is made of: fields, conditions, phone fields, open periods, the email, captcha and the builder.App developers
Server APISubmitting, who may submit, rate limits, secrets, seeding, staff access and MCP tools.App developers and module authors
AppPages, components, the Markdown embed, composables and utils.App developers
ReferenceHooks, routes, tables and translations.App developers

Next steps

  • Build a form – define a contact form in code, seed it and receive it by email.
  • Definition – add steps, conditional fields, uploads and an open period.
  • Send an email – pick the Notify driver that delivers form emails.
Copyright © 2026