Thursday, May 21, 2026

Leaving CloudKit

César Pinto Castillo:

CloudKit is one of the best-kept secrets in the Apple platform stack. For years it has quietly powered sync, storage, and sharing for our apps — for free, with zero servers to run, and with end-to-end encryption we didn’t have to design ourselves. And yet, we’re moving off it.

[…]

When a user’s data won’t sync, we have no view into what happened on Apple’s side. We’ve spent years bolting telemetry onto NSPersistentCloudKitContainer.eventChangedNotification just to find out why a save failed — and even with that, we’re guessing from client-side error codes. There are no server logs we can pull, no admin view into the user’s zone.

[…]

CloudKit is supposed to “just work” across Apple platforms. In practice every target has been its own debugging project: macOS only synced on app restart for a while, Apple Watch silently stopped syncing because a user hadn’t accepted a new iCloud ToS — a failure mode we couldn’t even surface to them — and one of our entitlement bugs was reported to us by Apple. AppleTV sync is still flaky in user reports today.

[…]

iCloud signed-out, iCloud full, family-sharing edge cases — CloudKit hands all of this to the client. We’ve built distinct account-state UI for iOS, macOS, watchOS, tvOS, and visionOS, with localizations for each. “Warn the user when their iCloud is full” has been an open ticket of ours since 2025 because we can’t reliably detect it.

Via Fatbobman:

[For] small teams, CloudKit offers an almost unbelievable combination of features[…] But as their product evolved, CloudKit’s limitations became increasingly apparent[…] and most importantly, the inability to truly expand toward the Web and cross-platform ecosystems. Eventually, César’s team migrated to a Supabase/Postgres-based synchronization architecture.

Previously:

Update (2026-06-04): Fatbobman:

Many developers, after integrating CloudKit synchronization with Core Data or SwiftData, encounter a confusing phenomenon: The app synchronizes perfectly across multiple devices, yet when querying Records in the Apple Developer CloudKit Console, it shows “No Records Found” or a completely blank list.

[…]

To resolve this, you need to manually add indexes in the CloudKit Dashboard.

Patrick McConnell:

Sometimes you will mess up the magic. Perhaps you add a record or field to a Model class then remove it. The Schema may become confused and your app may have errors or crash. You can go into the CloudKit console and reset the environment at any point and the current Production Schema will be copied into the Development environment. If you are seeing odd issues with your app revolving around SwiftData or CloudKit feel free to nuke that environment and start over. The next time you run the app in Xcode/Simulator the Schema will be updated to match your actual code. Tip number one is nuke the Development environment and nuke often. Don’t spend hours trying to figure out some strange CloudKit error only to find out at the end that you got your Schema all mixed up.

[…]

In my case what appears to happen is the migration goes fine, the app launches and works as expected with all my new features and shiny new model updates. Until you fully close the app and relaunch. Then SwiftData acts like it’s never seen this data before and is very offended. Boom. FatalError.

4 Comments RSS · Twitter · Mastodon


So I know I am very biased cuz of my love for CloudKit, but I've a bit of beef with the OG article: NSPersistentCloudKitContainer is the root of their pain, not CloudKit. And imo that is simply because using a generic engine to fit every Core Data setup, especially as they grow in complexity, will hit a wall at some point and requiring tailoring. The general pile-on has been just... entirely incorrect because of that wrong target. For example, this commentary caught my eye:

> CloudKit remains one of the strongest moats in Apple’s ecosystem. But as apps grow more complex, datasets larger, and user expectations around synchronization latency higher, its boundaries are becoming increasingly visible. In pursuing “invisible sync,” Apple may also need to seriously rethink this infrastructure layer that has seen relatively little evolution over the years.

CloudKit itself is VERY reliable, fast, and stable as a syncing tool—It's just a key value store with a notification server attached—and that's all it needs to be. That's not to say there isn't much room for improvement (web and sharing outside Apple's ecosystem being YUGE ones) but CKSyncEngine could have solved a lot of technical problems from the article in the same way that decoupling the sync from the data store for the migrated to engine did.


@Dandy It seems like the error opacity and large data handling are specific to using NSPersistentCloudKitContainer but that other issues like the blocking ToS and account full apply to all of CloudKit.


I found their article not very convincing, tbh. A lot of it sounds like issues avoidable on their end:

- ToS-outages are covered by error codes
- Forgetting schema deployment should be covered by testing (there is cktool for that)
- Putting large images into CoreData doesn't sound like a good idea either
- I don't fully understand the telemetry part - for me app-based error tracing via Sentry and CloudKit Console were totally sufficient
- The URL sharing bug is ugly, but something that is easy to find and to solve.

I agree that CloudKit has its downsides. But it also appeared extremely reliable to me over the last years.

And I have my doubts that self-hosting or other 3rd party services are much better. CloudKit survived several 3rd party storage services and Apple haf no security meltdown with it yet. I'd also rather trust Apple to do E2E encryption and privacy protection right than most other companies.


> an open ticket of ours since 2025

Is that supposed to sound like a long time?!

Leave a Comment