@softwareproduction/nuxt-introspection-forms

Nuxt module for introspection-forms — schema-driven form generation from GraphQL
194
Install
npm install @softwareproduction/nuxt-introspection-forms
Latest Version:0.1.0
Vue:3
License:MIT
Last Updated:Jun 8, 2026
Links: GitHub  ·  npm
Maintainer: bananapeelsurfer

@softwareproduction/nuxt-introspection-forms

Nuxt module that wraps @softwareproduction/introspection-forms for seamless integration with Nuxt 3 and 4 applications. It handles component registration, composable auto-imports, alias configuration, and global defaults injection — so you can use schema-driven forms with zero boilerplate.

Table of Contents

Installation

yarn add @softwareproduction/nuxt-introspection-forms @softwareproduction/introspection-forms
yarn add -D @graphql-codegen/cli graphql

Peer dependencies: vue >= 3.4, nuxt >= 3.9.

Module Setup

Add the module to your nuxt.config.ts:

export default defineNuxtConfig({
  modules: ['@softwareproduction/nuxt-introspection-forms'],

  introspectionForms: {
    // Path to generated introspection types (relative to rootDir)
    generatedPath: 'types/generated/introspection',
  },
})

The module will:

  1. Register <IntrospectionForm> and <IntrospectionField> as global components.
  2. Auto-import useIntrospectionForm and useIntrospectionFormsEnumFilter composables.
  3. Create #introspection-types and #introspection-forms aliases for convenient imports.
  4. Register a client-side plugin that provides global defaults via inject.

Component Mapping Plugin

Create a Nuxt plugin to define which UI components render which field types. This is where you connect @softwareproduction/introspection-forms to your design system.

// app/plugins/configure-forms.ts
import {
  FormInput,
  FormCheckbox,
  FormRadio,
  FormSelect,
  FormTextarea,
  FormDateInput,
} from '#components'
import type { IntrospectionFormsDefaults, IntrospectionField } from '@softwareproduction/introspection-forms'
import { withProps, useIntrospectionFormsEnumFilter } from '@softwareproduction/introspection-forms'

export default defineNuxtPlugin(nuxtApp => {
  const { filterEnumValues } = useIntrospectionFormsEnumFilter()

  nuxtApp.provide('introspection-forms', {
    // Map by TypeScript scalar type
    byFieldType: {
      string: { component: FormInput },
      number: { component: FormInput, props: { type: 'number' } },
      boolean: { component: FormCheckbox },
      enum: (introspection: IntrospectionField) => ({
        component: introspection.enumValues.length > 5 ? FormSelect : FormRadio,
        props: withProps<typeof FormRadio>(() => ({
          options: (_, t) =>
            filterEnumValues(introspection).map(value => ({
              value,
              label: t(`forms.${introspection.originalType}.${value}`),
            })),
        })),
      }),
    },

    // Map by original GraphQL type name
    byOriginalType: {
      DateTime: { component: FormDateInput },
      Float: { component: FormInput, props: { type: 'number', step: '0.01' } },
    },

    // Map by field name (regex matching, first match wins)
    byFieldName: [
      { regexp: /^email/i, config: { component: FormInput, props: { type: 'email' } } },
      { regexp: /phone|telefon/i, config: { component: FormInput, props: { type: 'tel' } } },
      { regexp: /^(body|message|notes)/i, config: { component: FormTextarea } },
      { regexp: /files/i, config: { component: FormInput, props: { type: 'file', multiple: true } } },
      { regexp: /^(password|passwort)/i, config: { component: FormInput, props: { type: 'password' } } },
    ],

    // Global enum value filters
    enumFilters: {
      Salutation: values => values.filter(v => v !== 'None'),
    },
  } satisfies IntrospectionFormsDefaults)
})

Usage in Pages & Components

The module auto-imports everything you need. Import only the generated introspection type:

<script setup lang="ts">
import { TypeOfContactFormInput } from '#introspection-types'

const data = ref(TypeOfContactFormInput.create())

const form = useIntrospectionForm(TypeOfContactFormInput, {
  firstName: true,
  lastName: true,
  email: true,
  body: true,
})

async function submit() {
  // data.value now contains the form values
  await $fetch('/api/contact', { method: 'POST', body: data.value })
}
</script>

<template>
  <IntrospectionForm :form="form" :model="data">
    <button @click.prevent="submit">Send</button>
  </IntrospectionForm>
</template>

With Field Overrides

Override defaults for specific fields without losing the auto-resolved component:

<script setup lang="ts">
import { TypeOfRegistrationFormInput } from '#introspection-types'

const data = ref(TypeOfRegistrationFormInput.create({ useSameAddress: true }))

const form = useIntrospectionForm(TypeOfRegistrationFormInput, {
  firstName: { span: 1 },                           // Keep default component, adjust layout
  lastName: { span: 1 },
  email: true,                                      // Use defaults entirely
  phone: {
    visible: model => model.preferredContact === 'Phone',  // Conditional visibility
  },
  acceptTerms: { span: 2 },
})
</script>

With Nested Forms

<script setup lang="ts">
import { TypeOfOrderInput, TypeOfAddressInput } from '#introspection-types'

const data = ref(TypeOfOrderInput.create())

const addressForm = useIntrospectionForm(TypeOfAddressInput, {
  street: true,
  houseNumber: true,
  zipCode: true,
  city: true,
})

const form = useIntrospectionForm(TypeOfOrderInput, {
  productId: true,
  quantity: true,
  shippingAddress: { form: addressForm },
})
</script>

GraphQL Codegen Integration

Set up the codegen plugin to generate introspection metadata from your schema:

// codegen.config.ts
import type { CodegenConfig } from '@graphql-codegen/cli'

const config: CodegenConfig = {
  schema: './schema.graphql',
  generates: {
    './types/generated/introspection/_placeholder.ts': {
      plugins: ['@softwareproduction/introspection-forms/codegen'],
      config: {
        output: './types/generated/introspection',
        typesImport: '../graphql-types',
        introspectionTypeImport: '@softwareproduction/introspection-forms',
      },
    },
  },
}

export default config

Add scripts to your package.json:

{
  "scripts": {
    "gen:forms": "graphql-codegen --config codegen.config.ts",
    "gen:types": "graphql-codegen --config codegen-types.config.ts"
  }
}

Run both generators when your schema changes:

yarn gen:types && yarn gen:forms

Module Options

Configure via introspectionForms in nuxt.config.ts:

Option Type Default Description
generatedPath string 'types/generated/introspection' Path to generated introspection files (relative to rootDir)
defaults IntrospectionFormsDefaults undefined Static component mapping defaults (alternative to the plugin approach for simple configs)

Provided Aliases

The module registers two aliases for convenient imports:

Alias Resolves To Usage
#introspection-types <rootDir>/<generatedPath> import { TypeOfXxx } from '#introspection-types'
#introspection-forms @softwareproduction/introspection-forms package import { withProps } from '#introspection-forms'

Auto-Imports

These are available globally without manual imports:

Import Source Description
useIntrospectionForm @softwareproduction/introspection-forms Create a form runtime
useIntrospectionFormsEnumFilter @softwareproduction/introspection-forms Access enum filters

Components registered globally:

Component Description
<IntrospectionForm> Top-level form renderer
<IntrospectionField> Individual field renderer

Validation with Dryv

The module integrates with Dryv for client-side validation. Pass a DryvValidationRuleSet to useIntrospectionForm and use v-model:validate to receive the validate command from the component:

<script setup lang="ts">
import { TypeOfContactFormInput } from '#introspection-types'
import { ContactFormValidationSet } from '#validation'

const data = ref(TypeOfContactFormInput.create())
const validate = ref<(checkOnly?: boolean) => Promise<boolean>>()

const form = useIntrospectionForm(
  TypeOfContactFormInput,
  ContactFormValidationSet,
  {
    firstName: true,
    lastName: true,
    email: true,
  },
)

async function submit() {
  const success = await validate.value?.()
  if (!success) return
  await $fetch('/api/submit', { method: 'POST', body: data.value })
}
</script>

<template>
  <IntrospectionForm :form="form" :model="data" v-model:validate="validate">
    <button @click.prevent="submit">Submit</button>
  </IntrospectionForm>
</template>

The component internally creates a Dryv validation session when rules are provided and exposes the validate function via v-model:validate. Validation errors appear inline beneath each field automatically.

Advanced Configuration

Multiple Form Configs (Spread Pattern)

Break large forms into logical groups:

const personalFields = { firstName: true, lastName: true, dateOfBirth: true }
const contactFields = { email: true, phone: true, preferredContact: true }
const consentFields = { acceptTerms: { span: 2 }, acceptNewsletter: { span: 2 } }

const form = useIntrospectionForm(TypeOfRegistrationFormInput, {
  ...personalFields,
  ...contactFields,
  ...consentFields,
})

Form-Level Visibility and Disabled

const form = useIntrospectionForm({
  introspection: TypeOfPaymentInput,
  config: { cardNumber: true, cvv: true, expiryDate: true },
  visible: model => model.paymentMethod === 'creditCard',
  disabled: model => model.isProcessing,
})

Storage Persistence per Form

<IntrospectionForm
  :form="form"
  :model="data"
  storage="session"
  :interceptStorage="(stored) => ({ ...stored, acceptTerms: false })"
>
  ...
</IntrospectionForm>

License

MIT

Related Packages

@softwareproduction/nuxt-dryv

Nuxt module for integrating the Dryv validation library (@softwareproduction/dry...

380 0

Version History

Version Released Vue License
0.1.0 ^3.4.0 MIT