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
| Key | Use case |
|---|---|
simple-contact | Contact details, subject, message and consent. |
satisfaction-survey | Rating, recommendation, comment and conditional contact. |
admin-crud | Create or update an account in an administration interface. |
employee-onboarding | Multi-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
<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:
<FormBuilder
:templates="templates"
template-mode="replace"
/>Public API
<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:
getTemplates(): QFormBuilderTemplateRegistry
applyTemplate(
templateKey: string,
options?: QFormBuilderTemplateApplyOptions,
): QFormBuilderTemplateApplyEvent | nullThe 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:
- the template schema is sanitized through the same pipeline as JSON imports;
- duplicate names inside a template are rejected;
- collisions with the current form are suffixed (
email,email_2,email_3); - conditions and
qform_comparerules are rewritten with the new names; - initial values are remapped;
- 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
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
replacefor structural workflows andappendfor simple blocks. - Keep sensitive data out of initial values.
- Test custom templates with the same locale, validation registry and theme used in production.