Types Reference
Every type on this page is importable from the single @saastro/forms entry point — the package has no subpath exports:
import type { FormConfig, FieldConfig, Step, SubmitActionNode } from '@saastro/forms';
The reference is organized by concept. Types that have dedicated guide pages link to them for detailed usage.
Form configuration
FormConfig
The top-level configuration object consumed by <Form config={...} /> and returned by FormBuilder.build().
interface FormConfig {
formId: string;
fields: Record<string, FieldConfig>;
steps: Record<string, Step>; // keyed by step id — a Record, not an array
initialStep?: string;
layout?: FormLayout; // grid settings — columns lives here, not top-level
buttons?: FormButtons;
submitConfirmation?: SubmitConfirmationConfig;
pluginManager?: PluginManager;
successMessage?: string | ((values: Record<string, unknown>) => string);
errorMessage?: string | ((error: Error, values: Record<string, unknown>) => string);
redirect?: string | ((values: Record<string, unknown>) => string);
submitActions?: Record<string, SubmitActionNode>;
submitExecution?: SubmitExecutionConfig;
locale?: string; // active locale to render (applies the matching i18n overlay)
i18n?: FormI18n; // per-locale translation overlays
onStepChange?: (step: string) => void;
onSuccess?: (values: Record<string, unknown>) => void;
onError?: (error: Error, values: Record<string, unknown>) => void;
/** @deprecated Use submitActions instead */
submit?: SubmitConfig;
}
Notes:
- The identifier property is
formId(notid). stepsis aRecord<string, Step>— the record key is the step id.- Column count is configured under
layout(FormLayout), never at the top level.
See: FormBuilder API
FormLayout
Controls the form grid system.
interface FormLayout {
mode: 'auto' | 'manual';
gap?: GapValue; // 0-12 (default: 4)
minFieldWidth?: number; // Pixels, auto mode only (default: 240)
columns?: ColumnsValue; // 1-12
className?: string; // Extra CSS classes for grid container
}
See: Layout System
Breakpoint
type Breakpoint = 'default' | 'sm' | 'md' | 'lg' | 'xl' | '2xl';
SubmitConfirmationConfig
Double-click confirmation for forms with optional fields.
interface SubmitConfirmationConfig {
optionalFields: Array<{ name: string; message: string }>;
buttonBehavior: {
defaultText?: string;
warningText: string;
warningVariant?: ButtonConfig['variant'];
warningEffect?: ButtonConfig['effect'];
resetDelay?: number; // ms, default: 3000
};
applyOn: 'submit' | 'steps' | 'both';
showOnce?: boolean; // default: true
}
See: FormBuilder API
LocaleOverlay & FormI18n
Translation overlays merged over the base config when FormConfig.locale matches a key in i18n.translations. Only the strings you specify are overridden; everything else falls back to the base config.
interface LocaleOverlay {
fields?: Record<
string,
{
label?: string;
placeholder?: string;
helperText?: string;
description?: string;
options?: Array<{ value: string | number; label: string }>; // matched to base options by value
}
>;
buttons?: {
submit?: { label?: string };
next?: { label?: string };
back?: { label?: string };
};
steps?: Record<string, { title?: string; description?: string }>;
messages?: { success?: string; error?: string };
}
interface FormI18n {
defaultLocale?: string; // language of the base config strings
locales?: string[]; // available locales
translations?: Record<string, LocaleOverlay>;
}
The library ships built-in English defaults (button labels Submit/Next →/← Back, the ✅ Thank you! success message, placeholders like Select an option...); use these overlays to translate your forms, or flip the package defaults globally with setDefaultMessages. See: Internationalization
Fields
FieldConfig
The discriminated union of every field configuration: 29 union members covering the 37 field type strings (some interfaces serve several type values).
type FieldConfig =
| TextFieldProps // type: 'text' | 'email' | 'tel' | 'url' | 'password' | 'number'
| TextareaFieldProps // type: 'textarea'
| SliderFieldProps // type: 'slider'
| RangeFieldProps // type: 'range' — renders identically to slider
| RatingFieldProps // type: 'rating' — star rating, integer 1..max
| MatrixFieldProps // type: 'matrix' — Likert grid, value keyed by row
| PhoneFieldProps // type: 'phone' — international phone, value is E.164
| SelectFieldProps // type: 'select'
| ComboboxFieldProps // type: 'combobox'
| NativeSelectFieldProps // type: 'native-select'
| RadioFieldProps // type: 'radio'
| ButtonRadioFieldProps // type: 'button-radio'
| CheckboxFieldProps // type: 'checkbox'
| SwitchFieldProps // type: 'switch'
| CheckboxGroupFieldProps // type: 'checkbox-group'
| SwitchGroupFieldProps // type: 'switch-group'
| ButtonCheckboxFieldProps // type: 'button-checkbox'
| DateFieldProps // type: 'date'
| DateRangeFieldProps // type: 'daterange'
| HtmlFieldProps // type: 'html'
| ButtonCardFieldProps // type: 'button-card'
| OtpFieldProps // type: 'otp'
| CommandFieldProps // type: 'command' — same popover UI as combobox
| InputGroupFieldProps // type: 'input-group'
| CurrencyFieldProps // type: 'currency' — renders identically to input-group
| FileFieldProps // type: 'file'
| RepeaterFieldProps // type: 'repeater' — nested sub-fields
| HiddenFieldProps // type: 'hidden' — renders null; requires a SerializableFieldResolver
| ButtonConfig; // type: 'button' | 'submit' | 'next' | 'back'
See: Hidden Fields for hidden and resolvers.
BaseFieldProps
All field types extend this base:
interface BaseFieldProps
extends
LabelProps,
LayoutProps,
InputProps,
ValidationProps,
StateProps,
TransformProps,
ComputedProps,
AdvancedProps {}
Key properties inherited by all fields:
| Category | Properties |
|---|---|
| Label | label, hideLabel, label_className, description |
| Layout | layout, columns, wrapper_className, className |
| Input | input_className, value, defaultValue, placeholder, autocomplete, required, size, icon, iconProps |
| Validation | schema, customValidators, error_className, helperText, helper_className, tooltip |
| State | hidden, disabled, readOnly |
| Transform | transform |
| Computed | computed — { dependsOn: string[]; compute: (values) => unknown } |
| Advanced | trackingKey |
Two layout props exist in the type but are declared and not yet implemented by the runtime: field-level gridSpan and field-level order. Use layout.columns and layout.order (via FieldLayoutConfig) instead.
FieldLayoutConfig
interface FieldLayoutConfig {
columns?: Partial<Record<Breakpoint, number>>; // 1-12 per breakpoint
order?: number | Partial<Record<Breakpoint, number>>;
}
Option
Used by select, native-select, combobox, command, radio, button-radio, checkbox-group, switch-group, and button-checkbox:
type Option = {
label: string;
value: string;
icon?: ReactNode;
iconProps?: Record<string, unknown>;
};
ButtonCardOption
Used by button-card (note text instead of label):
type ButtonCardOption = {
value: string;
text: string;
description?: string;
icon?: ReactNode;
iconProps?: Record<string, unknown>;
};
Buttons
ButtonConfig
interface ButtonConfig extends BaseFieldProps {
type: 'button' | 'submit' | 'next' | 'back';
action?: () => void;
loading?: boolean;
icon?: ReactNode;
iconPosition?: 'left' | 'right';
variant?: 'default' | 'destructive' | 'outline' | 'secondary' | 'ghost' | 'link';
size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
effect?: string; // declared but not applied by the built-in button renderer
rounded?: string; // declared but not applied by the built-in button renderer
label?: string;
}
effect and rounded are typed as plain strings; the built-in renderer currently ignores them, so any visual effect depends on your own styling (e.g. via className). Default button labels are English (Submit, Next →, ← Back) — override per button via label, per locale via i18n, or globally with setDefaultMessages.
FormButtons
interface FormButtons {
submit?: ButtonConfig;
next?: ButtonConfig;
back?: ButtonConfig;
align?: 'start' | 'center' | 'end' | 'between' | 'responsive';
inline?: boolean;
}
See: Buttons
Steps
Step
type Step = {
id?: string; // Optional — the Record key serves as id
title?: string; // Shown by multi-step UIs; i18n-translatable
description?: string; // i18n-translatable
fields: string[]; // Field names in this step
next?: StepCondition[];
defaultNext?: string; // Fallback if no condition matches
};
StepCondition
type StepCondition = ConditionGroup & {
target: string; // Step id to navigate to
};
See: Multi-Step Forms
Conditional logic
ConditionOperator
type ConditionOperator =
| 'equals'
| 'notEquals'
| 'contains'
| 'notContains'
| 'greaterThan'
| 'lessThan'
| 'greaterThanOrEqual'
| 'lessThanOrEqual'
| 'isTrue'
| 'isFalse'
| 'isEmpty'
| 'isNotEmpty';
Condition
type Condition = {
field: string;
operator: ConditionOperator;
value?: string | number | boolean | null;
};
ConditionGroup
type ConditionGroup = {
conditions: Condition[];
operator: 'AND' | 'OR';
};
See: Conditional Logic, Multi-Step Forms
Submit actions
SubmitActionNode
The complete configuration for a single submit action:
interface SubmitActionNode {
id: string;
action: SubmitAction;
trigger: SubmitTrigger;
condition?: SubmitActionCondition;
order?: number;
continueOnError?: boolean;
disabled?: boolean;
fieldMapping?: FieldMappingConfig;
}
SubmitAction (union)
type SubmitAction =
| HttpSubmitAction // type: 'http'
| WebhookSubmitAction // type: 'webhook'
| EmailSubmitAction // type: 'email'
| CustomSubmitAction // type: 'custom'
| IntegrationSubmitAction; // type: 'integration' — server-side only
IntegrationSubmitAction ({ type: 'integration'; name: string; integrationRef: string; fieldMapping?: Record<string, string> }) is not executed by the in-browser runtime — it declares an integration that the hosted submit backend dispatches server-side after receiving the submission. See: HubForm & hosted submit
SubmitActionCondition
interface SubmitActionCondition {
field: string;
operator: ConditionOperator;
value: unknown;
}
SubmitTrigger
type SubmitTriggerType =
| 'onSubmit'
| 'onStepEnter'
| 'onStepExit'
| 'onFieldChange'
| 'onFieldBlur'
| 'onDelay'
| 'manual';
interface SubmitTrigger {
type: SubmitTriggerType;
stepId?: string;
fieldName?: string;
delayMs?: number;
debounceMs?: number;
}
FieldMapping
// Two formats: simple rename map, or the advanced object
type FieldMappingConfig = Record<string, string> | FieldMapping;
interface FieldMapping {
fields?: Record<string, string | FieldMapEntry>;
inject?: Record<string, unknown>; // static values or { $resolver: ... }
exclude?: string[];
passthrough?: boolean; // default: true
}
interface FieldMapEntry {
to: string;
transform?: BuiltinTransform | FieldTransformFn;
}
type BuiltinTransform =
| 'toString'
| 'toNumber'
| 'toBoolean'
| 'booleanString'
| 'dateISO'
| 'dateYMD'
| 'dateDMY'
| 'dateTimestamp'
| 'trim'
| 'lowercase'
| 'uppercase'
| 'emptyToNull';
FieldResolver
Injects computed/dynamic values into a payload (in FieldMapping.inject) or into hidden fields. Eight variants:
type FieldResolver =
| { $resolver: 'timestamp' }
| { $resolver: 'hostname' }
| { $resolver: 'urlParam'; param: string; fallback?: string }
| { $resolver: 'ip'; endpoint?: string; fallback?: string }
| { $resolver: 'pageUrl' }
| { $resolver: 'referrer' }
| { $resolver: 'userAgent' }
| { $resolver: 'custom'; fn: () => unknown };
// JSON-serializable subset (excludes 'custom') — required by hidden fields
type SerializableFieldResolver = Exclude<FieldResolver, { $resolver: 'custom' }>;
The package also exports BUILTIN_RESOLVERS, a constant listing the 7 serializable resolvers with { id, label, description } metadata. See: Hidden Fields
SubmitExecutionConfig
interface SubmitExecutionConfig {
mode: 'sequential' | 'parallel';
stopOnFirstError?: boolean;
globalTimeout?: number;
}
SubmitActionsResult
interface SubmitActionsResult {
results: SubmitActionResult[];
allSuccessful: boolean;
successCount: number;
failureCount: number;
totalDurationMs: number;
}
interface SubmitActionResult {
actionId: string;
actionName: string;
success: boolean;
data?: unknown;
error?: Error;
durationMs: number;
startedAt: Date;
completedAt: Date;
}
Legacy submit (deprecated)
SubmitConfig (DefaultSubmitConfig | CustomSubmitConfig) backs the deprecated FormConfig.submit property — prefer submitActions. CustomSubmitConfig ({ type: 'custom'; onSubmit: (values) => Promise<void> }) is still relevant: it is the return type of createHubFormSubmit from the hosted submit integration.
See: Submit & Actions
Plugins
FormPlugin
interface FormPlugin {
name: string;
version: string;
description?: string;
options?: Record<string, unknown>;
init?: (options?: Record<string, unknown>) => void;
cleanup?: () => void;
onFormInit?: (config: FormConfig) => void;
onFieldChange?: (fieldName: string, value: unknown, allValues: Record<string, unknown>) => void;
onStepChange?: (stepId: string, values: Record<string, unknown>) => void;
onBeforeSubmit?: (values: Record<string, unknown>) => void | Promise<void>;
onAfterSubmit?: (values: Record<string, unknown>, response: unknown) => void;
onError?: (error: Error, values?: Record<string, unknown>) => void;
transformConfig?: (config: FormConfig) => FormConfig;
transformValues?: (
values: Record<string, unknown>,
) => Record<string, unknown> | Promise<Record<string, unknown>>;
registerFields?: () => Record<
string,
React.ComponentType<{ name: string; fieldConfig: any; control: any; colSpanItem: string }>
>;
validators?: Record<
string,
(value: unknown, context: ValidationContext) => boolean | string | Promise<boolean | string>
>;
}
Note: the renderer type used by registerFields is shown inline because it is not exported as a named type. The name FieldRenderer that is exported from @saastro/forms is the React component that renders a single field — not a type.
ValidationContext
interface ValidationContext {
fieldName: string;
allValues: Record<string, unknown>;
abortSignal?: AbortSignal;
}
definePlugin()
function definePlugin(plugin: FormPlugin): FormPlugin;
See: Plugins
Validation
ValidationRules
Serializable validation rules compiled to Zod at runtime:
type ValidationRules = {
required?: boolean;
requiredMessage?: string;
minLength?: number;
minLengthMessage?: string;
maxLength?: number;
maxLengthMessage?: string;
pattern?: string;
patternMessage?: string;
format?: 'email' | 'url' | 'uuid' | 'cuid' | 'emoji';
formatMessage?: string;
min?: number;
minMessage?: string;
max?: number;
maxMessage?: string;
integer?: boolean;
integerMessage?: string;
positive?: boolean;
positiveMessage?: string;
minItems?: number;
minItemsMessage?: string;
maxItems?: number;
maxItemsMessage?: string;
mustBeTrue?: boolean;
mustBeTrueMessage?: string;
minDate?: string;
minDateMessage?: string;
maxDate?: string;
maxDateMessage?: string;
mustBeFuture?: boolean;
mustBeFutureMessage?: string;
mustBePast?: boolean;
mustBePastMessage?: string;
preset?: string;
};
SchemaType
type SchemaType = z.ZodType | ValidationRules;
See: Validation
Components
ComponentRegistry
The full 49-slot interface for UI component injection. See Component System for the complete table.
interface ComponentRegistry {
// Inputs (4): Input, Textarea, Button, Label
// Checkbox & Switch (2): Checkbox, Switch
// Radio (2): RadioGroup, RadioGroupItem
// Select (5): Select, SelectTrigger, SelectContent, SelectItem, SelectValue
// Native Select (1): NativeSelect
// Slider (1): Slider
// Popover (3): Popover, PopoverTrigger, PopoverContent
// Tooltip (4): Tooltip, TooltipTrigger, TooltipContent, TooltipProvider
// Separator (1): Separator
// Dialog (6): Dialog, DialogTrigger, DialogContent, DialogHeader, DialogTitle, DialogDescription
// Command (6): Command, CommandInput, CommandList, CommandEmpty, CommandGroup, CommandItem
// Input OTP (3): InputOTP, InputOTPGroup, InputOTPSlot
// Accordion (4): Accordion, AccordionItem, AccordionTrigger, AccordionContent
// Calendar (1): Calendar
// Form (2): FormField, FormControl
// Field (4): Field, FieldLabel, FieldDescription, FieldError
}
Helper Types
type PartialComponentRegistry = Partial<ComponentRegistry>;
type ComponentRegistryInput = { [K in keyof ComponentRegistry]: React.ComponentType<any> };
type ComponentOverrides = Partial<{ [K in keyof ComponentRegistry]: React.ComponentType<any> }>;
See: Component System