Skip to content

Validation

ZyraForm supports Zod-like validation rules that are automatically applied based on your schema definition.

.text("email").email().notNull()
// Error: "email must be a valid email"
.text("website").url().nullable()
// Error: "website must be a valid URL"
.text("api_endpoint").httpUrl()
// Error: "api_endpoint must be a valid HTTP/HTTPS URL"
.text("user_id").uuid()
// Error: "user_id must be a valid UUID"
.text("username").minLength(3).maxLength(20)
// Error: "username must be at least 3 characters"
// Error: "username must be at most 20 characters"
.text("code").exactLength(6)
// Error: "code must be exactly 6 characters"
.text("domain").startsWith("https://")
.text("slug").endsWith(".com")
.text("description").includes("important")
.text("username").lowercase()
.text("api_key").uppercase()
.text("phone").regex("^\\+?[1-9]\\d{1,14}$", error: "Invalid phone number")
.text("username").regex("^[a-zA-Z0-9_]+$", error: "Username must contain only letters, numbers, and underscores")
.integer("age").positive().intMin(18).intMax(120)
// Error: "age must be positive"
// Error: "age must be at least 18"
// Error: "age must be at most 120"
.integer("page").even()
.integer("row").odd()
.integer("balance").negative()
.real("price").double().positive().minimum(0.01).maximum(9999.99)
// Error: "price must be positive"
// Error: "price must be at least 0.01"
// Error: "price must be at most 9999.99"

Create custom validation rules with error messages:

.text("password")
.minLength(8)
.custom("Password must contain uppercase, lowercase, and number") { value in
guard let password = value as? String else { return false }
let hasUppercase = password.range(of: "[A-Z]", options: .regularExpression) != nil
let hasLowercase = password.range(of: "[a-z]", options: .regularExpression) != nil
let hasNumber = password.range(of: "\\d", options: .regularExpression) != nil
return hasUppercase && hasLowercase && hasNumber
}
.real("price").double()
.positive()
.custom("Price must be at least 0.01") { value in
if let price = value as? Double {
return price >= 0.01
}
return false
}

Chain multiple validations:

.text("email")
.email()
.notNull()
.minLength(5)
.maxLength(255)
.integer("age")
.positive()
.intMin(13)
.intMax(120)
.custom("Age must be a valid integer") { value in
value is Int
}
let RegistrationSchema = ExtendedTable(
name: "\(AppConfig.dbPrefix)users",
columns: [
.text("email")
.email()
.notNull(),
.text("username")
.minLength(3)
.maxLength(20)
.regex("^[a-zA-Z0-9_]+$", error: "Username must contain only letters, numbers, and underscores")
.notNull(),
.text("password")
.minLength(8)
.custom("Password must contain uppercase, lowercase, and number") { value in
guard let password = value as? String else { return false }
let hasUppercase = password.range(of: "[A-Z]", options: .regularExpression) != nil
let hasLowercase = password.range(of: "[a-z]", options: .regularExpression) != nil
let hasNumber = password.range(of: "\\d", options: .regularExpression) != nil
return hasUppercase && hasLowercase && hasNumber
}
.notNull(),
.text("age")
.int()
.positive()
.intMin(18)
.intMax(120)
.nullable(),
.text("website")
.url()
.nullable()
]
)
let ProductsSchema = ExtendedTable(
name: "\(AppConfig.dbPrefix)products",
columns: [
.text("name")
.minLength(1)
.maxLength(100)
.notNull(),
.text("sku")
.regex("^[A-Z]{3}-\\d{4}$", error: "SKU must be in format ABC-1234")
.notNull(),
.real("price")
.double()
.positive()
.minimum(0.01)
.maximum(9999.99)
.custom("Price must be at least 0.01") { value in
if let price = value as? Double {
return price >= 0.01
}
return false
}
.notNull(),
.text("category")
.custom("Category must be one of: electronics, clothing, books") { value in
guard let category = value as? String else { return false }
return ["electronics", "clothing", "books"].contains(category)
}
.notNull()
]
)

ZyraForm provides default error messages for all validation rules:

  • Required: "{field} is required"
  • Email: "{field} must be a valid email"
  • URL: "{field} must be a valid URL"
  • Min length: "{field} must be at least {min} characters"
  • Max length: "{field} must be at most {max} characters"
  • Positive: "{field} must be positive"
  • Range: "{field} must be at least {min}" or "{field} must be at most {max}"

Provide custom error messages:

.text("password")
.custom("Password must be at least 8 characters with uppercase, lowercase, and number") { value in
// validation logic
}
.text("sku")
.regex("^[A-Z]{3}-\\d{4}$", error: "SKU must be in format ABC-1234")
if form.hasError("email") {
// Show error
}
let errorCount = form.errors.errorCount
let allErrors = form.errors.allErrors
let emailError = form.getError("email")
// Returns: Optional<String>
if let error = form.errors["email"] {
print(error)
}
ForEach(form.errors.fields, id: \.self) { field in
Text(form.errors[field] ?? "")
.foregroundColor(.red)
.font(.caption)
}

Control when validation occurs:

// Validate on every change
.mode(.onChange)
// Validate on blur
.mode(.onBlur)
// Validate on submit only
.mode(.onSubmit)
// Validate once touched
.mode(.onTouched)