| name | zod-schemas |
| description | Zod validation patterns for DropFlow. Use when creating API input schemas, DTOs, env parsers, form schemas, or worker job payloads in packages/types/. |
Zod Schemas — DropFlow
Package: zod
Location: packages/types/src/
Schema Organization
packages/types/src/
├── order.ts # Order schemas + types
├── product.ts # Product schemas + types
├── invoice.ts # Invoice schemas + types
├── shipment.ts # Shipment schemas + types
├── workflow.ts # Workflow schemas + types
├── api.ts # Generic API response schemas
└── index.ts # Re-exports everything
Pattern: Input Schema + DTO
import { z } from "zod";
export const CreateOrderInput = z.object({
buyerName: z.string().min(1),
buyerEmail: z.string().email(),
buyerPhone: z.string().regex(/^\+91\d{10}$/, "Must be +91 followed by 10 digits"),
shippingAddress: AddressSchema,
billingAddress: AddressSchema,
items: z.array(OrderItemInput).min(1),
currency: z.enum(["INR", "USD", "EUR", "GBP"]).default("INR"),
notes: z.string().optional(),
});
export type CreateOrderInput = z.infer<typeof CreateOrderInput>;
export const OrderDTO = z.object({
id: z.string(),
orderNumber: z.string(),
status: z.nativeEnum(OrderStatus),
totalPaise: z.number().int(),
totalFormatted: z.string(),
createdAt: z.string().datetime(),
});
export type OrderDTO = z.infer<typeof OrderDTO>;
Indian Conventions in Schemas
const IndianPhone = z.string().regex(/^\+91\d{10}$/);
const MoneyPaise = z.number().int().nonnegative();
const GSTIN = z.string().regex(/^[0-9]{2}[A-Z]{5}[0-9]{4}[A-Z]{1}[1-9A-Z]{1}Z[0-9A-Z]{1}$/);
const AddressSchema = z.object({
line1: z.string().min(1),
line2: z.string().optional(),
city: z.string().min(1),
state: z.string().min(1),
pin: z.string().regex(/^\d{6}$/, "Must be 6-digit PIN code"),
country: z.string().default("IN"),
});
const MoneyResponse = z.({
: z.().(),
: z.(),
});
Environment Parsing (lib/env.ts)
import { z } from "zod";
const envSchema = z.object({
DATABASE_URL: z.string().url(),
CLERK_SECRET_KEY: z.string().startsWith("sk_"),
FLY_WORKER_URL: z.string().url(),
WORKER_SECRET: z.string().min(32),
RAZORPAY_KEY_SECRET: z.string(),
NODE_ENV: z.enum(["development", "production", "test"]).default("development"),
});
export const env = envSchema.parse(process.env);
API Response Schema
export const ApiResponse = <T extends z.ZodType>(dataSchema: T) =>
z.object({
success: z.boolean(),
data: dataSchema.optional(),
error: z.object({
code: z.string(),
message: z.string(),
}).optional(),
});
export const PaginatedResponse = <T extends z.ZodType>(itemSchema: T) =>
z.object({
items: z.array(itemSchema),
total: z.number().int(),
page: z.number().int().positive(),
pageSize: z.number().int().positive(),
hasMore: z.boolean(),
});
Worker Job Payload Schemas
export const OrderJobPayload = z.object({
orderId: z.string(),
tenantId: z.string(),
action: z.enum(["FULFILL", "CANCEL", "REFUND"]),
});
export const InvoiceJobPayload = z.object({
orderId: z.string(),
tenantId: z.string(),
});
Conventions
- Schema name matches type name:
CreateOrderInput (both schema and type)
- All inputs validated at boundary (API route, server action, worker job entry)
- Never trust data past the boundary — always parse with Zod first
- Export both schema and inferred type from
packages/types
- Use
z.nativeEnum() for Prisma enums