{"id":51981,"date":"2026-05-21T14:00:13","date_gmt":"2026-05-21T18:00:13","guid":{"rendered":"https:\/\/mjtsai.com\/blog\/?p=51981"},"modified":"2026-06-11T15:26:47","modified_gmt":"2026-06-11T19:26:47","slug":"leaving-cloudkit","status":"publish","type":"post","link":"https:\/\/mjtsai.com\/blog\/2026\/05\/21\/leaving-cloudkit\/","title":{"rendered":"Leaving CloudKit"},"content":{"rendered":"<p><a href=\"https:\/\/blog.ambi.se\/leaving-cloudkit\">C&eacute;sar Pinto Castillo<\/a>:<\/p>\n<blockquote cite=\"https:\/\/blog.ambi.se\/leaving-cloudkit\">\n<p>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 &mdash; for free, with zero servers to run, and with end-to-end encryption we didn&rsquo;t have to design ourselves. And yet, we&rsquo;re moving off it.<\/p>\n<p>[&#8230;]<\/p>\n<p>When a user&rsquo;s data won&rsquo;t sync, we have no view into what happened on Apple&rsquo;s side. We&rsquo;ve spent years bolting telemetry onto <code class=\"language-plaintext highlighter-rouge\">NSPersistentCloudKitContainer.eventChangedNotification<\/code> just to find out <em>why<\/em> a save failed &mdash; and even with that, we&rsquo;re guessing from client-side error codes. There are no server logs we can pull, no admin view into the user&rsquo;s zone.<\/p>\n<p>[&#8230;]<\/p>\n<p>CloudKit is supposed to &ldquo;just work&rdquo; 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&rsquo;t accepted a new iCloud ToS &mdash; a failure mode we couldn&rsquo;t even surface to them &mdash; and one of our entitlement bugs was reported to <em>us<\/em> by Apple. AppleTV sync is still flaky in user reports today.<\/p>\n<p>[&#8230;]<\/p>\n<p>iCloud signed-out, iCloud full, family-sharing edge cases &mdash; CloudKit hands all of this to the client. We&rsquo;ve built distinct account-state UI for iOS, macOS, watchOS, tvOS, and visionOS, with localizations for each. &ldquo;Warn the user when their iCloud is full&rdquo; has been an open ticket of ours since 2025 because we can&rsquo;t reliably detect it.<\/p>\n<\/blockquote>\n\n<p>Via <a href=\"https:\/\/weekly.fatbobman.com\/p\/fatbobmans-swift-weekly-136\">Fatbobman<\/a>:<\/p>\n<blockquote cite=\"https:\/\/weekly.fatbobman.com\/p\/fatbobmans-swift-weekly-136\">\n<p>[For] small teams, CloudKit offers an almost unbelievable combination of features[&#8230;] But as their product evolved, CloudKit&rsquo;s limitations became increasingly apparent[&#8230;] and most importantly, the inability to truly expand toward the Web and cross-platform ecosystems. Eventually, C&eacute;sar&rsquo;s team migrated to a Supabase\/Postgres-based synchronization architecture.<\/p>\n<\/blockquote>\n\n<p>Previously:<\/p>\n<ul>\n<li><a href=\"https:\/\/mjtsai.com\/blog\/2026\/04\/01\/cloudkit-problems-with-ios-26-4\/\">CloudKit Problems With iOS 26.4<\/a><\/li>\n<li><a href=\"https:\/\/mjtsai.com\/blog\/2024\/05\/29\/cloudkit-throttles-and-debugging\/\">CloudKit Throttles and Debugging<\/a><\/li>\n<li><a href=\"https:\/\/mjtsai.com\/blog\/2022\/04\/18\/nspersistentcloudkitcontainer-in-2022\/\">NSPersistentCloudKitContainer in 2022<\/a><\/li>\n<li><a href=\"https:\/\/mjtsai.com\/blog\/2019\/06\/04\/syncing-core-data-with-cloudkit-and-nspersistentcloudkitcontainer\/\">Syncing Core Data With CloudKit and NSPersistentCloudKitContainer<\/a><\/li>\n<\/ul>\n\n<p id=\"leaving-cloudkit-update-2026-06-04\">Update (<a href=\"#leaving-cloudkit-update-2026-06-04\">2026-06-04<\/a>): <a href=\"https:\/\/fatbobman.com\/en\/snippet\/show-records-in-cloudkit-dashboard\/\">Fatbobman<\/a>:<\/p>\n<blockquote cite=\"https:\/\/fatbobman.com\/en\/snippet\/show-records-in-cloudkit-dashboard\/\">\n<p>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 <code>Records<\/code> in the Apple Developer CloudKit Console, it shows &ldquo;No Records Found&rdquo; or a completely blank list.<\/p>\n<p>[&#8230;]<\/p>\n<p>To resolve this, you need to manually add indexes in the CloudKit Dashboard.<\/p>\n<\/blockquote>\n\n<p><a href=\"https:\/\/pmcconnell.micro.blog\/2026\/04\/30\/fun-with-swiftdata-and-cloudkit.html\">Patrick McConnell<\/a>:<\/p>\n<blockquote cite=\"https:\/\/pmcconnell.micro.blog\/2026\/04\/30\/fun-with-swiftdata-and-cloudkit.html\">\n<p>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&rsquo;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.<\/p>\n<p>[&#8230;]<\/p>\n<p>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&rsquo;s never seen this data before and is very offended. Boom. FatalError.<\/p>\n<\/blockquote>\n\n<p id=\"leaving-cloudkit-update-2026-06-11\">Update (<a href=\"#cloudkit-bugs-in-may-2026-update-2026-06-11\">2026-06-11<\/a>): <a href=\"https:\/\/mastodon.social\/@agiletortoise\/116728042737815634\">Greg Pierce<\/a>:<\/p>\n<blockquote cite=\"https:\/\/mastodon.social\/@agiletortoise\/116728042737815634\">\n<p>Drafts sync still has workarounds for issues that probably haven&rsquo;t been in CloudKit since iOS 8, but I&rsquo;m scared to rip them out. Those early days were rough. <\/p>\n<p>But, I don&rsquo;t have servers and user data to worry about, so I don&rsquo;t complain too much.<\/p>\n<\/blockquote>","protected":false},"excerpt":{"rendered":"<p>C&eacute;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 &mdash; for free, with zero servers to run, and with end-to-end encryption we didn&rsquo;t have to design ourselves. And yet, we&rsquo;re moving off it. [&#8230;] When a [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"apple_news_api_created_at":"2026-05-21T18:00:16Z","apple_news_api_id":"2475a1ad-ad13-44db-b563-1521e3d865e7","apple_news_api_modified_at":"2026-06-11T19:26:50Z","apple_news_api_revision":"AAAAAAAAAAAAAAAAAAAAAg==","apple_news_api_share_url":"https:\/\/apple.news\/AJHWhra0TRNu1YxUh49hl5w","apple_news_coverimage":0,"apple_news_coverimage_caption":"","apple_news_is_hidden":false,"apple_news_is_paid":false,"apple_news_is_preview":false,"apple_news_is_sponsored":false,"apple_news_maturity_rating":"","apple_news_metadata":"\"\"","apple_news_pullquote":"","apple_news_pullquote_position":"","apple_news_slug":"","apple_news_sections":"\"\"","apple_news_suppress_video_url":false,"apple_news_use_image_component":false,"footnotes":""},"categories":[4],"tags":[916,109,31,2741,30,2742,325,71,2404,1558,2780,2403,2772,1212,2599],"class_list":["post-51981","post","type-post","status-publish","format-standard","hentry","category-programming-category","tag-cloudkit","tag-coredata","tag-ios","tag-ios-26","tag-mac","tag-macos-tahoe-26","tag-postgresql","tag-programming","tag-swiftdata","tag-tvos","tag-tvos-26","tag-visionos","tag-visionos-26","tag-watchos","tag-watchos-26"],"apple_news_notices":[],"_links":{"self":[{"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/posts\/51981","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/comments?post=51981"}],"version-history":[{"count":4,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/posts\/51981\/revisions"}],"predecessor-version":[{"id":52213,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/posts\/51981\/revisions\/52213"}],"wp:attachment":[{"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/media?parent=51981"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/categories?post=51981"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/tags?post=51981"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}