LimitedRound 2 is open. Get your first month free, no extra charge.Join the waitlist ›
Wire in your backend

How to use CloudKit in an iOS app

TL;DR. CloudKit stores and syncs app data through the user iCloud account with no server to run and no API keys to manage. You enable the iCloud capability, then save and fetch CKRecord values in a private or public database. Because auth is the Apple ID, there is no secret to ship. AppFlight enables the capability and generates the data layer.

What CloudKit does for an iOS app

CloudKit gives you free, per-user cloud storage that syncs across a person's devices automatically. The private database is scoped to one Apple ID and counts against that user's iCloud quota, so your storage costs stay low. The public database is shared by everyone using the app. Because there are no keys and no separate login, CloudKit is a strong fit for apps that keep personal data, like notes, journals, or trackers, where you want sync without standing up a backend or asking users to create another account.

Adding CloudKit with AppFlight

CloudKit needs the iCloud capability and a CloudKit container turned on for the app. In AppFlight you enable iCloud for the project, and because CloudKit uses the Apple ID for auth, there is no key to classify as client or backend and nothing secret to ship. You can then ask the AI to define a record type, save a record, or run a query, and it generates the CloudKit code. If you want local persistence plus sync, you can use SwiftData with CloudKit instead and skip the CKRecord plumbing.

A SwiftUI example

Save and fetch records in the user's private database with async CloudKit:

import CloudKit

let database = CKContainer.default().privateCloudDatabase

func saveNote(_ text: String) async throws {
    let record = CKRecord(recordType: "Note")
    record["text"] = text as CKRecordValue
    record["createdAt"] = Date() as CKRecordValue
    try await database.save(record)
}

func fetchNotes() async throws -> [String] {
    let query = CKQuery(
        recordType: "Note",
        predicate: NSPredicate(value: true)
    )
    query.sortDescriptors = [
        NSSortDescriptor(key: "createdAt", ascending: false)
    ]
    let (results, _) = try await database.records(matching: query)
    return results.compactMap { _, result in
        try? result.get()["text"] as? String
    }
}

Call these from a task in your view and store the results in @State. Saved records appear on the same user's other devices once iCloud syncs.

Alternatives

SwiftData and Core Data handle local persistence and can mirror to CloudKit, so reach for those when you want offline storage plus sync without hand-writing records. Firestore and Supabase are the better choice when data is shared across accounts, needs server-side queries, or must reach users who are not signed in to iCloud. CloudKit wins for private, per-user data where zero backend and zero keys are the goal.

FAQ

Does CloudKit need an API key or backend?

No. CloudKit uses the user signed-in Apple ID for identity and runs on Apple servers, so there is no API key to embed and no backend to host. You enable the iCloud capability and a CloudKit container, and Apple handles storage and sync.

What is the difference between the private and public database?

The private database holds data scoped to one user iCloud account and counts against their iCloud storage. The public database is shared across all users of the app and counts against your app quota. Most apps use the private database for personal data.

Does CloudKit work with SwiftData or Core Data?

Yes. SwiftData can mirror a local store to CloudKit, and Core Data does the same through NSPersistentCloudKitContainer. That route gives you local persistence plus iCloud sync without writing CKRecord code by hand.

Sources

Build this app without opening Xcode.

AppFlight turns a plain-English prompt into a real native iOS app and ships it to the App Store. Round 2 is open: free for your first month.

Join the waitlist