Skip to content

Form templates

Version 0.1.9 adds a public registry of ready-to-use form templates. Users can search the left-panel template browser, replace the current form, or append a template to the existing schema.

Templates remain serializable data: schema, optional initial values and optional settings. No Vue component, handler or executable code is stored in JSON.

Built-in Community templates

KeyUse case
simple-contactContact details, subject, message and consent.
satisfaction-surveyRating, recommendation, comment and conditional contact.
admin-crudCreate or update an account in an administration interface.
employee-onboardingMulti-step identity, access, equipment and confirmation workflow.

The titles and descriptions of these four templates follow the active FormBuilder locale.

Use the template browser

Open the components drawer and select Templates. Two actions are available:

  • Use template replaces the current schema and values after confirmation;
  • Append keeps the current form and appends the new fields at root level.

Applying a template creates a template-apply history entry. Undo restores the previous schema. Values and settings are not yet part of the timeline; host applications requiring full transactional restoration should retain a business snapshot server-side.

Provide custom templates

vue
<script setup lang="ts">
import type {
  QFormBuilderTemplateApplyEvent,
  QFormBuilderTemplateRegistry,
} from '@vevedh/qform-builder-layer/types'
import { defineQFormTemplate } from '@vevedh/qform-builder-layer/templates'

const templates: QFormBuilderTemplateRegistry = [
  defineQFormTemplate({
    key: 'incident-report',
    title: 'Incident report',
    description: 'Collects the information required for initial qualification.',
    icon: 'report_problem',
    category: 'security',
    tags: ['incident', 'support', 'security'],
    schema: [
      {
        $formkit: 'q-select',
        name: 'severity',
        label: 'Severity',
        options: [
          { label: 'Low', value: 'low' },
          { label: 'Medium', value: 'medium' },
          { label: 'High', value: 'high' },
        ],
        validation: 'required',
      },
      {
        $formkit: 'q-input',
        name: 'summary',
        label: 'Summary',
        validation: 'required|length:10,160',
      },
      {
        $formkit: 'q-input',
        name: 'details',
        label: 'Detailed description',
        inputType: 'textarea',
        validation: 'required|length:20,4000',
      },
    ],
    values: {
      severity: 'medium',
    },
  }),
]

function handleTemplateApply(event: QFormBuilderTemplateApplyEvent): void {
  console.info(event.builderId, event.template.key, event.mode, event.nameMap)
}
</script>

<template>
  <FormBuilder
    builder-id="incident-builder"
    :templates="templates"
    @template-apply="handleTemplateApply"
  />
</template>

With the default append registry mode, custom templates are merged with the four Community templates. A custom definition with the same key overrides the built-in definition.

To expose only the host registry:

vue
<FormBuilder
  :templates="templates"
  template-mode="replace"
/>

Public API

vue
<script setup lang="ts">
import type {
  FormBuilderPublicApi,
  QFormBuilderTemplateApplyEvent,
} from '@vevedh/qform-builder-layer/types'

const builder = ref<FormBuilderPublicApi | null>(null)

function appendContact(): QFormBuilderTemplateApplyEvent | null {
  return builder.value?.applyTemplate('simple-contact', {
    mode: 'append',
    applyValues: true,
    applySettings: false,
  }) ?? null
}

function replaceWithOnboarding(): QFormBuilderTemplateApplyEvent | null {
  return builder.value?.applyTemplate('employee-onboarding', {
    mode: 'replace',
  }) ?? null
}

function listTemplates(): void {
  const templates = builder.value?.getTemplates() ?? []
  console.table(templates.map(({ key, title, category }) => ({ key, title, category })))
}
</script>

<template>
  <FormBuilder ref="builder" builder-id="templates-api" />
</template>

Exposed methods:

ts
getTemplates(): QFormBuilderTemplateRegistry
applyTemplate(
  templateKey: string,
  options?: QFormBuilderTemplateApplyOptions,
): QFormBuilderTemplateApplyEvent | null

The template-apply event contains the schema, values, settings, application mode and nameMap, which maps every source name to the actual inserted name.

Safe append merge

The engine enforces these invariants:

  1. the template schema is sanitized through the same pipeline as JSON imports;
  2. duplicate names inside a template are rejected;
  3. collisions with the current form are suffixed (email, email_2, email_3);
  4. conditions and qform_compare rules are rewritten with the new names;
  5. initial values are remapped;
  6. append is rejected whenever the current form or the template contains a root q-stepper, unless the current form is empty.

Source objects are cloned before transformation, so the host registry is never mutated.

Metadata, values and settings are normalized before exposure: functions, dates, non-finite numbers, custom prototypes and __proto__ / prototype / constructor keys are rejected. Data is bounded to 20 levels, 5,000 nodes and 100,000 characters per string. Preview width is converted to a number and capped at 4,096 px.

The active visual theme always remains owned by the FormBuilder instance: a template cannot replace it. Only formName, previewMode, columns and recognized preview settings can be imported from a template.

Pure helpers

ts
import type {
  FormBuilderSchema,
  FormBuilderSettings,
  FormBuilderValues,
} from '@vevedh/qform-builder-layer/types'
import { enQFormBuilderLocale } from '@vevedh/qform-builder-layer/locales'
import {
  applyQFormBuilderTemplate,
  canAppendQFormBuilderTemplate,
  createCommunityTemplates,
} from '@vevedh/qform-builder-layer/templates'

function applyOutsideComponent(
  schema: FormBuilderSchema,
  values: FormBuilderValues,
  settings: FormBuilderSettings,
): FormBuilderSchema {
  const template = createCommunityTemplates(enQFormBuilderLocale)
    .find(candidate => candidate.key === 'simple-contact')

  if (!template || !canAppendQFormBuilderTemplate(schema, template.schema)) {
    return schema
  }

  return applyQFormBuilderTemplate(
    template,
    { schema, values, settings },
    { mode: 'append' },
  ).schema
}

createCommunityTemplates() accepts a resolved QForm Builder locale. In an application, prefer getTemplates() when host locales overrides must be preserved.

Security and server validation

A template accelerates form design; it is never a security policy. On submission:

  • validate the payload again in FeathersJS/NFZ with Zod;
  • enforce RBAC/ABAC independently from visible or hidden fields;
  • discard unauthorized properties with a strict schema;
  • validate files, URLs, regular expressions and asynchronous rules server-side;
  • version persisted business schemas so migrations remain explicit.

Best practices

  • Use stable, short and versionable keys such as incident-report-v2.
  • Keep field names independent from translated labels.
  • Prefer replace for structural workflows and append for simple blocks.
  • Keep sensitive data out of initial values.
  • Test custom templates with the same locale, validation registry and theme used in production.

QForm Builder — Nuxt 4, Quasar and FormKit layer for dynamic forms.