Schema Types
TypeScript types for FieldCraft schemas.
All types are exported from @squaredr/fieldcraft-core. Import them for type-safe schema definitions.
import type {
FormEngineSchema,
Section,
Question,
QuestionType,
Option,
// ... see below for full list
} from "@squaredr/fieldcraft-core";
FormEngineSchema
type FormEngineSchema = {
id: string;
version: string;
title: string;
description?: string;
branding?: BrandingConfig;
settings?: FormSettings;
sections: Section[];
submitAction: SubmitAction;
onComplete?: CompleteAction;
};
Section
type Section = {
id: string;
title: string;
description?: string;
showIf?: ConditionExpression;
questions: Question[];
onExit?: SectionExitAction;
};
Question
type Question = {
id: string;
type: QuestionType;
label: string;
helpText?: string;
placeholder?: string;
required?: boolean | ConditionExpression;
showIf?: ConditionExpression;
disabled?: boolean | ConditionExpression;
validation?: ValidationRule[];
prefillKey?: string;
config?: QuestionConfig;
options?: Option[];
layout?: QuestionLayout;
};
QuestionType
type QuestionType =
// Text
| "short_text" | "long_text" | "email" | "phone"
| "phone_international" | "url" | "legal_name"
// Numeric
| "number" | "slider" | "rating" | "nps" | "likert" | "opinion_scale"
// Selection
| "single_select" | "multi_select" | "dropdown"
| "boolean" | "country_select" | "ranking"
// Date & Time
| "date" | "date_range" | "time" | "appointment"
// Media
| "file_upload" | "signature" | "image_capture"
// Advanced
| "address" | "payment" | "matrix" | "repeater"
| "calculated" | "hidden" | "scoring"
// Structural
| "section_header" | "consent" | "info_block" | "page_break"
// Extensible
| (string & {});
Option
type Option = {
label: string;
value: string | number | boolean;
helpText?: string;
icon?: string;
exclusive?: boolean; // Deselects other options when chosen
};
QuestionLayout
type QuestionLayout = {
width?: "full" | "half" | "third";
columns?: number;
};
ConditionExpression & ConditionOperator
type ConditionExpression = {
combine?: "AND" | "OR";
conditions?: ConditionExpression[];
field?: string;
operator?: ConditionOperator;
value?: unknown;
};
type ConditionOperator =
| "eq" | "neq" | "gt" | "gte" | "lt" | "lte"
| "in" | "notIn" | "exists" | "notExists"
| "contains" | "notContains" | "startsWith" | "endsWith"
| "between" | "matches";
ValidationRule
type ValidationRule =
| { type: "required"; message?: string }
| { type: "min"; value: number; message?: string }
| { type: "max"; value: number; message?: string }
| { type: "minLength"; value: number; message?: string }
| { type: "maxLength"; value: number; message?: string }
| { type: "pattern"; regex: string; flags?: string; message?: string }
| { type: "email"; message?: string }
| { type: "phone"; message?: string }
| { type: "url"; message?: string }
| { type: "date"; min?: string; max?: string; message?: string }
| { type: "fileSize"; maxMb: number; message?: string }
| { type: "fileType"; accept: string[]; message?: string }
| { type: "custom"; name: string; params?: Record<string, unknown>; message?: string }
| { type: "async"; endpoint: string; debounceMs?: number; message?: string };
CustomValidator & AsyncValidator
type CustomValidator = (
value: unknown,
values: Record<string, unknown>,
params?: Record<string, unknown>,
) => string | null;
type AsyncValidator = (
value: unknown,
params?: Record<string, unknown>,
) => Promise<string | null>;
SubmitAction
type SubmitAction = {
type: "http" | "callback" | "adapter";
url?: string;
method?: "POST" | "PUT" | "PATCH";
headers?: Record<string, string>;
};
CompleteAction
type CompleteAction = {
type: "redirect" | "message" | "callback";
url?: string;
message?: string;
showSummary?: boolean;
};
SectionExitAction & JumpRule
type SectionExitAction = {
rules: JumpRule[];
default?: string;
};
type JumpRule = {
condition: ConditionExpression;
jumpTo: string;
};
FormSettings
type FormSettings = {
displayMode?: "classic" | "stepped" | "conversational";
allowDraftSave?: boolean;
draftStorage?: "local" | "server" | "both";
draftTtlHours?: number;
showProgress?: boolean;
progressStyle?: "bar" | "steps" | "percentage";
prefill?: PrefillConfig;
noPiiInLogs?: boolean;
locale?: string;
serverUrl?: string;
submitButton?: {
label?: string;
loadingLabel?: string;
successLabel?: string;
};
navigation?: {
showBack?: boolean;
showSectionList?: boolean;
nextLabel?: string;
backLabel?: string;
allowSkip?: boolean;
};
};
PrefillConfig
type PrefillConfig = {
source: "props" | "url" | "both";
paramPrefix?: string;
transform?: (raw: Record<string, string>) => Record<string, unknown>;
};
BrandingConfig
type BrandingConfig = {
logoUrl?: string;
logoAlt?: string;
faviconUrl?: string;
poweredBy?: boolean;
};
FormResponse
type FormResponse = {
schemaId: string;
schemaVersion: string;
submittedAt: string; // ISO 8601
sessionToken: string;
values: Record<string, unknown>;
scores?: Record<string, number>;
totalScore?: number;
metadata?: Record<string, unknown>;
completionTimeMs?: number;
};
Adapter Interfaces
interface SubmitAdapter {
name: string;
submit(response: FormResponse): Promise<void>;
onError?: (error: Error) => void;
}
interface DraftAdapter {
save(draft: DraftData): Promise<void>;
load(schemaId: string, sessionToken: string): Promise<DraftData | null>;
delete(schemaId: string, sessionToken: string): Promise<void>;
}
type DraftData = {
schemaId: string;
sessionToken: string;
partialData: Record<string, unknown>;
currentSectionId?: string;
visitedSectionIds?: string[];
savedAt: string;
expiresAt: string;
};
interface AnalyticsAdapter {
trackView(schemaId: string): void;
trackStart(schemaId: string, sectionId: string): void;
trackFieldInteraction(fieldId: string, type: "focus" | "blur" | "change"): void;
trackSectionComplete(sectionId: string, timeMs: number): void;
trackSubmit(schemaId: string, totalTimeMs: number): void;
trackAbandon(schemaId: string, lastSectionId: string): void;
}