@letar/forms

Utilities

Schema helpers, meta functions, and relation providers

UI Metadata Helpers

Functions to enrich Zod schemas with UI metadata.

withUIMeta / withUIMetaDeep

Add UI metadata to schema fields:

import { withUIMeta, withUIMetaDeep } from '@letar/forms'

// Add meta to a single field
const nameField = withUIMeta(z.string(), {
  title: 'Full Name',
  placeholder: 'Enter name',
})

// Add meta to all fields in an object
const Schema = withUIMetaDeep(
  z.object({
    name: z.string(),
    email: z.string().email(),
  }),
  {
    name: { title: 'Name' },
    email: { title: 'Email' },
  }
)

Type-specific meta helpers

import { textMeta, numberMeta, booleanMeta, dateMeta, enumMeta, relationMeta } from '@letar/forms'

z.string().meta(textMeta('Title', { placeholder: 'Enter...' }))
z.number().meta(numberMeta('Price', { min: 0, currency: 'USD' }))
z.boolean().meta(booleanMeta('Active'))
z.date().meta(dateMeta('Birthday'))
z.enum(['a', 'b']).meta(enumMeta('Status'))

Schema Traversal

Utilities for walking Zod schemas.

traverseSchema

Walk all fields in a schema:

import { traverseSchema } from '@letar/forms'

traverseSchema(Schema, (path, fieldSchema) => {
  console.log(path, fieldSchema._def.typeName)
})

getFieldPaths

Get all field paths from a schema:

import { getFieldPaths } from '@letar/forms'

const paths = getFieldPaths(Schema)
// ['name', 'email', 'address.street', 'address.city']

Constraint Extraction

getZodConstraints

Extract validation constraints from a Zod schema:

import { getZodConstraints } from '@letar/forms'

const constraints = getZodConstraints(z.string().min(2).max(100).email())
// { minLength: 2, maxLength: 100, format: 'email' }

generateConstraintHint

Generate human-readable hint text from constraints:

import { generateConstraintHint } from '@letar/forms'

const hint = generateConstraintHint(constraints)
// "2–100 characters, must be a valid email"

Field Type Mapping

resolveFieldType

Determine which field component to render based on schema type:

import { resolveFieldType } from '@letar/forms'

const fieldType = resolveFieldType(z.string().email())
// 'string' (with type="email" hint)

createForm

Create extended forms with custom fields, selects, and lazy loading. See the full createForm guide for details.

import { createForm } from '@letar/forms'

const AppForm = createForm({
  extraFields: { PlateNumber: FieldPlateNumber },
  extraSelects: { VideoCodec: SelectVideoCodec },
  lazySelects: {
    Category: () => import('./selects/select-category').then((m) => m.SelectCategory),
  },
})

// Use: <AppForm.Field.PlateNumber name="plate" />
// Use: <AppForm.Select.VideoCodec name="codec" />

RelationFieldProvider

Provides relation data to form fields. Wraps the form component.

import { RelationFieldProvider } from '@letar/forms'

;<RelationFieldProvider
  relations={{
    category: {
      useQuery: useFindManyCategory,
      labelField: 'name',
      valueField: 'id',
    },
  }}
>
  <Form schema={Schema} onSubmit={save}>
    <Form.Field.Select name="categoryId" />
  </Form>
</RelationFieldProvider>
PropTypeDescription
relationsRecord<string, RelationConfig>Map of relation name → config
childrenReactNodeForm content

RelationConfig

PropTypeDescription
useQuery() => { data, isLoading }Hook returning data array
labelFieldstringField to use as option label
valueFieldstringField to use as option value (default: 'id')
descriptionFieldstring?Field for option description

withRelations

HOC alternative to RelationFieldProvider:

import { withRelations } from '@letar/forms'

const EnhancedForm = withRelations(BaseForm, {
  category: { useQuery: useFindManyCategory, labelField: 'name' },
})

// Use: <EnhancedForm schema={Schema} onSubmit={save} />

See Relation Fields for full guide.

On this page