# How to add a field type



## When to use this [#when-to-use-this]

A form generator's stock dispatch doesn't cover a schema shape
you need to render. Common cases: a custom `format` value
mapping to a specific React component (date picker, rich text,
file upload, etc.).

## Prerequisites [#prerequisites]

* The form generator cloned ([tutorial: cloning](/docs/authoring/tutorials/01-cloning-a-generator)).
* A consumer-side React component for the field type (existing
  or about to be written).

## Steps [#steps]

### Create the Snippet class in `src/fields/` [#create-the-snippet-class-in-srcfields]

Form generators have a `src/fields/` subdirectory with one
Snippet per field type:

```ts
// src/fields/DatePickerInput.ts
import type { GenerateContextType } from '@skmtc/core'
import { TsSnippet } from '@skmtc/lang-typescript'

type Args = {
  context: GenerateContextType
  destinationPath: string
  fieldName: string
}

export class DatePickerInput extends TsSnippet {
  #fieldName: string

  constructor(args: Args) {
    super({ context: args.context })
    this.#fieldName = args.fieldName

    this.register({
      destinationPath: args.destinationPath,
      imports: { '@/components/DatePicker': ['DatePicker'] }
    })
  }

  override toString(): string {
    return `<DatePicker {...form.register('${this.#fieldName}')} />`
  }
}
```

The Snippet's `toString()` produces just the JSX for one field.

### Register the field's import [#register-the-fields-import]

The Snippet's constructor (or a `register` call from its parent)
needs to register the import for the consumer-side component:

```ts
constructor(...) {
  super(args)
  this.register({
    destinationPath: args.destinationPath,
    imports: { '@/components/DatePicker': ['DatePicker'] }
  })
}
```

Imports go to the file's `destinationPath`. See
[the Import reference](/docs/reference/api/dsl-import).

### Add a dispatch branch in `schemaToField` [#add-a-dispatch-branch-in-schematofield]

`src/schemaToField.ts` is where the form generator decides which
field renderer to use per schema property:

```ts
// schemaToField.ts (simplified)
import { DatePickerInput } from './fields/DatePickerInput.ts'

export const schemaToField = (args) => {
  const { schema, fieldName, format } = args

  switch (true) {
    case format === 'date': return new DatePickerInput(...)
    case schema.type === 'string' && format === 'email': return new EmailInput(...)
    case schema.type === 'string': return new StringInput(...)
    case schema.type === 'boolean': return new BooleanInput(...)
    // ... your new branch
  }
}
```

Add a `case` for your new field type **before** the more-general
fallbacks. Order matters — first match wins.

### Implement the consumer-side field component [#implement-the-consumer-side-field-component]

The Snippet produces a JSX reference; the actual `<DatePicker />`
component needs to exist on the consumer side. Either add it to
your component library, or import an existing one (e.g.,
`react-day-picker`).

The generator doesn't produce this component — it's user code.

### Rebundle and regenerate [#rebundle-and-regenerate]

```bash
skmtc bundle my-project
skmtc generate my-project
```

## Verification [#verification]

Generate against a schema with a `format: 'date'` field. Inspect
the generated form file — the new field renderer should appear
in place of the stock string input:

```tsx
// src/generated/forms/CreateEvent.generated.tsx
<DatePicker {...form.register('startDate')} />
```

In your app, the form should now render the date picker
component.

## Troubleshooting [#troubleshooting]

* **Stock renderer still used** — Your case statement is
  unreachable. Either a more-general case matches first, or your
  match condition is wrong. Re-check the order in
  `schemaToField`.
* **Component import missing in output** — The `register({
  imports })` call didn't run. Confirm the Snippet's constructor
  actually fires (it does if `schemaToField` reaches the `new
  DatePickerInput(...)` branch).
* **Generated form fails to compile** — Consumer-side
  `DatePicker` component doesn't exist or has a different prop
  shape. Either build it or adjust the Snippet's output.

## Related [#related]

* [Recipe: Custom form field renderer](/docs/authoring/recipes/custom-form-field-renderer) —
  end-to-end example with concrete code
* [gen-shadcn-form reference](/docs/reference/stock-generators/gen-shadcn-form) —
  the source layout this how-to operates against
* [Projections and Snippets concept](/docs/concepts/projections-and-snippets)
