# Forms (https://form.dimah.dev/docs/forms)



Forms are TypeScript (`defineForm`) or rows in your database (`saveForm`). Code-authored forms feed `$Infer`. Database forms do not.

***

## Authoring Forms in TypeScript [#authoring-forms-in-typescript]

We recommend defining each questionnaire in a dedicated file under `lib/forms/`:

```ts title="lib/forms/onboarding.ts"
import { defineForm } from "@dimah-form/server";

export const onboardingForm = defineForm({
  title: "Employee Onboarding",
  slug: "onboarding",
  description: "Complete your profile and equipment preferences.",
  status: "active",
  fields: [
    {
      id: "fullName",
      type: "text",
      label: "Full Name",
      required: true,
      minLength: 2,
      maxLength: 100,
    },
    {
      id: "department",
      type: "select",
      label: "Department",
      required: true,
      options: [
        { value: "engineering", label: "Engineering" },
        { value: "design", label: "Product Design" },
        { value: "marketing", label: "Marketing" },
      ],
    },
    {
      id: "githubHandle",
      type: "text",
      label: "GitHub Username",
      required: true,
      showWhen: {
        field: "department",
        equals: "engineering",
      },
    },
    {
      id: "portfolioUrl",
      type: "text",
      label: "Portfolio Link",
      showWhen: {
        field: "department",
        equals: "design",
      },
    },
    {
      id: "newsletter",
      type: "boolean",
      label: "Subscribe to company updates",
      defaultValue: true,
    },
  ],
});
```

Export your forms from a central index:

```ts title="lib/forms/index.ts"
import { onboardingForm } from "./onboarding";

export const forms = {
  onboarding: onboardingForm,
};
```

Pass the forms dictionary into `dimahForm()`:

```ts title="lib/form.ts"
import { dimahForm, memoryAdapter } from "@dimah-form/server";
import { forms } from "@/lib/forms";

export const form = dimahForm({
  database: memoryAdapter(),
  forms,
});

export type Form = typeof form;
```

***

## Form Properties [#form-properties]

| Property      | Type                                | Default     | Description                                                                    |
| :------------ | :---------------------------------- | :---------- | :----------------------------------------------------------------------------- |
| `title`       | `string`                            | —           | Human-readable title of the questionnaire.                                     |
| `slug`        | `string`                            | Catalog key | Unique URL-friendly slug. `getForm` and `startResponse` accept `id` or `slug`. |
| `description` | `string`                            | `undefined` | Optional subtitle or instructions for respondents.                             |
| `status`      | `"active" \| "draft" \| "archived"` | `"active"`  | Only `"active"` forms can start new response sessions.                         |
| `fields`      | `FormField[]`                       | `[]`        | Array of field definitions.                                                    |
| `meta`        | `Record<string, unknown>`           | `{}`        | Custom JSON dictionary for UI layout hints, icons, or category tags.           |

***

## Form Status Lifecycle [#form-status-lifecycle]

* **`active`**: Form is published and accepts new responses via `startResponse`.
* **`draft`**: Form is still being designed. Starting a response returns `409 FORM_INACTIVE`.
* **`archived`**: Form is deprecated. Existing submitted responses remain readable, but new responses are blocked.

***

## Field Anatomy [#field-anatomy]

Each item in `fields` requires an `id`, a `type`, and an optional `label`:

```ts
{
  id: "workEmail",
  type: "email",
  label: "Work Email Address",
  required: true,
  defaultValue: "user@example.com",
  meta: {
    placeholder: "you@company.com",
    tooltip: "We will send your onboarding packet here.",
  },
}
```

### Common Field Attributes [#common-field-attributes]

* **`id`**: Unique string identifier within the form. Serves as the key in the answers dictionary.
* **`type`**: Built-in type (`"text"`, `"number"`, `"boolean"`, `"select"`, `"multiSelect"`, `"email"`, `"date"`) or a custom type registered via `defineFieldType`.
* **`label`**: Display label for UI components (retrieved via `fieldLabel(field)`).
* **`required`**: If `true`, the field must be non-empty when submitting the form (if visible).
* **`defaultValue`**: Seeded into the answers map upon calling `startResponse`.
* **`showWhen`**: Conditional visibility rules.
* **`meta`**: Arbitrary metadata for UI customization (e.g., placeholder, step number, layout width).

***

## Conditional Visibility (`showWhen`) [#conditional-visibility-showwhen]

Fields can dynamically show or hide based on the current answers. When a field is hidden, it is excluded from `visibleFields` and its answer is stripped before final submission.

### 1. Simple Conditions [#1-simple-conditions]

```ts
// Check exact value match
showWhen: { field: "role", equals: "manager" }

// Check inequality
showWhen: { field: "plan", notEquals: "free" }

// Check if a multiSelect array contains a specific option
showWhen: { field: "techStack", includes: "typescript" }
```

### 2. Compound Conditions (`all` / `any`) [#2-compound-conditions-all--any]

```ts
// AND condition (all must match)
showWhen: {
  all: [
    { field: "experience", equals: "senior" },
    { field: "location", equals: "remote" },
  ],
}

// OR condition (at least one must match)
showWhen: {
  any: [
    { field: "department", equals: "engineering" },
    { field: "department", equals: "product" },
  ],
}
```

***

## Code-Authored vs Database Forms [#code-authored-vs-database-forms]

| Feature                  | Code-Authored Forms (`forms: {}`)                | Dynamic Database Forms (`saveForm()`)        |
| :----------------------- | :----------------------------------------------- | :------------------------------------------- |
| **Declaration**          | TypeScript files via `defineForm`                | Admin UI / CMS via `saveForm` API endpoint   |
| **Type Inference**       | Full compile-time `$Infer` typing                | `Record<string, unknown>`                    |
| **Version Control**      | Tracked in Git alongside application code        | Stored in the `questionnaire` database table |
| **Overwrite Protection** | Protected from accidental API deletion / updates | Can be modified or deleted via API routes    |

***

## End-to-End Type Inference (`$Infer`) [#end-to-end-type-inference-infer]

When you export `export type Form = typeof form`, dimah-form automatically derives the exact shape of your answers:

```ts
import type { Form } from "@/lib/form";

type OnboardingAnswers = Form["$Infer"]["answers"]["onboarding"];

// TypeScript automatically infers:
// type OnboardingAnswers = {
//   fullName: string;
//   department: "engineering" | "design" | "marketing";
//   githubHandle?: string;
//   portfolioUrl?: string;
//   newsletter?: boolean;
// }
```

Use this type in your React client:

```tsx
const q = useFormResponse<Form["$Infer"]["answers"]["onboarding"]>({
  snapshot: formSnapshot,
});
```

***

## Next Steps [#next-steps]

<Cards>
  <Card title="React Client" href="/docs/react" description="Learn how useFormResponse handles visible fields and answer bindings." />

  <Card title="Widgets" href="/docs/widgets" description="Build reusable UI widgets for text, select, and boolean fields." />

  <Card title="Field Types Reference" href="/docs/field-types" description="Explore built-in field validation constraints and options." />
</Cards>
