21

Schema input and output

Understand how NotForm types the values users edit separately from the validated data your submit handler receives.

A schema can accept one shape and produce another. NotForm keeps these two shapes distinct everywhere:

InputOutput
What it isWhat users edit, before parsingWhat the schema produces after validating
Where it appearsform.values, initialValues, field.model, form.setValue()The argument of onSubmit, and { value } from form.validate()
Type helperSchemaInput<typeof schema>SchemaOutput<typeof schema>

A transform changes the output

import { z } from 'zod'

const orderSchema = z.object({
  quantity: z.string().transform(Number),
})

const form = useForm({
  initialValues: { quantity: '2' },
  onSubmit(data) {
    data.quantity.toFixed(0) // data.quantity is a number
  },
  schema: orderSchema,
})

form.values.quantity // a string

The input control holds text, so the form value stays a string. Your handler receives the converted number. The same distinction applies to every Standard Schema validator.

Common ways input and output differ

Schema featureEffect on the shapes
Transform or coercionThe output type differs from the input type.
Default valueThe key is optional in the input and required in the output.
Trimming or normalizingform.values keeps what the user typed. The output is normalized.
Pipes into another schemaThe output is the final stage. The input is the first.

Because form.values always holds the input, the text a user typed is never silently rewritten. Normalization happens only in the data you receive on submit.

Initial values

initialValues use the input shape, and may be partial. They are the values controls display, so use the representation your controls and schema expect, not the parsed output.

useForm({
  initialValues: { quantity: '2' }, // input shape, not a number
  schema,
})

Though not mandatory, provide a value for every field you bind. A key missing from initialValues reaches the schema as undefined, and validators then report their generic type message instead of your custom one. Empty strings, false, and [] are the usual starting values for text, checkboxes, and lists.

Typed paths and values

Paths and values for form.field(), form.arrayField(), and form.setValue() come from the input shape. A path that does not exist is a TypeScript error, and setValue checks the value type.

form.setValue('quantity', '3') // ok
form.setValue('quantity', 3) // type error: input is a string
form.field('quantty') // type error: no such path

Naming the types

Import the helpers when you need to name a shape, for example in a function signature.

import type { SchemaInput, SchemaOutput } from 'notform'

type OrderInput = SchemaInput<typeof orderSchema>
type OrderOutput = SchemaOutput<typeof orderSchema>

async function saveOrder(order: OrderOutput) {
  // ...
}

See TypeScript for more patterns.

Next steps

Copyright © 2026 NotForm
Published under MIT License