Skip to content

Quick Start

This guide will walk you through creating a simple user registration form with validation, error handling, and PowerSync integration.

Schema Definition

Compare ZyraForm schema with Zod, Drizzle, and SwiftData

let UsersSchema = ExtendedTable(
  name: "(AppConfig.dbPrefix)users",
  primaryKey: "id",
  columns: [
      .text("email").email().notNull(),
      .text("name").minLength(2).maxLength(50).notNull(),
      .text("age").int().positive().intMin(18).intMax(120).nullable(),
      .text("website").url().nullable()
  ]
)

Create a schema file (e.g., Schemas/UserSchema.swift):

import Foundation
import PowerSync
import ZyraForm
let UsersSchema = ExtendedTable(
name: "\(AppConfig.dbPrefix)users",
primaryKey: "id",
columns: [
.text("email").email().notNull(),
.text("name").minLength(2).maxLength(50).notNull(),
.text("age").int().positive().intMin(18).intMax(120).nullable(),
.text("website").url().nullable()
]
)

Define your form values type (e.g., FormValues/UserFormValues.swift):

import Foundation
import ZyraForm
struct UserFormValues: FormValues {
var email: String = ""
var name: String = ""
var age: Int?
var website: String?
init() {}
}

Build your SwiftUI form view (e.g., Views/Forms/UserFormView.swift):

import SwiftUI
import ZyraForm
struct UserFormView: View {
@StateObject var form = PowerSyncForm<UserFormValues>(schema: UsersSchema)
var body: some View {
Form {
Section("Account Information") {
// Email field
if let emailBinding = form.binding(for: "email") {
TextField("Email", text: emailBinding)
.textContentType(.emailAddress)
.autocapitalization(.none)
if form.hasError("email") {
Text(form.getError("email") ?? "")
.foregroundColor(.red)
.font(.caption)
}
}
// Name field
if let nameBinding = form.binding(for: "name") {
TextField("Name", text: nameBinding)
if form.hasError("name") {
Text(form.getError("name") ?? "")
.foregroundColor(.red)
.font(.caption)
}
}
// Age field
if let ageBinding = form.intBinding(for: "age") {
TextField("Age", text: ageBinding)
.keyboardType(.numberPad)
if form.hasError("age") {
Text(form.getError("age") ?? "")
.foregroundColor(.red)
.font(.caption)
}
}
// Website field (optional)
if let websiteBinding = form.binding(for: "website") {
TextField("Website (optional)", text: websiteBinding)
.textContentType(.URL)
.autocapitalization(.none)
if form.hasError("website") {
Text(form.getError("website") ?? "")
.foregroundColor(.red)
.font(.caption)
}
}
}
Section {
Button(action: {
form.handleSubmit { values in
Task {
await saveUser(values)
}
}
}) {
Text("Register")
.frame(maxWidth: .infinity)
}
.disabled(!form.isValid || form.isSubmitting)
}
}
.navigationTitle("Registration")
}
private func saveUser(_ values: UserFormValues) async {
let service = GenericPowerSyncService(
tableName: "\(AppConfig.dbPrefix)users",
userId: getCurrentUserId()
)
do {
let config = UsersSchema.toTableFieldConfig()
try await service.createRecord(
fields: [
"email": values.email,
"name": values.name,
"age": values.age ?? NSNull(),
"website": values.website ?? NSNull()
],
encryptedFields: config.encryptedFields
)
print("✅ User created successfully!")
} catch {
print("❌ Failed to create user: \(error)")
}
}
}

Update your PowerSync schema initialization:

let PowerSyncSchema = Schema(
tables: [
UsersSchema.toPowerSyncTable()
]
)

Use the form view in your app:

import SwiftUI
@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
NavigationView {
UserFormView()
}
}
}
}

Automatic Validation: Email, URL, length, and custom validations run automatically
Error Display: Errors appear below fields with red styling
Type Safety: Form values are type-safe and inferred from schema
Reactive Updates: Changes update the form state automatically
Submit Handling: Form validates before submission
PowerSync Integration: Data saves to offline-first database