Type Definitions

md2form is written entirely in TypeScript, enabling type-safe form processing. This page provides detailed explanations of all type definitions.

v2 API Types

New types for parse results, options, and diagnostics were added in v2.

ParseResult

The return type of parseForm.

type ParseResult = {
  document: FormDocument // Parsed form definition
  diagnostics: Diagnostic[] // Error, warning, and information messages
  ok: boolean // false if there are errors in strict mode
}

ParseOptions

The options type passed to parseForm / createParser.

type ParseOptions = {
  strict?: boolean // Default: false
  defaultTitle?: string // Default: "Untitled Form"
  validateSettings?: boolean // Default: true
}

ParsedFormDocument

A branded type when ok === true.

type ParsedFormDocument = FormDocument & {
  readonly __brand?: unique symbol
}

Diagnostic

The type for individual diagnostic messages.

type Diagnostic = {
  code: DiagnosticCode
  severity: Severity
  message: string
  line?: number
  column?: number
  path?: string
}

DiagnosticCode

type DiagnosticCode =
  | "MISSING_FORM_TITLE"
  | "MISSING_ELEMENT_TYPE"
  | "UNSUPPORTED_ELEMENT_TYPE"
  | "UNSUPPORTED_PROPERTY"
  | "INVALID_PROPERTY_VALUE"
  | "MISSING_REQUIRED_FIELD"
  | "PROPERTY_BEFORE_TYPE"
  | "ORPHAN_QUESTION"
  | "AMBIGUOUS_STRUCTURE"
  | "UNKNOWN_SETTING_KEY"
  | "INVALID_SETTING_VALUE"

Severity

type Severity = "error" | "warning" | "info"

Parser

The return type of createParser.

type Parser = {
  parse(markdown: string): ParseResult
}

Form Definition Types

FormDocument

The main type representing an entire parsed form.

type FormDocument = {
  schemaVersion?: number // Schema version (v2 = 2)
  title: string // Form title (required)
  description?: string // Form description (optional)
  settings?: FormSettings // Form settings (optional)
  pages: Page[] // Array of pages (sections)
}

Usage Example:

import { parseForm, isParsedDocument } from "md2form"
import type { FormDocument } from "md2form"
 
const result = parseForm(markdown)
const form: FormDocument = result.document
console.log(form.title) // string
console.log(form.schemaVersion) // 2
console.log(form.pages.length) // number

FormSettings

Type representing form-wide settings configured in frontmatter.

type FormSettings = {
  collectEmail?: boolean // Collect email addresses
  allowMultipleResponses?: boolean // Allow multiple responses
  limitResponses?: number | null // Limit number of responses
  showProgressBar?: boolean // Show progress bar
  shuffleQuestions?: boolean // Shuffle questions
  themeColor?: string // Theme color
  backgroundImage?: string // Background image
  font?: string // Font
  responseReceipt?: "always" | "never" | "whenRequested" // Response receipt
}

Page

Type representing a single page (section) in a form.

type Page = {
  title?: string // Page title (optional)
  description?: string // Page description (optional)
  elements: FormElement[] // Array of question elements
}

FormElementBase

Base properties common to all question elements.

type FormElementBase = {
  type: ElementType // Question type (required)
  label?: string // Question title from ### heading (v2)
  description?: string // Supplementary text after heading
  required?: boolean // Required input flag
  visible?: boolean // Visibility flag
}

v2 Changes: The label field stores the content of ### question heading. In v1, the same value was in description, but in v2 description is the supplementary paragraph text after the heading.

Question Type Enumeration

type ElementType =
  | "short_text" // Short text input
  | "long_text" // Long text input
  | "number" // Numeric input
  | "email" // Email address input
  | "phone" // Phone number input
  | "dropdown" // Dropdown selection
  | "radio" // Radio button selection
  | "checkbox" // Checkbox selection
  | "date" // Date selection
  | "time" // Time selection
  | "rating" // Star rating
  | "likert" // Likert scale
  | "matrix" // Matrix (grid)
  | "file_upload" // File upload
  | "section_header" // Section header
  | "scale" // Scale (slider)
  | "signature" // Digital signature
  | "image" // Image display
  | "video" // Video display
  | "boolean" // Yes/No selection

v2 Changes: The "unknown" type from v1 was removed in v2. Unknown types are reported as diagnostics (UNSUPPORTED_ELEMENT_TYPE).

FormElement Union Type

A union type containing all question elements.

type FormElement =
  | ShortText
  | LongText
  | NumberField
  | EmailField
  | PhoneField
  | DropdownField
  | RadioField
  | CheckboxField
  | DateField
  | TimeField
  | RatingField
  | LikertField
  | MatrixField
  | FileUploadField
  | SectionHeader
  | ScaleField
  | SignatureField
  | MediaField
  | BooleanField

Detailed Question Type Definitions

Text Input

ShortText

type ShortText = FormElementBase & {
  type: "short_text"
  placeholder?: string // Placeholder text
  maxLength?: number // Maximum number of characters
  default?: string // Default value
}

LongText

type LongText = FormElementBase & {
  type: "long_text"
  placeholder?: string // Placeholder text
  maxLength?: number // Maximum number of characters
  default?: string // Default value
  richText?: boolean // Enable rich text editing
}

NumberField

type NumberField = FormElementBase & {
  type: "number"
  placeholder?: string // Placeholder text
  min?: number // Minimum value
  max?: number // Maximum value
  step?: number // Increment value
  default?: number | null // Default value
  integerOnly?: boolean // Allow only integers
}

EmailField

type EmailField = FormElementBase & {
  type: "email"
  placeholder?: string // Placeholder text
  default?: string // Default value
  allowMultiple?: boolean // Allow multiple email addresses
}

PhoneField

type PhoneField = FormElementBase & {
  type: "phone"
  placeholder?: string // Placeholder text
  countryCodeRequired?: boolean // Require country code
  default?: string // Default value
}

Selection

type DropdownField = FormElementBase & {
  type: "dropdown"
  options: string[] // Choices
  allowOther?: boolean // Allow "Other" choice
  multiple?: false // Multiple selection (always false)
  default?: string | null // Default selected value
  searchable?: boolean // Enable search functionality
}

RadioField

type RadioField = FormElementBase & {
  type: "radio"
  options: string[] // Choices
  allowOther?: boolean // Allow "Other" choice
  default?: string | null // Default selected value
}

CheckboxField

type CheckboxField = FormElementBase & {
  type: "checkbox"
  options: string[] // Choices
  minSelected?: number | null // Minimum number of selections
  maxSelected?: number | null // Maximum number of selections
  default?: string[] | null // Default selected values array
}

Date/Time

DateField

type DateField = FormElementBase & {
  type: "date"
  includeTime?: boolean // Whether to include time
  minDate?: string // Minimum date (ISO format)
  maxDate?: string // Maximum date (ISO format)
  default?: string | null // Default date
}

TimeField

type TimeField = FormElementBase & {
  type: "time"
  minTime?: string // Minimum time (HH:MM)
  maxTime?: string // Maximum time (HH:MM)
  stepMinutes?: number // Increment (minutes)
  default?: string | null // Default time
}

Rating/Scale

RatingField

type RatingField = FormElementBase & {
  type: "rating"
  scale?: number // Number of rating levels
  labels?: { low?: string; high?: string } // Lowest and highest labels
  default?: number | null // Default rating
  icon?: "star" | "heart" | "circle" // Icon type
}

LikertField

type LikertField = FormElementBase & {
  type: "likert"
  statements: string[] // Rating items (rows)
  scaleLabels: string[] // Rating scale (columns)
  requiredPerStatement?: boolean // Rate each item as required
}

MatrixField

type MatrixField = FormElementBase & {
  type: "matrix"
  rows: string[] // Row labels
  columns: string[] // Column labels
  cellType?: "radio" | "checkbox" | "number" | "short_text" // Cell type
  requiredPerRow?: boolean // Each row requires input
}

ScaleField

type ScaleField = FormElementBase & {
  type: "scale"
  min: number // Minimum value
  max: number // Maximum value
  step?: number // Increment value
  minLabel?: string // Minimum value label
  maxLabel?: string // Maximum value label
  default?: number | null // Default value
}

File/Signature

FileUploadField

type FileUploadField = FormElementBase & {
  type: "file_upload"
  allowedTypes?: string[] // Allowed file formats
  maxFiles?: number // Maximum number of files
  maxSizeMB?: number // Maximum file size (MB)
}

SignatureField

type SignatureField = FormElementBase & {
  type: "signature"
  captureMode?: "draw" | "type" | "upload" // Signature capture method
  required?: boolean // Required signature
}

Media/Display

MediaField

type MediaField = FormElementBase & {
  type: "image" | "video"
  src: string // Media URL
  alt?: string // Alternative text (image only)
  width?: number | "auto" // Width
  height?: number | "auto" // Height
  caption?: string // Caption
}

SectionHeader

type SectionHeader = FormElementBase & {
  type: "section_header"
  title?: string // Main title
  subtitle?: string // Subtitle
}

Other

BooleanField

type BooleanField = FormElementBase & {
  type: "boolean"
  onLabel?: string // Label for true state
  offLabel?: string // Label for false state
  default?: boolean | null // Default value
}

v2 Changes: The UnknownElement type from v1 was removed in v2. Unknown #type values are reported as UNSUPPORTED_ELEMENT_TYPE diagnostics.

Convenient Type Aliases

TextInputElement

Union type of text input elements:

type TextInputElement = ShortText | LongText | NumberField | EmailField | PhoneField

Type Guard Implementation

Type guard functions for safely processing form elements in TypeScript:

import type { FormElement, ShortText, NumberField, RatingField } from "md2form"
 
// Basic type guards
function isShortText(element: FormElement): element is ShortText {
  return element.type === "short_text"
}
 
function isNumberField(element: FormElement): element is NumberField {
  return element.type === "number"
}
 
function isRatingField(element: FormElement): element is RatingField {
  return element.type === "rating"
}
 
// Generic type guard
function isElementOfType<T extends FormElement>(
  element: FormElement,
  type: T["type"],
): element is T {
  return element.type === type
}
 
// Usage example
form.pages.forEach((page) => {
  page.elements.forEach((element) => {
    if (isShortText(element)) {
      console.log(element.placeholder) // Type safe
    }
 
    if (isElementOfType<NumberField>(element, "number")) {
      console.log(element.min, element.max) // Type safe
    }
  })
})

Practical Type Usage Examples

Type-Safe Form Processing

import { parseForm, isParsedDocument } from "md2form"
import type { FormDocument, FormElement, ShortText, NumberField, CheckboxField } from "md2form"
 
// Usage example: parse and pass to FormProcessor
const result = parseForm(markdown, { strict: true })
if (!isParsedDocument(result)) throw new Error("Parse failed")
 
// Form processing class
class FormProcessor {
  private form: FormDocument
 
  constructor(form: FormDocument) {
    this.form = form
  }
 
  // Type-safe element extraction
  getElementsByType<T extends FormElement>(type: T["type"]): T[] {
    const elements: T[] = []
 
    this.form.pages.forEach((page) => {
      page.elements.forEach((element) => {
        if (element.type === type) {
          elements.push(element as T)
        }
      })
    })
 
    return elements
  }
 
  // Get required elements
  getRequiredElements(): FormElement[] {
    const required: FormElement[] = []
 
    this.form.pages.forEach((page) => {
      page.elements.forEach((element) => {
        if (element.required) {
          required.push(element)
        }
      })
    })
 
    return required
  }
 
  // Get statistics
  getStatistics(): {
    totalElements: number
    requiredElements: number
    elementTypeCount: Record<string, number>
  } {
    let totalElements = 0
    let requiredElements = 0
    const elementTypeCount: Record<string, number> = {}
 
    this.form.pages.forEach((page) => {
      page.elements.forEach((element) => {
        totalElements++
 
        if (element.required) {
          requiredElements++
        }
 
        elementTypeCount[element.type] = (elementTypeCount[element.type] || 0) + 1
      })
    })
 
    return {
      totalElements,
      requiredElements,
      elementTypeCount,
    }
  }
}

Validation Functions

import type { FormElement, ShortText, LongText, NumberField, CheckboxField } from "md2form"
 
// Type-safe validation function
function validateFormElement(element: FormElement): string[] {
  const errors: string[] = []
 
  // Common validation
  if (element.required && !element.label) {
    errors.push("Required questions must have a label")
  }
 
  // Type-specific validation
  switch (element.type) {
    case "short_text":
    case "long_text":
      const textElement = element as ShortText | LongText
      if (textElement.maxLength && textElement.maxLength <= 0) {
        errors.push("maxLength must be a positive value")
      }
      break
 
    case "number":
      const numberElement = element as NumberField
      if (
        numberElement.min !== undefined &&
        numberElement.max !== undefined &&
        numberElement.min > numberElement.max
      ) {
        errors.push("Minimum value must be less than or equal to maximum value")
      }
      break
 
    case "checkbox":
      const checkboxElement = element as CheckboxField
      if (checkboxElement.options.length === 0) {
        errors.push("Checkboxes must have choices")
      }
      if (
        checkboxElement.maxSelected &&
        checkboxElement.maxSelected > checkboxElement.options.length
      ) {
        errors.push("Maximum selections must be less than or equal to number of choices")
      }
      break
  }
 
  return errors
}

Custom Type Extension

import type { FormSettings, FormDocument } from "md2form"
 
// Extend custom settings type
interface ExtendedFormSettings extends FormSettings {
  customBranding?: {
    logo?: string
    primaryColor?: string
    secondaryColor?: string
  }
  analytics?: {
    trackingId?: string
    enableHeatmap?: boolean
  }
}
 
// Extended form document type
interface ExtendedFormDocument extends Omit<FormDocument, "settings"> {
  settings?: ExtendedFormSettings
}
 
// Usage example
function processExtendedForm(form: ExtendedFormDocument) {
  console.log(form.title)
 
  if (form.settings?.customBranding?.logo) {
    console.log("Logo:", form.settings.customBranding.logo)
  }
 
  if (form.settings?.analytics?.trackingId) {
    console.log("Tracking ID:", form.settings.analytics.trackingId)
  }
}

Benefits of Type Definitions

1. Development-Time Safety

import type { ShortText } from "md2form"
 
// Compile-time error detection
const element: ShortText = {
  type: "short_text",
  label: "Your Name",
  maxLenght: 100, // ❌ Typo - compile error
}
 
// Correct usage
const element: ShortText = {
  type: "short_text",
  label: "Your Name",
  maxLength: 100, // ✅ Correct
}

2. IntelliSense Support

Auto-completion and documentation are available in the IDE.

3. Safe Refactoring

Type definitions enable safe refactoring even during large-scale changes.

JSON Type Definitions (Supplementary)

Auxiliary types related to JSON processing are defined in src/types/json.types.ts. These can also be imported directly from md2form:

import type { JsonValue, JsonObject, JsonArray } from "md2form"
 
// JSON value type definition
type JsonValue = string | number | boolean | null | JsonObject | JsonArray
 
interface JsonObject {
  [key: string]: JsonValue
}
 
interface JsonArray extends Array<JsonValue> {}
 
// Type for converting form data to JSON
type SerializableFormDocument = Omit<FormDocument, "pages"> & {
  pages: SerializableJsonObject[]
}

Type Definition Best Practices

1. Explicit Type Annotations

import { parseForm, isParsedDocument } from "md2form"
import type { FormDocument } from "md2form"
 
const result = parseForm(markdown, { strict: true })
 
if (!isParsedDocument(result)) {
  throw new Error("Parse failed")
}
 
// ✅ Explicit type annotation
const form: FormDocument = result.document

2. Using Type Guards

import type { ShortText } from "md2form"
 
// ❌ Relying on type casting
const shortText = element as ShortText
console.log(shortText.maxLength)
 
// ✅ Using type guards
if (isShortText(element)) {
  console.log(element.maxLength) // Type safe
}

3. Proper Handling of Union Types

import type { FormElement } from "md2form"
 
// ✅ Using discriminated unions
function processElement(element: FormElement) {
  switch (element.type) {
    case "short_text":
      // element is ShortText at this point
      console.log(element.maxLength)
      break
    case "number":
      // element is NumberField at this point
      console.log(element.min, element.max)
      break
    // ... other cases
  }
}

Next Steps

After understanding type definitions, check the following pages for more detailed information:

  • API Reference - Details of functions and classes
  • Examples - Real-world usage examples in TypeScript
  • Basics - Fundamentals of type-safe parsing