2026-03-27 16:20:46 +03:00

3.4 KiB

Schema Reference

File Location

apps/dashboard/modules/<feature>/<feature>.schema.ts

Template

import { z } from "zod"

// Reusable relation field schema — use for all foreign-key / lookup fields
const relationFieldSchema = z
    .object({ value: z.string(), label: z.string() })
    .nullable()

const <feature>FormSchema = z.object({
    // ── Relations (stored as { value, label } objects, mapped to IDs on submit) ──
    // category: relationFieldSchema,

    // ── Required strings ──
    // name: z.string().min(1, "Name is required"),

    // ── Optional strings ──
    // description: z.string().optional(),

    // ── Optional email (allows empty string) ──
    // email: z.union([
    //     z.string().email("Enter a valid email address"),
    //     z.literal(""),
    // ]).optional(),

    // ── Optional phone ──
    // phone: z.string().optional(),

    // ── Boolean ──
    // is_active: z.boolean().default(true),

    // ── Number ──
    // quantity: z.coerce.number().min(0),

    // ── Date ──
    // due_date: z.string().optional(),
})

type <Feature>FormValues = z.infer<typeof <feature>FormSchema>

export { <feature>FormSchema, relationFieldSchema }
export type { <Feature>FormValues }

Field Type Patterns

Required string

name: z.string().min(1, "Name is required"),

Optional string

notes: z.string().optional(),

Optional email (allows empty)

email: z.union([
    z.string().email("Enter a valid email address"),
    z.literal(""),
]).optional(),

Relation / Foreign key

const relationFieldSchema = z
    .object({ value: z.string(), label: z.string() })
    .nullable()

// In schema:
department: relationFieldSchema,

Required relation

department: z
    .object({ value: z.string(), label: z.string() })
    .refine((v) => v !== null, { message: "Department is required" }),

Boolean with default

is_active: z.boolean().default(true),

Number (from string input)

quantity: z.coerce.number().min(0, "Must be non-negative"),
price: z.coerce.number().min(0),

Static enum select

status: z.enum(["active", "inactive", "pending"]).default("active"),
salutation: z.string().optional(),

Real Example: CustomerFormSchema

import { z } from "zod"

const relationFieldSchema = z
    .object({ value: z.string(), label: z.string() })
    .nullable()

type RelationField = z.infer<typeof relationFieldSchema>

const customerFormSchema = z.object({
    customer_type: relationFieldSchema,
    referral_source: relationFieldSchema,
    payment_terms: relationFieldSchema,
    country: relationFieldSchema,
    state: relationFieldSchema,
    salutation: z.string().optional(),
    first_name: z.string().min(1, "First name is required"),
    last_name: z.string().min(1, "Last name is required"),
    company_name: z.string().optional(),
    email: z.union([
        z.string().email("Enter a valid email address"),
        z.literal(""),
    ]).optional(),
    phone: z.string().optional(),
    alternate_phone: z.string().optional(),
    address_line_1: z.string().optional(),
    address_line_2: z.string().optional(),
    city: z.string().optional(),
    zip_code: z.string().optional(),
})

type CustomerFormValues = z.infer<typeof customerFormSchema>

export { customerFormSchema, relationFieldSchema }
export type { CustomerFormValues, RelationField }