Migrating Your Schema

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

Schema migration has been a part of SwiftData since it was first introduced back in iOS 17. Migrating the model schemas for your app between versions ensures that data stored in the old format can work with the expected format in the new version of your app. In this segment of the lesson, the focus will be on the high level concepts of migrations, and how you need to update your code to deal with model inheritance. Further details on migration are out of the scope of this lesson.

Setting up a Model Schema

The .modelContainer(for:) method is a convenient way to specify which models SwiftData should care about, and uses convenient defaults when setting up the container. However, it’s not the only way to set up a model container. You can declare the container as a property of the main App struct:

let modelContainer: ModelContainer = {
  //....
}
let modelContainer: ModelContainer = {
  let schema = Schema([Recipe.self])
}
let modelContainer: ModelContainer = {
  let schema = Schema([Recipe.self])
	
  let config = ModelConfiguration(
    schema: schema,
    url: URL.documentsDirectory.appending(path: "myRecipeDatabase.sqlite"),
    allowsSave: true,
    isAutosaveEnabled: true,
    isUndoEnabled: false
  )
}
let modelContainer: ModelContainer = {
  do {
    let schema = Schema([Recipe.self])

    let config = ModelConfiguration(
      schema: schema,
      url: URL.documentsDirectory.appending(path: "myRecipeDatabase.sqlite"),
      allowsSave: true,
      isAutosaveEnabled: true,
      isUndoEnabled: false
    )
	    
    // Initialize the container with the configuration
    container = try ModelContainer(for: schema, configurations: [config])
  } catch {
    fatalError("Failed to configure SwiftData container: \(error)")
  }
}

Setting up a Model Schema with Migration

In order to make your app future proof, you should probably assume that you’ll need a schema migration at some point in the future. To help with this, you can declare your schema as a VersionedSchema:

enum RecipesSchemaV1: VersionedSchema {
  static var versionIdentifier: Schema.Version { Schema.Version(1, 0, 0) }
  static var models: [any PersistentModel.Type] {
    [Recipe.self]
  }
}
enum RecipesMigrationPlan: SchemaMigrationPlan {
  static var schemas: [any VersionedSchema.Type] {
    var currentSchemas: [any VersionedSchema.Type] =
    [RecipesSchemaV1.self]
    return currentSchemas
  }
  
  static var stages: [MigrationStage] {
    var currentStages: [MigrationStage] = []

    return currentStages
  }
}

Migration When Using Child Models

Recall that model inheritance is new in iOS 26, so that means you can only handle migrations with child classes in iOS 26 and above. This means that you need to instruct the compiler that certain parts of the schema migration code are only available in certain versions of the operating system. For example:

@available(iOS 26, *)
enum RecipesSchemaV2: VersionedSchema {
  static var versionIdentifier: Schema.Version { Schema.Version(2, 0, 0) }

  static var models: [any PersistentModel.Type] {
    [
      Recipe.self,
      BakedGood.self,
      Beverage.self
    ]
  }
}
//....
if #available(iOS 26, *) {
  currentSchemas.append(RecipesSchemaV2.self)
}
//...
enum RecipesMigrationPlan: SchemaMigrationPlan {
  static var schemas: [any VersionedSchema.Type] {
    var currentSchemas: [any VersionedSchema.Type] =
    [RecipesSchemaV1.self]
    if #available(iOS 26, *) {
      currentSchemas.append(RecipesSchemaV2.self)
    }
    return currentSchemas
  }

  @available(iOS 26, *)
  static let migrateV1toV2 = MigrationStage.lightweight(
    fromVersion: RecipesSchemaV1.self,
    toVersion: RecipesSchemaV2.self
  )

  static var stages: [MigrationStage] {
    var currentStages: [MigrationStage] = []
    if #available(iOS 26, *) {
      currentStages.append(migrateV1toV2)
    }
    return currentStages
  }
}
See forum comments
Download course materials from Github
Previous: Introduction Next: Migrating a Schema Demo