A Swift SDK for InstantDB - build real-time, offline-first applications for Apple platforms.
- Real-time sync - Instant data synchronization across all clients
- Offline-first - Works offline with automatic sync when reconnected
- Optimistic updates - Instant UI feedback with automatic rollback on failure
- Presence system - Real-time collaboration with room-based presence
- Type-safe - Swift macros for compile-time safety
- Multi-platform - iOS, macOS, tvOS, and watchOS support
.package(url: "https://github.com/instantdb/instant-ios-sdk", from: "0.2.0")let db = InstantClient(appID: "YOUR_APP_ID")The SDK historically used a lot of print(...) statements while we were building out
the WebSocket transport, schema handling, and client-side joins. That was helpful during
development, but it quickly becomes noisy in real applications and in swift test output.
The SDK now keeps stdout quiet by default and makes verbose logging opt-in.
Logging is controlled via environment variables:
INSTANTDB_LOG_LEVEL:off,error,info,debug(default:error)INSTANTDB_DEBUG=1: forcesdebug
Enable high-signal connection logs:
INSTANTDB_LOG_LEVEL=info swift test --package-path instant-ios-sdkEnable verbose protocol / query debugging:
INSTANTDB_LOG_LEVEL=debug swift test --package-path instant-ios-sdkIn Xcode:
- Edit Scheme β Run β Arguments
- Add an environment variable
INSTANTDB_LOG_LEVEL=debug(orINSTANTDB_DEBUG=1)
Define your models with the @InstantEntity macro:
@InstantEntity("goals")
struct Goal {
let id: String
var title: String
var difficulty: Int?
}This generates create, update, delete, link, unlink transact methods on the type.
Real-time subscriptions with AsyncStream:
for await result in db.query(Goal.self).values() {
self.goals = result.data
}
for await result in db.query(Goal.self)
.where { $0.difficulty > 5 }
.limit(10)
.values() {
self.goals = result.data
}Note: Comparison operators require the field to be indexed in InstantDB.
Callback-based:
var subscriptions = Set<SubscriptionToken>()
try db.subscribe(db.query(Goal.self)) { result in
self.goals = result.data
}
.store(in: &subscriptions)Using generated methods inside transact result builder (requires @InstantEntity macro):
try db.transact {
Goal.create(title: "Ship v1", difficulty: 8)
Goal.update(id: goalId, title: "Ship v2")
Goal.delete(id: oldId)
}Using the transaction builder:
try db.transact(db.tx.goals[newId()].update(["title": "Ship v1"]))
try db.transact([
db.tx.goals[id1].update(["title": "First"]),
db.tx.goals[id2].delete()
])try await db.authManager.sendMagicCode(email: "user@example.com")
try await db.authManager.signInWithMagicCode(email: email, code: code)
try await db.authManager.signInWithIdToken(clientName: "apple", idToken: token)
try await db.authManager.signInAsGuest()
try await db.authManager.signOut()Define your schema in Swift:
import InstantDB
let schema = InstantSchema {
Entity("users")
.field("email", .string, .unique, .indexed)
.field("name", .string)
.optionalField("bio", .string)
Entity("posts")
.field("title", .string, .indexed)
.field("content", .string)
.field("createdAt", .date)
Link("users", "posts")
.hasMany()
.to("posts", "author")
}Generate JSON and push to InstantDB:
# Generate instant.schema.json from Swift
instant-schema generate
# Preview changes
instant-schema plan --app-id <id> --token <admin token>
# Push schema
instant-schema push --app-id <id> --token <admin token>
Build collaborative features with room-based presence:
let leaveRoom = db.presence.subscribePresence(
roomId: "canvas-123",
initialPresence: ["x": 100, "y": 200, "color": "blue"]
) { presenceSlice in
for (peerId, peerData) in presenceSlice {
print("Peer \(peerId): \(peerData)")
}
}
db.presence.publishPresence(roomId: "canvas-123", data: ["x": 150, "y": 250])
leaveRoom()struct CursorPresence: Codable, Sendable {
var x: Double
var y: Double
var color: String
}
let cleanup = db.presence.subscribeTypedPresence(
roomId: "canvas-123",
type: CursorPresence.self,
initialPresence: CursorPresence(x: 100, y: 200, color: "blue")
) { peers in
for (peerId, cursor) in peers {
drawCursor(at: cursor.x, cursor.y, color: cursor.color)
}
}let unsub = db.presence.subscribeTopic(roomId: "chat-room", topic: "reactions") { payload in
if let emoji = payload["emoji"] as? String {
showReaction(emoji)
}
}
db.presence.publishTopic(roomId: "chat-room", topic: "reactions", data: ["emoji": "π"])let fileId = try await db.storage.uploadFile(
path: "photos/avatar.jpg",
data: imageData,
options: .init(contentType: "image/jpeg")
)
let fileId = try await db.storage.uploadFile(
path: "documents/report.pdf",
fileURL: localFileURL
)
let downloadURL = try await db.storage.downloadURL(path: "photos/avatar.jpg")
let deletedId = try await db.storage.deleteFile(path: "photos/avatar.jpg")The SDK works offline by default:
- Optimistic updates - Mutations apply immediately to UI
- Offline queue - Transactions queue while offline
- Auto-sync - Queued mutations sync when reconnected
- Query caching - Query results persist across sessions
try db.transact {
Todo.create(title: "Buy groceries")
}Monitor connection state using Combine:
import Combine
var cancellables = Set<AnyCancellable>()
db.$connectionState
.sink { state in
switch state {
case .disconnected:
print("Offline - changes will sync when reconnected")
case .connecting:
print("Connecting...")
case .connected:
print("Connected, authenticating...")
case .authenticated:
print("Online and ready")
}
}
.store(in: &cancellables)- No permission rule management from SDK (use dashboard or TypeScript SDK)
- watchOS/tvOS: No Google Sign-In (Apple Sign-In and Magic Code work)
If a linked entity shows up in the optimistic UI but later flips to nil after a server
refresh, the most common cause is a broken link attribute in the server schema:
- The attribute exists but has
value-type: blobinstead ofref, or - The attribute is a
refbut is missingreverse-identitymetadata.
The Swift SDK assembles nested query results client-side. For ref attributes, it relies
on reverse-identity to understand which namespace and label represent the other side of
the relationship. Without that metadata, the SDK cannot perform the join reliably.
Push a correct schema definition (TypeScript or Swift schema DSL) so the server stores the
link as a true ref with forward + reverse identities.
The SDK also includes a best-effort, lazy repair mechanism when you perform link operations:
it may piggyback an add-attr update in the same transaction to repair a broken ref attribute,
and then it will apply refreshed schema data from refresh-ok before recomputing query results.
- Cursor-based pagination (
first,last,after,before) - Advanced where operators (
$in,$like,$isNull,and/or) - Ordering/sorting by indexed fields
- Field projection (select specific attributes)
- Nested queries on linked entities
-
queryOnce()for one-time reads
- Schema definition DSL in Swift
- CLI tool to deploy schema via Platform API
- Type generation from schema
- Query/transaction validation against schema
- Presence system (
joinRoom,publishPresence,subscribePresence) - Pub/Sub topics (
publishTopic,subscribeTopic) - Room management
- Connection status monitoring
- File upload URL generation
- File delete
- Download URL generation
- Local triple store (GRDB/SQLite)
- Optimistic updates
- Offline mode with sync
- Conflict resolution (server-wins currently)
- SwiftUI property wrappers (
@Query,@Presence) - GitHub Actions CI/CD
- DocC documentation
- 80%+ test coverage