Migrating an existing SwiftData store (explicit SQLite URL) to an App Group with CloudKit after migrating from Core Data

I have a production app that originally used Core Data + CloudKit and was later migrated to SwiftData.

The SwiftData migration preserved the existing SQLite store by explicitly pointing ModelConfiguration at the original database:

let configuration = ModelConfiguration(url: storeURL)
let container = try ModelContainer(for: schema, configurations: configuration)

Because of this, my app does not use the higher-level ModelConfiguration(groupContainer:cloudKitDatabase:) initializer.

I would now like to migrate the store into an App Group so it can be shared with widgets.

During the WWDC26 SwiftData Group Lab (around 18:53), the guidance was:

Moving to an App Group container is more involved: it's a different directory and entitlements can't be aligned to the old location, so you'll get a new container and must copy the existing data over into the group container, then start from there.

However, I couldn't find documentation describing how Apple recommends performing that copy for a SwiftData application that already uses an explicit SQLite URL.


Why the Core Data APIs don't seem applicable

The obvious approach would be to use Core Data APIs such as:

  • replacePersistentStore
  • migratePersistentStore

However, these APIs require a Core Data stack and a managed object model (.momd).

After migrating completely to SwiftData, I no longer have a .momd in my project, so creating an NSPersistentContainer solely to move an existing SQLite store doesn't appear to be possible.

Is there a supported way to use these APIs with a SwiftData store, or are they no longer intended for this scenario?


Experiment

Since the migration happens before creating the ModelContainer, I experimented with simply moving the entire persistence package using FileManager before SwiftData is initialized.

Specifically I move:

  • Store.sqlite
  • Store.sqlite-wal
  • Store.sqlite-shm
  • .Store_SUPPORT
  • Store_ckAssets

from the application's Application Support directory into the App Group container, and then initialize SwiftData using:

let configuration = ModelConfiguration(url: appGroupStoreURL)
let container = try ModelContainer(for: schema, configurations: configuration)

After doing this:

  • all existing data is present;
  • new data can be created successfully;
  • if I run an older build that still points to Application Support, SwiftData simply creates a brand-new empty store there, which suggests the original store was indeed moved successfully.

So from a local persistence perspective, this appears to work.


Remaining concern

Although this approach appears to preserve the SQLite store unchanged, I don't know whether it is actually safe for CloudKit.

Specifically:

  • Does moving the complete persistence package with FileManager preserve all CloudKit metadata needed for continued synchronization?
  • Is there any risk that CloudKit will treat the moved store as a different store and re-upload or duplicate records?
  • Are there additional files or directories that must also be moved besides:
    • Store.sqlite
    • Store.sqlite-wal
    • Store.sqlite-shm
    • .Store_SUPPORT
    • Store_ckAssets
  • Is there an Apple-recommended migration path for this scenario that avoids introducing a temporary Core Data model purely to move the store?

In other words:

What is the recommended migration path for an existing production SwiftData application using an explicit SQLite URL to move into an App Group while continuing to use CloudKit?


One additional question

The SwiftData documentation provides two different ways to configure persistent storage:

ModelConfiguration(
    groupContainer: ...,
    cloudKitDatabase: ...
)

and

ModelConfiguration(
    url: ...,
    cloudKitDatabase: ...
)

My understanding is that when using the groupContainer initializer, SwiftData may automatically handle moving the persistent store into the App Group when the application is updated.

However, when using the url initializer, the application is explicitly responsible for choosing the store location.

Is that understanding correct?

If so:

  • Is there any supported automatic migration mechanism when using ModelConfiguration(url:), or is manual migration expected?
  • If manual migration is expected, is moving the complete persistence package (.sqlite, -wal, -shm, .Store_SUPPORT, Store_ckAssets) before creating the ModelContainer the recommended approach?
  • Or is there another Apple-recommended migration path for this scenario?

My related posts/questions

Answered by DTS Engineer in 897408022

makeManagedObjectModel(for:mergedWith:) allows you to create a Core Data model with your SwiftData schema. With the returned Core Data model, you should be able to use replacePersistentStore(at:destinationOptions:withPersistentStoreFrom:sourceOptions:type:) to copy the store.

When you share a Core Data + CloudKit store between your main app and an extension, the synchronization becomes a bit tricky, as discuseed here. You might want to go through the thread before going further.

Best,
——
Ziqiao Chen
 Worldwide Developer Relations.

makeManagedObjectModel(for:mergedWith:) allows you to create a Core Data model with your SwiftData schema. With the returned Core Data model, you should be able to use replacePersistentStore(at:destinationOptions:withPersistentStoreFrom:sourceOptions:type:) to copy the store.

When you share a Core Data + CloudKit store between your main app and an extension, the synchronization becomes a bit tricky, as discuseed here. You might want to go through the thread before going further.

Best,
——
Ziqiao Chen
 Worldwide Developer Relations.

Thank you for the pointer to makeManagedObjectModel(for:mergedWith:).

My production app currently targets iOS 17, which is also the current recommended deployment target in Xcode, so that API isn't available to my existing users.

What is the recommended migration path for applications targeting iOS 17–18?

Specifically, if a SwiftData application has already fully migrated away from Core Data and no longer contains a .momd, should it:

  • manually move the complete persistence package (.sqlite, -wal, -shm, .Store_SUPPORT, Store_ckAssets) into the App Group before creating the ModelContainer, or
  • recreate/maintain a Core Data model solely to use replacePersistentStore, even though the application otherwise no longer uses Core Data?

Reintroducing a Core Data model seems undesirable because the application has already gone through several lightweight Core Data migrations and, more recently, SwiftData schema migrations. Maintaining a second representation of the schema solely for a one-time file relocation doesn't seem like the intended long-term solution.

If manually moving the persistence package is the recommended approach, does moving the complete package preserve all CloudKit metadata required for continued synchronization, or are there additional files or migration steps required to ensure CloudKit continues syncing the existing store without re-importing or duplicating records?

In other words, what is the Apple-recommended migration path for existing SwiftData applications on iOS 17–18?

If makeManagedObjectModel(for:mergedWith:) is unavailable on the target platforms, I'd go with copying the files using file system APIs, rather than maintaining a Core Data model. The CloudKit synchronization should be fine because the required metadata is part of the database. The hidden folder (.Store_SUPPORT) may contain files for attributes that "Allow External Storage". Be sure to test it if that is your case.

The names and structure of the database files and the supporting folders are not publicly documented, but in your case, this is a one-time migration, and so you wil be good.

Maybe worth mentioning, I typically put my database under my own folder (Application Support > [MyAppFolder] > Store.* and etc). That way, I can copy the database by simply copying [MyAppFolder], if needed.

Best,
——
Ziqiao Chen
 Worldwide Developer Relations.

Thank you again for the clarification.

Based on your recommendation for iOS 17–18, I implemented the migration as a one-time file-system move before creating the ModelContainer.

I also took your suggestion of placing the database inside its own folder within the App Group, so the migration simply moves the entire persistence package into that directory before SwiftData is initialized.

In case it's useful to anyone else who comes across this thread, here's the implementation I've ended up with:

let modelContainer: ModelContainer = {
    let schema = Schema([
        MyModel.self
    ])

    let fileManager = FileManager.default

    let storePrefix = "Store"
    let storeName = "\(storePrefix).sqlite"

    let appGroupID = "group.com.example.myapp"

    let applicationSupportDirectory = fileManager.urls(for: .applicationSupportDirectory,
                                                       in: .userDomainMask)[0]
    let oldStoreURL = applicationSupportDirectory.appendingPathComponent(storeName)

    guard let appGroupURL = fileManager.containerURL(
        forSecurityApplicationGroupIdentifier: appGroupID
    ) else {
        fatalError("App Group not found.")
    }

    // Store everything in a dedicated folder so future migrations only need
    // to move this directory.
    let persistenceFolderURL = appGroupURL.appendingPathComponent("Persistence", isDirectory: true)
    let newStoreURL = persistenceFolderURL.appendingPathComponent(storeName)

    if !fileManager.fileExists(atPath: persistenceFolderURL.path) {
        try! fileManager.createDirectory(at: persistenceFolderURL,
                                         withIntermediateDirectories: true)
    }

    // One-time migration from Application Support to the App Group.
    if fileManager.fileExists(atPath: oldStoreURL.path) &&
        !fileManager.fileExists(atPath: newStoreURL.path) {

        let contents = try! fileManager.contentsOfDirectory(
            at: applicationSupportDirectory,
            includingPropertiesForKeys: nil,
            options: [] // Don't skip hidden folders such as .Store_SUPPORT
        )

        for fileURL in contents {
            let fileName = fileURL.lastPathComponent

            let shouldMove =
                fileName == storeName ||
                fileName == "\(storeName)-wal" ||
                fileName == "\(storeName)-shm" ||
                fileName == ".\(storePrefix)_SUPPORT" ||
                fileName == "\(storePrefix)_ckAssets"

            if shouldMove {
                let destinationURL = persistenceFolderURL.appendingPathComponent(fileName)
                try! fileManager.moveItem(at: fileURL, to: destinationURL)
            }
        }
    }

    let configuration = ModelConfiguration(url: newStoreURL)
    return try! ModelContainer(for: schema, configurations: configuration)
}()
Migrating an existing SwiftData store (explicit SQLite URL) to an App Group with CloudKit after migrating from Core Data
 
 
Q