PhotoKit background upload extension — evaluation for cairn
PhotoKit background upload extension
An Immich GitHub comment noted that “iOS 27 added a PhotoKit background
upload extension.” This note records what the API is and why cairn
doesn’t use it. API details come from the iOS 27.1 SDK headers
(Photos.framework: PHAssetResourceUploadJob.h,
PHAssetResourceUploadJobChangeRequest.h, PHPhotoLibrary.h, and the
Swift interface), not from Apple’s prose docs, which render client-side
and don’t fetch.
What it is
- iOS 26.1 introduced the
com.apple.photos.background-uploadextension point. The system invokes the extension on its own schedule; the extension enqueuesPHAssetResourceUploadJobs throughPHAssetResourceUploadJobChangeRequest. A job is onePHAssetResourceplus a destinationNSURLRequest, and the system performs the upload even while the app is suspended or the device is locked. - The host app turns the feature on via
PHPhotoLibrary, and it requires full library access. Unacknowledged jobs are capped atPHAssetResourceUploadJob.jobLimit; the extension must acknowledge or retry finished jobs to free slots. - iOS 26.4 added
responseHeaderFields,error,cancel(), and a download-only job type (creationRequestForDownloadJob(resource:)), which pulls an iCloud original onto the device without uploading it. The system may purge the downloaded copy later. - iOS 27 deprecated
PHBackgroundResourceUploadExtension(process()/notifyTermination()) in favor ofPHBackgroundResourceUploadJobExtension(asyncprocessJobs()/willTerminate()). It also replacedsetUploadJobExtensionEnabledwithenableUploadJobExtension(options:)/disableUploadJobExtension(), addedPHAssetResourceUploadJobOptions.preventsExpensiveNetworkAccess(Wi-Fi-only), and addedPHAssetResource.assetResource(forUploadJob:). The iOS 27 comment refers to this revision of the 26.1 feature. - The upload body is the raw resource bytes only. Immich’s upload
endpoint needs multipart form fields (
deviceAssetId,deviceId), so the Immich app hasn’t adopted it (immich#23245).
Why cairn doesn’t use it
- cairn doesn’t upload. The API is a system-managed upload queue. cairn’s network work is tagging and trashing on the Immich server, which the API can’t express.
- Using it as a background wake is a bad bet. In principle the
extension could check for deletions (
fetchPersistentChanges) and drain the trash queue when the system launches it. Against that:- The headers don’t say when the system launches the extension.
- cairn would need an App Group to share its SwiftData stores with the extension.
- Using an upload extension for anything else invites App Review rejection under guideline 2.5.4 (background services only for their intended purpose).
- Users would see it as “background upload”, which misdescribes cairn.
- The closest real fit is the download-only job type. It could
pre-fetch iCloud originals for the hash backlog that the
app.cairn.hashBGProcessingTaskcurrently handles. It isn’t worth a new extension target on its own: it works only from inside the extension, the system may purge the download, and it carries the same “background upload” framing.
Worth watching
If the Immich app or a third-party uploader (e.g. YAIIU) moves to this
API, check which PHAssetResource it uploads. If it isn’t the original,
the server-side SHA1 won’t match the checksum cairn computes on the
phone, and reconciliation will treat those assets as unmatched.
Sources
- iOS 27.1 SDK,
Photos.frameworkheaders and Swift interface (Xcode 27.1). - immich#23245 — the two limitations blocking Immich adoption.
- immich#23358 — original feature request.
- nextcloud/ios#4279 — a shipping iOS 27 adoption.
- FawenYo/YAIIU#78 — an Immich uploader adopting the iOS 27 extension.