Skip to content

Instantly share code, notes, and snippets.

@saulin18
Created May 5, 2026 15:11
Show Gist options
  • Select an option

  • Save saulin18/7ee86a49d0a83db0479395c1492f9bc6 to your computer and use it in GitHub Desktop.

Select an option

Save saulin18/7ee86a49d0a83db0479395c1492f9bc6 to your computer and use it in GitHub Desktop.
form-schema-builder with Zod
import * as z from "zod";
export type FormFieldType = "text" | "textarea" | "email" | "tel" | "number" | "select" | "multiselect" | "array" | "date";
export interface FormField {
name: string;
label: string;
type: FormFieldType;
required?: boolean;
placeholder?: string;
options?: string[];
validation?: {
min?: number;
max?: number;
pattern?: string;
message?: string;
};
items?: FormField;
}
export interface FormSchema {
fields: FormField[];
submitButtonText?: string;
title?: string;
}
export function generateZodSchema(fields: FormField[]): z.ZodObject<Record<string, z.ZodTypeAny>> {
const schema: Record<string, z.ZodTypeAny> = {};
fields.forEach((field) => {
let fieldSchema = getBaseSchema(field.type, field.options);
if (field.validation) {
fieldSchema = applyValidations(fieldSchema, field.validation, field.type);
}
if (!field.required) {
fieldSchema = fieldSchema.optional();
}
schema[field.name] = fieldSchema;
});
return z.object(schema);
}
function getBaseSchema(type: FormField["type"], options?: string[]): z.ZodTypeAny {
switch (type) {
case "text":
case "textarea":
case "email":
case "tel":
case "date":
return z.string();
case "number":
return z.coerce.number();
case "select":
return options && options.length > 0
? z.enum(options as [string, ...string[]])
: z.string();
case "multiselect":
case "array":
return z.array(z.string());
default:
return z.string();
}
}
function applyValidations(
schema: z.ZodTypeAny,
validation: NonNullable<FormField["validation"]>,
fieldType: FormField["type"]
): z.ZodTypeAny {
let result = schema;
const msg = validation.message;
if (validation.min !== undefined) {
if (fieldType === "number") {
result = (result as z.ZodNumber).min(validation.min, {
message: msg || `El valor mínimo es ${validation.min}`,
});
} else {
result = (result as z.ZodString).min(validation.min, {
message: msg || `Mínimo ${validation.min} caracteres`,
});
}
}
if (validation.max !== undefined) {
if (fieldType === "number") {
result = (result as z.ZodNumber).max(validation.max, {
message: msg || `El valor máximo es ${validation.max}`,
});
} else {
result = (result as z.ZodString).max(validation.max, {
message: msg || `Máximo ${validation.max} caracteres`,
});
}
}
if (validation.pattern) {
result = (result as z.ZodString).regex(
new RegExp(validation.pattern),
validation.message || "Invalid format"
);
}
return result;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment