VeeValidate v5 Bridge
VeeValidate v5 accepts Standard Schema-compatible validators as validationSchema.
That means json-rest-schema does not need a heavy VeeValidate-specific runtime adapter. This package now ships a small bridge that wraps a schema instance in the Standard Schema interface VeeValidate already understands.
Import it like this:
import { useForm } from 'vee-validate'
import { createSchema } from 'json-rest-schema'
import { toVeeValidateSchema } from 'json-rest-schema/vee-validate'Basic usage
const profileSchema = createSchema({
name: { type: 'string', required: true, minLength: 3 },
role: { type: 'string', defaultTo: 'guest' }
})
const { handleSubmit, errors, values } = useForm({
initialValues: {
name: ''
},
validationSchema: toVeeValidateSchema(profileSchema)
})That default bridge uses create semantics.
So on successful submit:
- required fields are enforced
- normalized values are returned
- defaults are applied to the submitted output
If the user submits:
{
name: ' Alex '
}then the validated submit payload is equivalent to:
{
name: 'Alex',
role: 'guest'
}Edit forms and custom operations
If the form is editing an existing resource, pass the operation explicitly:
const { handleSubmit } = useForm({
initialValues,
validationSchema: toVeeValidateSchema(profileSchema, {
operation: 'patch'
})
})Custom operations work too:
const { handleSubmit } = useForm({
initialValues,
validationSchema: toVeeValidateSchema(profileSchema, {
operation: 'upsert'
})
})Important VeeValidate limitation: defaults do not initialize form state
This is important enough to say clearly:
- the bridge validates and normalizes the schema output
- VeeValidate still expects you to provide your own
initialValues - schema defaults do not automatically populate the form's starting state
So this is the intended split:
initialValuescontrols the raw form statetoVeeValidateSchema(...)controls validation and normalized submit output
If you want a default field visible in the UI before submit, put it in initialValues.
If you only want the normalized payload to contain the default when the user submits, let the schema apply it.
Error paths
The bridge turns the library's flat error map into Standard Schema issues with nested paths.
So an internal error like:
{
'roles.0.label': {
field: 'roles.0.label',
code: 'REQUIRED',
message: 'Field is required',
params: {}
}
}becomes Standard Schema issues equivalent to:
[
{
message: 'Field is required',
path: ['roles', 0, 'label']
}
]That is what lets VeeValidate map nested array/object errors back onto the right field state.
Worked VeeValidate example
import { useForm } from 'vee-validate'
import { createSchema } from 'json-rest-schema'
import { toVeeValidateSchema } from 'json-rest-schema/vee-validate'
const workspaceSummarySchema = createSchema({
id: { type: 'id', required: true },
slug: { type: 'string', required: true, minLength: 3 },
ownerUserId: { type: 'id', required: true }
})
const workspaceSchema = createSchema({
workspace: {
type: 'object',
required: true,
schema: workspaceSummarySchema
}
})
const { defineField, handleSubmit, errors } = useForm({
initialValues: {
workspace: {
slug: ''
}
},
validationSchema: toVeeValidateSchema(workspaceSchema, {
operation: 'patch'
})
})
const [slug, slugAttrs] = defineField('workspace.slug')
const saveWorkspace = handleSubmit((validatedObject) => {
return api.saveWorkspace(validatedObject)
})<template>
<form @submit.prevent="saveWorkspace">
<input v-model="slug" v-bind="slugAttrs">
<span>{{ errors['workspace.slug'] }}</span>
<button type="submit">Save</button>
</form>
</template>This keeps the responsibilities clean:
- VeeValidate owns touched/dirty/submit orchestration
json-rest-schemaowns validation and normalization- your submit handler owns business logic