A schema-based form generator component for Vue 3 and Nuxt 3.
This is the Vue 3 / Nuxt 3 compatible version of the popular vue-form-generator library, completely rewritten using Vue 3 Composition API and TypeScript.
- β Vue 3 & Nuxt 3 Support - Built from ground up for Vue 3 Composition API
- β Schema-based form generation from JSON
- β Supports all core field types: input, checkbox, checklist, radios, select, textarea, submit, label, upload
- β Built-in validation (required, number, string, email, URL, credit card, date, regex, etc.)
- β Field groups with legends
- β Dynamic field visibility
- β Custom validators
- β Custom field components
- β TypeScript support
- β Zero external dependencies
- β Hot Module Replacement (HMR) for development
npm install vue-form-generator-3
# or
yarn add vue-form-generator-3
# or
pnpm add vue-form-generator-3npm install vue-form-generator-3
# or
yarn add vue-form-generator-3
# or
pnpm add vue-form-generator-3<template>
<VueFormGenerator
:schema="schema"
:model="model"
:options="formOptions"
@model-updated="onModelUpdated"
@validated="onValidated"
/>
</template>
<script setup>
import { ref } from "vue";
import { VueFormGenerator, schema as schemaUtils, validators } from "vue-form-generator-3";
import "vue-form-generator-3/dist/style.css";
const model = ref({
name: "",
email: "",
age: 25,
});
const formOptions = {
validateAfterLoad: false,
validateAfterChanged: true,
};
const schema = {
fields: [
{
type: "input",
inputType: "text",
label: "Name",
model: "name",
placeholder: "Enter your name",
required: true,
validator: ["required", "string"],
},
{
type: "input",
inputType: "email",
label: "Email",
model: "email",
placeholder: "Enter email",
required: true,
validator: ["required", "email"],
},
{
type: "input",
inputType: "number",
label: "Age",
model: "age",
placeholder: "Enter age",
min: 18,
max: 120,
validator: "number",
},
{
type: "submit",
buttonText: "Submit",
validateBeforeSubmit: true,
onSubmit(model) {
console.log("Form submitted:", model);
},
},
],
};
function onModelUpdated(newVal, schema) {
console.log("Field updated:", schema, newVal);
}
function onValidated(isValid, errors) {
console.log("Form valid:", isValid, errors);
}
</script>// main.ts
import { createApp } from "vue";
import App from "./App.vue";
import VueFormGenerator from "vue-form-generator-3";
import "vue-form-generator-3/dist/style.css";
const app = createApp(App);
// Register globally with optional custom validators
app.use(VueFormGenerator, {
validators: {
customValidator: (value, field, model) => {
if (value !== "expected") {
return ["Value must be 'expected'"];
}
return [];
},
},
});
app.mount("#app");Option 1: Using Nuxt Module (Recommended)
// nuxt.config.ts
export default defineNuxtConfig({
modules: ["vue-form-generator-3/src/module"],
css: ["vue-form-generator-3/dist/style.css"],
});Option 2: Using Plugin
// plugins/vue-form-generator.ts
import VueFormGenerator from "vue-form-generator-3";
export default defineNuxtPlugin((nuxtApp) => {
nuxtApp.vueApp.use(VueFormGenerator, {
validators: {
// Optional: Add custom validators
},
});
});// nuxt.config.ts
export default defineNuxtConfig({
css: ["vue-form-generator-3/dist/style.css"],
});| Type | Component | Description |
|---|---|---|
input |
field-input | Text, number, email, password, date, etc. |
checkbox |
field-checkbox | Single checkbox |
checklist |
field-checklist | Multiple checkbox list |
radios |
field-radios | Radio button group |
select |
field-select | Dropdown select |
textarea |
field-textarea | Multi-line text input |
submit |
field-submit | Submit button |
label |
field-label | Display-only label |
upload |
field-upload | File upload |
Each field in the schema can have:
type- Field type (required)inputType- For input fields: text, number, email, password, date, etc.label- Field labelmodel- Model property path (e.g., "user.name")placeholder- Placeholder textrequired- Whether field is requireddisabled- Whether field is disabledreadonly- Whether field is readonlyvisible- Visibility condition (boolean or function)hint- Hint text below the fieldhelp- Help tooltip textvalidator- Validator(s): function name string or arrayvalues- For select, checklist, radios: array of optionsdefault- Default valuestyleClasses- Additional CSS classesonChanged- Change callbackonValidated- Validation callbackfieldClasses- Additional classes for field wrapper
min/max- Min/max values for number/dateminlength/maxlength- Text length limitspattern- Regex pattern for validationaccept- File types for uploaddebounceFormatTimeout- Debounce time for formatting
const schema = {
groups: [
{
legend: "Personal Information",
fields: [
{
type: "input",
inputType: "text",
label: "First Name",
model: "firstName",
required: true,
},
{
type: "input",
inputType: "text",
label: "Last Name",
model: "lastName",
required: true,
},
],
},
{
legend: "Contact Details",
fields: [
{
type: "input",
inputType: "email",
label: "Email",
model: "email",
validator: "email",
},
{
type: "input",
inputType: "tel",
label: "Phone",
model: "phone",
},
],
},
],
};const schema = {
fields: [
{
type: "input",
inputType: "text",
label: "Company Name",
model: "company",
visible: (model, field, form) => {
return model.hasCompany === true;
},
},
{
type: "checkbox",
label: "I have a company",
model: "hasCompany",
},
],
};import { VueFormGenerator } from "vue-form-generator-3";
app.use(VueFormGenerator, {
validators: {
phoneNumber(value, field, model) {
if (!value) return [];
const phoneRegex = /^[0-9]{10}$/;
if (!phoneRegex.test(value)) {
return ["Invalid phone number. Must be 10 digits."];
}
return [];
},
passwordStrength(value, field, model) {
if (!value) return [];
const errors = [];
if (value.length < 8) {
errors.push("Password must be at least 8 characters");
}
if (!/[A-Z]/.test(value)) {
errors.push("Password must contain uppercase letter");
}
if (!/[0-9]/.test(value)) {
errors.push("Password must contain a number");
}
return errors;
},
},
});
// Use in schema
const schema = {
fields: [
{
type: "input",
inputType: "tel",
label: "Phone",
model: "phone",
validator: "phoneNumber",
},
{
type: "input",
inputType: "password",
label: "Password",
model: "password",
validator: "passwordStrength",
},
],
};Create a new Vue component with the following structure:
<!-- CustomField.vue -->
<template>
<div class="custom-field-wrapper">
<label :for="getFieldID(schema)">
{{ schema.label }}
</label>
<div class="field-content">
<input
:id="getFieldID(schema)"
:value="value.get()"
@input="onInput"
:disabled="disabled"
class="custom-input"
/>
</div>
<div v-if="fieldErrors(schema).length > 0" class="errors">
<span v-for="error in fieldErrors(schema)" :key="error">
{{ error }}
</span>
</div>
</div>
</template>
<script setup lang="ts">
import { getCurrentInstance } from "vue";
import { useAbstractField } from "vue-form-generator-3";
const props = defineProps({
vfg: Object,
model: Object,
schema: Object,
formOptions: Object,
disabled: Boolean,
});
const emit = defineEmits(["model-updated", "validated"]);
const instance = getCurrentInstance();
const { value, validate, clearValidationErrors, getFieldID, getFieldClasses, updateModelValue } = useAbstractField(
props,
emit,
instance
);
const onInput = (event: Event) => {
const target = event.target as HTMLInputElement;
updateModelValue(target.value, value.get());
};
const fieldErrors = (schema: any) => {
// Access errors from parent or manage locally
return [];
};
defineExpose({ validate, clearValidationErrors });
</script>
<style scoped>
.custom-field-wrapper {
margin-bottom: 1rem;
}
.custom-input {
width: 100%;
padding: 8px;
border: 1px solid #ccc;
border-radius: 4px;
}
.errors {
color: red;
font-size: 0.875em;
margin-top: 0.25rem;
}
</style>// main.ts
import { createApp } from "vue";
import App from "./App.vue";
import VueFormGenerator, { registerFieldComponent } from "vue-form-generator-3";
import CustomField from "./components/CustomField.vue";
const app = createApp(App);
// Register the plugin
app.use(VueFormGenerator);
// Register custom field component
registerFieldComponent("custom-field", CustomField);
app.mount("#app");// plugins/vue-form-generator.ts
import { registerFieldComponent } from "vue-form-generator-3";
import CustomField from "~/components/CustomField.vue";
export default defineNuxtPlugin((nuxtApp) => {
// Register custom field
registerFieldComponent("custom-field", CustomField);
});// nuxt.config.ts
export default defineNuxtConfig({
modules: ["vue-form-generator-3/src/module"],
css: ["vue-form-generator-3/dist/style.css"],
});const schema = {
fields: [
{
type: "custom-field", // Use your custom field type
label: "Custom Input",
model: "customValue",
placeholder: "Enter custom value",
},
],
};This library includes a playground/example that demonstrates all features:
# Install dependencies
npm install
# Run development server
npm run dev
# Open browser to http://localhost:5173/The playground includes:
- All field types (input, select, checkbox, radios, textarea, submit)
- Form validation with built-in validators
- Model updates and validation events
- Field groups with legends
- Dynamic field visibility
- Custom styling examples
# Build the library
npm run build
# Output files in dist/:
# - vue-form-generator-3.js (ES module, 41KB)
# - vue-form-generator-3.umd.cjs (UMD, 32KB)
# - style.css (6KB)Main form generator component.
Props:
schema(Object) - Form schema definitionmodel(Object) - Form data modeloptions(Object) - Form optionsmultiple(Boolean) - Enable multiple modeisNewModel(Boolean) - Mark as new model
Events:
@model-updated(newVal, schema)- Fired when any field value changes@validated(isValid, errors, vfg)- Fired after validation
Methods (via ref):
validate()- Validate all fieldsclearValidationErrors()- Clear all validation errors
Wrapper component for each field (used internally).
Schema utility functions.
import { schema } from "vue-form-generator-3";
// Available utilities
schema.slugifyFormID(schema, prefix); // Generate field IDBuilt-in validator functions.
import { validators } from "vue-form-generator-3";
// Available validators
validators.required;
validators.number;
validators.integer;
validators.double;
validators.string;
validators.array;
validators.date;
validators.regexp;
validators.email;
validators.url;
validators.creditCard;
validators.alpha;
validators.alphaNumeric;Register a custom field component.
import { registerFieldComponent } from "vue-form-generator-3";
import MyCustomField from "./MyCustomField.vue";
registerFieldComponent("my-custom", MyCustomField);If you're migrating from the original vue-form-generator for Vue 2:
- Composition API - All components now use
<script setup>syntax - TypeScript - Full TypeScript support with type definitions
- No lodash dependency - Built-in utility functions
- Vue 3 reactivity - Uses Vue 3's reactive system
- Nuxt 3 module - Native Nuxt 3 support
Most schema definitions work as-is. Key differences:
- Component registration uses
registerFieldComponent()instead of Vue 2 plugin system - Event handling uses Vue 3 emit syntax
- Custom validators receive same parameters
Before (Vue 2):
import VueFormGenerator from "vue-form-generator";
Vue.use(VueFormGenerator);After (Vue 3):
import { createApp } from "vue";
import VueFormGenerator from "vue-form-generator-3";
const app = createApp(App);
app.use(VueFormGenerator);Contributions are welcome! Please feel free to submit a Pull Request.
MIT
- Original Library (Vue 2): https://github.com/vue-generators/vue-form-generator
- Documentation: This README
- Issues: https://github.com/vue-generators/vue-form-generator-3/issues
This library is a Vue 3 / Nuxt 3 port of the original vue-form-generator library. Thanks to all contributors of the original project.