# Use SupaHost with iOS and Swift

> Connect a SwiftUI app to a SupaHost project with supabase-swift. Add the package, keep keys in an xcconfig, query into Codable models, watch auth state and sign in with Apple or an email code.

Source: https://supahost.app/docs/getting-started/ios-swift · Updated 2026-11-11

`supabase-swift` is the official Swift client. It uses async/await, decodes rows into your `Codable` types and stores the session in the Keychain. Point it at your SupaHost project URL with the `anon` key and it works as it does with any Supabase project.

The examples use the `recipes` table from the [quickstart](/docs/getting-started/quickstart).

## Add the package

In Xcode, choose **File → Add Package Dependencies**, enter `https://github.com/supabase/supabase-swift`, pick version 2.x and add the `Supabase` product to your app target. In a `Package.swift`:

```swift
dependencies: [
  .package(url: "https://github.com/supabase/supabase-swift", from: "2.0.0"),
],
targets: [
  .target(name: "RecipeBox", dependencies: [
    .product(name: "Supabase", package: "supabase-swift"),
  ]),
]
```

## Keep your keys out of source

Put the Project URL and `anon` key from the **Connect** tab (subtab **App**) in an `.xcconfig` file that you don't commit, and reference it from Info.plist:

```text
// Secrets.xcconfig ("//" starts a comment, hence the $() in the URL)
SUPABASE_URL = https:/$()/app-one.acme.supahost.space
SUPABASE_ANON_KEY = eyJhbGciOiJIUzI1NiIs...
```

In Info.plist add two String entries, `SUPABASE_URL` = `$(SUPABASE_URL)` and `SUPABASE_ANON_KEY` = `$(SUPABASE_ANON_KEY)`. The `anon` key is safe in an app binary. The `service_role` key must never be in one.

## Create the client

```swift
// Supabase.swift
import Foundation
import Supabase

let supabase: SupabaseClient = {
  let info = Bundle.main.infoDictionary!
  return SupabaseClient(
    supabaseURL: URL(string: info["SUPABASE_URL"] as! String)!,
    supabaseKey: info["SUPABASE_ANON_KEY"] as! String
  )
}()
```

## Query a table

Describe a row with a `Decodable` struct and let the client decode the response:

```swift
struct Recipe: Decodable, Identifiable {
  let id: Int
  let title: String
  let minutes: Int?
}

struct RecipeListView: View {
  @State private var recipes: [Recipe] = []
  @State private var errorText: String?

  var body: some View {
    List(recipes) { recipe in
      Text("\(recipe.title) (\(recipe.minutes ?? 0) min)")
    }
    .overlay { if let errorText { Text(errorText) } }
    .task { await load() }
  }

  func load() async {
    do {
      recipes = try await supabase
        .from("recipes")
        .select("id, title, minutes")
        .order("created_at", ascending: false)
        .execute()
        .value
    } catch {
      errorText = error.localizedDescription
    }
  }
}
```

Inserting uses an `Encodable` value:

```swift
struct NewRecipe: Encodable {
  let title: String
  let minutes: Int?
}

try await supabase
  .from("recipes")
  .insert(NewRecipe(title: "Lemon rice", minutes: 30))
  .execute()
```

The insert only succeeds for a signed-in user, because the quickstart's insert policy is for `authenticated`. Add `import SwiftUI` to files that define views.

## Watch auth state

`authStateChanges` is an async sequence of events. Use it to switch between your signed-in and signed-out screens:

```swift
@MainActor
final class AuthModel: ObservableObject {
  @Published var session: Session?

  func observe() async {
    for await (_, session) in supabase.auth.authStateChanges {
      self.session = session
    }
  }
}
```

Start it with `.task { await auth.observe() }` on your root view.

## Sign in

Email and password:

```swift
try await supabase.auth.signUp(email: email, password: password)
try await supabase.auth.signIn(email: email, password: password)
try await supabase.auth.signOut()
```

An emailed one-time code works well on iOS and needs no redirect at all. (For links instead, add your app's URL scheme, such as `recipebox://login`, to **Redirect URLs** on the Auth tab.)

```swift
try await supabase.auth.signInWithOTP(email: email)
// The user reads the code from the email and types it in:
try await supabase.auth.verifyOTP(email: email, token: code, type: .email)
```

## Sign in with Apple

Use `AuthenticationServices` to get an identity token, then hand it to Supabase. No redirect is involved:

```swift
import AuthenticationServices

SignInWithAppleButton { request in
  request.requestedScopes = [.email, .fullName]
} onCompletion: { result in
  Task {
    guard
      case .success(let auth) = result,
      let credential = auth.credential as? ASAuthorizationAppleIDCredential,
      let tokenData = credential.identityToken,
      let idToken = String(data: tokenData, encoding: .utf8)
    else { return }

    try await supabase.auth.signInWithIdToken(
      credentials: .init(provider: .apple, idToken: idToken)
    )
  }
}
```

Turn on Apple on the project's **Auth** tab first. See [Sign in with Apple](/docs/auth/apple) for the Apple Developer setup.

For other OAuth providers through a web view, the return address must be an `https://` Universal Link your app handles, listed under **Redirect URLs** on the Auth tab.

## Next steps

- [Magic links and email OTP](/docs/auth/magic-links-otp)
- [Uploading files](/docs/storage/uploads)
- [Realtime on SupaHost](/docs/realtime/overview)
- [Can I use SupaHost for a mobile app?](/kb/mobile-app-database)
