diff --git a/src/components/Settings/SettingsDialog.tsx b/src/components/Settings/SettingsDialog.tsx
index 7dfa04a0..bea6ff6f 100644
--- a/src/components/Settings/SettingsDialog.tsx
+++ b/src/components/Settings/SettingsDialog.tsx
@@ -7,7 +7,7 @@ import { useEffect, useState } from 'react'
 import { FormProvider, useForm } from 'react-hook-form'
 
 import { ProxySettings } from './ProxySettings'
-import { AppSettingsSchema } from '@/schemas/appSettings'
+import { AppSettingsSchema } from '@/schemas/settings'
 import { RecorderSettings } from './RecorderSettings'
 import { AppSettings } from '@/types/settings'
 import { UsageReportSettings } from './UsageReportSettings'
diff --git a/src/schemas/settings/README.md b/src/schemas/settings/README.md
new file mode 100644
index 00000000..f92934a4
--- /dev/null
+++ b/src/schemas/settings/README.md
@@ -0,0 +1,27 @@
+# Settings migration
+
+Migrations are needed when changing the structure of existing settings file such as renaming or deleting keys.
+
+If you're simply adding new options to the settings file, a migration is not needed. In this case, extend the latest schema available.
+
+## Creating a new migration
+
+For when a new migration is needed:
+
+1. Create a directory for the new version (e.g. `/v2`).
+2. Declare the new schema with appropriate changes.
+3. In `/v2/index.ts`, implement a `migrate` function that takes a `v2` schema and returns a `v3` schema.
+4. Update `/schemas/settings/index.ts` to use the new version:
+
+```ts
+function migrate(settings: z.infer<typeof AnySettingSchema>) {
+
+case '2.0':
+  return migrate(v2.migrate(settings))
+
+}
+```
+
+5. Export types from the new version in `/schemas/settings/index.ts`.
+6. Update the default settings in `src/settings.ts` according to the new schema.
+7. Make changes to the remaining implementation to use the new schema.
diff --git a/src/schemas/settings/index.test.ts b/src/schemas/settings/index.test.ts
new file mode 100644
index 00000000..8bb9fe00
--- /dev/null
+++ b/src/schemas/settings/index.test.ts
@@ -0,0 +1,34 @@
+import { describe, expect, it } from 'vitest'
+import { migrate } from '.'
+import * as v1 from './v1'
+
+describe('Settings migration', () => {
+  it('should migrate from v1 to latest', () => {
+    const v1Settings: v1.AppSettings = {
+      version: '1.0',
+      proxy: {
+        mode: 'regular',
+        port: 6000,
+        automaticallyFindPort: true,
+      },
+      recorder: {
+        detectBrowserPath: true,
+      },
+      windowState: {
+        width: 1200,
+        height: 800,
+        x: 0,
+        y: 0,
+        isMaximized: true,
+      },
+      usageReport: {
+        enabled: true,
+      },
+      appearance: {
+        theme: 'system',
+      },
+    }
+
+    expect(migrate(v1Settings).version).toBe('2.0')
+  })
+})
diff --git a/src/schemas/settings/index.ts b/src/schemas/settings/index.ts
new file mode 100644
index 00000000..915fb18b
--- /dev/null
+++ b/src/schemas/settings/index.ts
@@ -0,0 +1,30 @@
+import { z } from 'zod'
+import * as v1 from './v1'
+import * as v2 from './v2'
+import { exhaustive } from '../../utils/typescript'
+
+const AnySettingSchema = z.discriminatedUnion('version', [
+  v1.AppSettingsSchema,
+  v2.AppSettingsSchema,
+])
+
+export function migrate(settings: z.infer<typeof AnySettingSchema>) {
+  switch (settings.version) {
+    case '1.0':
+      return migrate(v1.migrate(settings))
+    case '2.0':
+      return settings
+    default:
+      return exhaustive(settings)
+  }
+}
+
+export const AppSettingsSchema = AnySettingSchema.transform(migrate)
+
+export {
+  AppearanceSchema,
+  ProxySettingsSchema,
+  RecorderSettingsSchema,
+  UsageReportSettingsSchema,
+  WindowStateSchema,
+} from './v2'
diff --git a/src/schemas/appSettings.ts b/src/schemas/settings/v1/index.ts
similarity index 83%
rename from src/schemas/appSettings.ts
rename to src/schemas/settings/v1/index.ts
index 84bf7df2..6d04b894 100644
--- a/src/schemas/appSettings.ts
+++ b/src/schemas/settings/v1/index.ts
@@ -1,4 +1,5 @@
 import { z } from 'zod'
+import * as v2 from '../v2'
 
 export const RegularProxySettingsSchema = z.object({
   mode: z.literal('regular'),
@@ -86,7 +87,7 @@ export const RecorderSettingsSchema = z
     }
   })
 
-const WindowStateSchema = z.object({
+export const WindowStateSchema = z.object({
   x: z.number().int(),
   y: z.number().int(),
   width: z.number().int(),
@@ -94,15 +95,31 @@ const WindowStateSchema = z.object({
   isMaximized: z.boolean(),
 })
 
-const AppearanceSchema = z.object({
+export const AppearanceSchema = z.object({
   theme: z.union([z.literal('light'), z.literal('dark'), z.literal('system')]),
 })
 
 export const AppSettingsSchema = z.object({
-  version: z.string(),
+  version: z.literal('1.0'),
   proxy: ProxySettingsSchema,
   recorder: RecorderSettingsSchema,
   windowState: WindowStateSchema,
   usageReport: UsageReportSettingsSchema,
   appearance: AppearanceSchema,
 })
+
+// Migrate settings to the next version
+export function migrate(
+  settings: z.infer<typeof AppSettingsSchema>
+): v2.AppSettings {
+  return {
+    version: '2.0',
+    proxy: settings.proxy,
+    recorder: settings.recorder,
+    windowState: settings.windowState,
+    usageReport: settings.usageReport,
+    appearance: settings.appearance,
+  }
+}
+
+export type AppSettings = z.infer<typeof AppSettingsSchema>
diff --git a/src/schemas/settings/v2/index.ts b/src/schemas/settings/v2/index.ts
new file mode 100644
index 00000000..758adfbb
--- /dev/null
+++ b/src/schemas/settings/v2/index.ts
@@ -0,0 +1,32 @@
+import { z } from 'zod'
+import {
+  AppearanceSchema,
+  ProxySettingsSchema,
+  RecorderSettingsSchema,
+  UsageReportSettingsSchema,
+  WindowStateSchema,
+} from '../v1'
+
+export {
+  AppearanceSchema,
+  ProxySettingsSchema,
+  RecorderSettingsSchema,
+  UsageReportSettingsSchema,
+  WindowStateSchema,
+}
+
+export const AppSettingsSchema = z.object({
+  version: z.literal('2.0'),
+  proxy: ProxySettingsSchema,
+  recorder: RecorderSettingsSchema,
+  windowState: WindowStateSchema,
+  usageReport: UsageReportSettingsSchema,
+  appearance: AppearanceSchema,
+})
+
+export type AppSettings = z.infer<typeof AppSettingsSchema>
+
+// TODO: Migrate settings to the next version
+export function migrate(settings: z.infer<typeof AppSettingsSchema>) {
+  return { ...settings }
+}
diff --git a/src/settings.ts b/src/settings.ts
index f3ae40b7..d2983f72 100644
--- a/src/settings.ts
+++ b/src/settings.ts
@@ -2,13 +2,13 @@ import { app, dialog } from 'electron'
 import { writeFile, open } from 'fs/promises'
 import path from 'node:path'
 import { AppSettings } from './types/settings'
-import { AppSettingsSchema } from './schemas/appSettings'
+import { AppSettingsSchema } from './schemas/settings'
 import { existsSync, readFileSync } from 'fs'
 import { safeJsonParse } from './utils/json'
 import log from 'electron-log/main'
 
 const defaultSettings: AppSettings = {
-  version: '1.0',
+  version: '2.0',
   proxy: {
     mode: 'regular',
     port: 6000,
diff --git a/src/types/settings.ts b/src/types/settings.ts
index ea2559b4..599dbd3d 100644
--- a/src/types/settings.ts
+++ b/src/types/settings.ts
@@ -3,7 +3,7 @@ import {
   ProxySettingsSchema,
   RecorderSettingsSchema,
   UsageReportSettingsSchema,
-} from '@/schemas/appSettings'
+} from '@/schemas/settings'
 import { z } from 'zod'
 
 export type AppSettings = z.infer<typeof AppSettingsSchema>