Skip to content

← All tools

Free check for Apple developers

SwiftData CloudKit sync checker

For SwiftData to sync with iCloud, Apple's rules are: no unique constraints (@Attribute(.unique) or #Unique), every relationship optional and with an inverse, and no .deny delete rule. Core Data, which does the syncing, also refuses to load the store if a property is not optional and has no default value where it is declared. Paste your @Model classes below to see each problem, the Apple page behind it and the fixed line. Under the checker: the setup steps Apple lists (iCloud with a CloudKit container, Background Modes with Remote notifications, ModelConfiguration cloudKitDatabase) with copyable code.

Check your @Model classes

Paste one or more SwiftData model classes. The check runs in this page; nothing is uploaded.

Paste your @Model classes above to see what would stop them syncing with iCloud.

Apple's rules for models that sync with iCloud

SwiftData syncs through the same CloudKit layer as Core Data, so the Core Data rules apply too.

Setup steps Apple lists

From Apple's article on syncing SwiftData model data. The code is Apple's own sample; swap in your container identifier and model types. SwiftData: Syncing model data across a person's devices

1. iCloud capability with CloudKit

In Xcode, open your target's Signing & Capabilities, click + Capability and add iCloud. Select CloudKit, then add or choose a container under Containers (identifiers start with iCloud.). The iCloud capability needs a developer account with admin permissions.

2. Background Modes: Remote notifications

Add the Background Modes capability and select Remote notifications. CloudKit sends silent notifications when data changes on the server, and SwiftData uses them to bring in the changes.

3. Choose the container (optional)

By default SwiftData uses the first CloudKit container listed in your entitlements. To pick one yourself, pass it as cloudKitDatabase:

let config = ModelConfiguration(cloudKitDatabase: .private("iCloud.com.example.Trips"))
let modelContainer = try ModelContainer(for: Trip.self, Accommodation.self,
                                        configurations: config)

Turn sync off

If the app already uses CloudKit for something else and the models should stay on the device, pass .none:

let config = ModelConfiguration(cloudKitDatabase: .none)
let modelContainer = try ModelContainer(for: Trip.self, Accommodation.self,
                                        configurations: config)

4. Create the development schema

Apple's sample sets up the CloudKit development schema in Debug builds only, then opens the SwiftData container. Check the result in the CloudKit Console.

let config = ModelConfiguration()

do {
#if DEBUG
    // Use an autorelease pool to make sure Swift deallocates the persistent
    // container before setting up the SwiftData stack.
    try autoreleasepool {
        let desc = NSPersistentStoreDescription(url: config.url)
        let opts = NSPersistentCloudKitContainerOptions(containerIdentifier: "iCloud.com.example.Trips")
        desc.cloudKitContainerOptions = opts
        // Load the store synchronously so it completes before initializing the
        // CloudKit schema.
        desc.shouldAddStoreAsynchronously = false
        if let mom = NSManagedObjectModel.makeManagedObjectModel(for: [Trip.self, Accommodation.self]) {
            let container = NSPersistentCloudKitContainer(name: "Trips", managedObjectModel: mom)
            container.persistentStoreDescriptions = [desc]
            container.loadPersistentStores {_, err in
                if let err {
                    fatalError(err.localizedDescription)
                }
            }
            // Initialize the CloudKit schema after the store finishes loading.
            try container.initializeCloudKitSchema()
            // Remove and unload the store from the persistent container.
            if let store = container.persistentStoreCoordinator.persistentStores.first {
                try container.persistentStoreCoordinator.remove(store)
            }
        }
    }
#endif
    modelContainer = try ModelContainer(for: Trip.self, Accommodation.self,
                                        configurations: config)
} catch {
    fatalError(error.localizedDescription)
}

5. Promote the schema before release

Promote the development schema to production in the CloudKit Console before you ship. From then on, changes can only add model types and properties.

Sources

Apple's pages, read on 1 November 2026:

How it works

  • •The page reads your Swift text and finds each class marked @Model, its stored properties, their types and default values, and the options in @Attribute, @Relationship and #Unique. Computed properties, static properties and @Transient properties are skipped.
  • •Each property is checked against the rules on Apple's SwiftData sync article and the Core Data CloudKit model page. Every finding names the rule, links the Apple page and shows the line after the fix; a copy of all your models with every fix applied is under the list.
  • •For inverses it looks at both sides: a pair is fine when either side names the other in inverse:. When no property points back, the fix adds one to the other model.
  • •The rules were checked against Core Data itself: a sample with every kind of problem was loaded with NSPersistentCloudKitContainer on macOS, Core Data listed the same problems this page finds, and after the fixes the store loaded with no error. The fixed models were type-checked against the iOS 27.1 SDK with an iOS 17 target.
  • •The rules and setup steps come from Apple's pages listed under Sources, read on 1 November 2026.

What it does and does not do

  • •It is a text parser for common code shapes, not the Swift compiler. Unusual code, such as several properties declared on one line, macros of your own or types defined through typealiases, may be read wrongly or missed.
  • •It knows only the models you paste. A property whose type is a model that is not pasted, and that has no @Relationship, is read as a plain property. Paste all related models together.
  • •It does not check your entitlements, capabilities, CloudKit container or the CloudKit Console. Use the setup steps below for those.
  • •It does not know what is already in your production CloudKit schema, so it cannot tell whether a change is still allowed there. It only warns about renames written with originalName:.
  • •Making a property optional is the safe fix, but code that reads it needs updating, and a default you choose may suit your data better than the one shown.
  • •Configurations, record size limits and conflicts between devices are not checked.

FAQ

Why can't I use @Attribute(.unique) with CloudKit?
Apple's SwiftData sync article says CloudKit cannot enforce the unique option, because changes sync at different times on different devices. The Core Data CloudKit page says unique constraints are not supported. Remove .unique (and #Unique) and handle duplicates in your own code after a sync, for example by merging records that share the same identifier.
Do to-many relationships have to be optional too?
Yes. Apple says CloudKit requires all relationships to be optional, and a non-optional array such as var items: [Item] = [] counts as required: Core Data reports it when the store loads. Write var items: [Item]? = [] and read it as items ?? [].
Why does my property need a default value when init() already sets it?
The value set in init() is not part of the model schema. Core Data's CloudKit setup checks the schema and refuses any property that is not optional and has no default value. Give the default where the property is declared, for example var name: String = "", or make it optional.
Do I need inverse: on both sides of a relationship?
No, name it on one side only. Apple says SwiftData sets the inverse when it can reliably infer it from your schema, and that you should set it explicitly otherwise. Inference fails when a model has more than one relationship to the same model, so naming it on one side of every pair avoids the doubt.
Which capabilities does SwiftData need for iCloud sync?
Two, per Apple: iCloud with CloudKit selected and a container chosen, and Background Modes with Remote notifications selected so CloudKit can tell the app about changes on the server. By default SwiftData uses the first CloudKit container in your entitlements; pass ModelConfiguration(cloudKitDatabase: .private("iCloud.…")) to choose one, or .none to keep the data on the device.
Can I change my models after the app is released?
Only by adding. Once the CloudKit schema is promoted to production, Apple says you cannot delete model types or change existing properties, and Core Data's page adds that record types and fields cannot be renamed. Add new properties with a default value or as optional, and leave old ones in place.

Guides that use this tool

Related