diff --git a/lib/context.dart b/lib/context.dart
index 0240cebaf28fa30235194ab2fc5f422518f6895e..a0c24a7cf8730da62238184e88dba3ad0544f48a 100644
--- a/lib/context.dart
+++ b/lib/context.dart
@@ -2,14 +2,27 @@ import 'untis/class.dart';
 import 'untis/fetch.dart';
 import 'untis/lecture.dart';
 import 'user/profile.dart';
+import 'user/save_state.dart';
 import 'util/listenable.dart';
 
 class AppContext {
   final UntisFetch untis;
+  final SaveManager _saveManager;
   final Listenable<Profile?> profile = Listenable(null);
+  final Listenable<DateTime> date = Listenable(DateTime.now());
+
   List<Class>? classes;
   List<Lecture>? lectures;
-  final Listenable<DateTime> date = Listenable(DateTime.now());
 
-  AppContext(this.untis);
+  AppContext(this.untis, SaveManager saveManager) : _saveManager = saveManager;
+
+  Future<void> save() => _saveManager.save();
+
+  Future<void> loadProfile() async {
+    profile.value = await _saveManager.load();
+
+    profile.value!
+      ..hiddenLectures.onChange.listen((_) => save())
+      ..selectedClasses.onChange.listen((_) => save());
+  }
 }
diff --git a/lib/main.dart b/lib/main.dart
index 8ae838986e6c52abcee1b3f0b27670ad70dbe191..9fd493976f7f5858fc4c9896d87f8612edff55fd 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -9,39 +9,57 @@ import 'timetable/timetable.dart';
 import 'toolbar/toolbar.dart';
 import 'untis/fetch.dart';
 import 'untis/timetable.dart';
-import 'user/profile.dart';
+import 'user/save_state.dart';
 import 'util/listenable.dart';
 
 void main() {
   runApp(const MyApp());
 }
 
-class MyApp extends StatelessWidget {
+class MyApp extends StatefulWidget {
   const MyApp({super.key});
 
+  @override
+  State<MyApp> createState() => _MyAppState();
+}
+
+class _MyAppState extends State<MyApp> {
+  final SaveManager saveManager = SaveManager();
+  final UntisFetch untis = UntisFetch();
+  late final AppContext appContext = AppContext(untis, saveManager);
+
+  @override
+  void initState() {
+    super.initState();
+    appContext.loadProfile();
+  }
+
   @override
   Widget build(BuildContext context) {
     return MaterialApp(
       title: appName,
       theme: theme,
-      home: const MyHomePage(),
+      home: MyHomePage(appContext: appContext),
     );
   }
 }
 
 class MyHomePage extends StatefulWidget {
-  const MyHomePage({super.key});
+  final AppContext appContext;
+
+  const MyHomePage({super.key, required this.appContext});
 
   @override
   State<MyHomePage> createState() => _MyHomePageState();
 }
 
 class _MyHomePageState extends State<MyHomePage> {
-  final UntisFetch untis = UntisFetch();
   TimetableData? timetable;
-  late AppContext appContext = AppContext(untis);
   late List<StreamSubscription> _subscriptions;
 
+  AppContext get appContext => widget.appContext;
+  UntisFetch get untis => appContext.untis;
+
   @override
   void initState() {
     super.initState();
@@ -57,7 +75,7 @@ class _MyHomePageState extends State<MyHomePage> {
   }
 
   Future<void> loadProfile() async {
-    final profile = Profile(selectedClasses: {19997, 20000});
+    final profile = await appContext.profile.firstNotNull;
     final classes = await untis.allClasses.fetched;
 
     appContext.profile.value = profile;
@@ -75,8 +93,7 @@ class _MyHomePageState extends State<MyHomePage> {
   Future<void> fetchTimetable() async {
     final date = appContext.date.value;
 
-    timetable = null;
-    setState(() {});
+    TimetableData? merged;
 
     final profile = await appContext.profile.firstNotNull;
     final selectedClasses = profile.selectedClasses;
@@ -86,14 +103,16 @@ class _MyHomePageState extends State<MyHomePage> {
     }));
 
     for (final fetched in allTimetables) {
-      if (timetable == null) {
-        timetable = fetched;
+      if (merged == null) {
+        merged = fetched;
       } else {
-        timetable = TimetableData.merge(timetable!, fetched);
+        merged = TimetableData.merge(merged, fetched);
       }
-
-      setState(() {});
     }
+
+    setState(() {
+      timetable = merged;
+    });
   }
 
   @override
diff --git a/lib/user/save_state.dart b/lib/user/save_state.dart
new file mode 100644
index 0000000000000000000000000000000000000000..0775c3b579e6fd1250cdf3de5b2e1925c888d105
--- /dev/null
+++ b/lib/user/save_state.dart
@@ -0,0 +1,56 @@
+import 'dart:async';
+import 'dart:convert';
+import 'dart:io';
+
+import 'package:path/path.dart' as path;
+import 'package:path_provider/path_provider.dart';
+
+import 'profile.dart';
+
+class SaveManager {
+  static const fileName = 'profile.json';
+
+  File? _saveFile;
+  FutureOr<File> get saveFile async => _saveFile ??= await _getSaveFile();
+
+  Profile? state;
+
+  Future<Directory> _getSaveDirectory() async {
+    if (Platform.isWindows) {
+      return await getApplicationCacheDirectory();
+    }
+    return await getApplicationDocumentsDirectory();
+  }
+
+  Future<File> _getSaveFile() async {
+    final directory = await _getSaveDirectory();
+    return File(path.join(directory.path, fileName));
+  }
+
+  Future<void> save() async {
+    final file = await saveFile;
+
+    final jsonString = jsonEncode(state!.toJson());
+    await file.writeAsString(jsonString);
+    print('Saved to ${file.path}');
+  }
+
+  Future<Profile> _load() async {
+    final file = await saveFile;
+
+    if (!await file.exists()) {
+      print('No profile saved, creating new one');
+      return Profile();
+    }
+
+    final fileContent = await file.readAsString();
+    final jsonContent = jsonDecode(fileContent);
+
+    print('Loading from ${file.path}');
+    return Profile.fromJson(jsonContent);
+  }
+
+  Future<Profile> load() async {
+    return state = await _load();
+  }
+}
diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift
index cccf817a52206e8a8eef501faed292993ff21a31..e777c67df2219fce2c33861bf98710f86bfc79b3 100644
--- a/macos/Flutter/GeneratedPluginRegistrant.swift
+++ b/macos/Flutter/GeneratedPluginRegistrant.swift
@@ -5,6 +5,8 @@
 import FlutterMacOS
 import Foundation
 
+import path_provider_foundation
 
 func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
+  PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
 }
diff --git a/pubspec.lock b/pubspec.lock
index b898505bdd017e792fa77b5bbd0d55e307180a22..d0aa88d36257b6b15e3b594503b4ea53ef4e10e4 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -57,6 +57,14 @@ packages:
       url: "https://pub.dev"
     source: hosted
     version: "1.3.1"
+  ffi:
+    dependency: transitive
+    description:
+      name: ffi
+      sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878"
+      url: "https://pub.dev"
+    source: hosted
+    version: "2.1.0"
   flutter:
     dependency: "direct main"
     description: flutter
@@ -108,13 +116,77 @@ packages:
     source: hosted
     version: "1.9.1"
   path:
-    dependency: transitive
+    dependency: "direct main"
     description:
       name: path
       sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917"
       url: "https://pub.dev"
     source: hosted
     version: "1.8.3"
+  path_provider:
+    dependency: "direct main"
+    description:
+      name: path_provider
+      sha256: a1aa8aaa2542a6bc57e381f132af822420216c80d4781f7aa085ca3229208aaa
+      url: "https://pub.dev"
+    source: hosted
+    version: "2.1.1"
+  path_provider_android:
+    dependency: transitive
+    description:
+      name: path_provider_android
+      sha256: e595b98692943b4881b219f0a9e3945118d3c16bd7e2813f98ec6e532d905f72
+      url: "https://pub.dev"
+    source: hosted
+    version: "2.2.1"
+  path_provider_foundation:
+    dependency: transitive
+    description:
+      name: path_provider_foundation
+      sha256: "19314d595120f82aca0ba62787d58dde2cc6b5df7d2f0daf72489e38d1b57f2d"
+      url: "https://pub.dev"
+    source: hosted
+    version: "2.3.1"
+  path_provider_linux:
+    dependency: transitive
+    description:
+      name: path_provider_linux
+      sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279
+      url: "https://pub.dev"
+    source: hosted
+    version: "2.2.1"
+  path_provider_platform_interface:
+    dependency: transitive
+    description:
+      name: path_provider_platform_interface
+      sha256: "94b1e0dd80970c1ce43d5d4e050a9918fce4f4a775e6142424c30a29a363265c"
+      url: "https://pub.dev"
+    source: hosted
+    version: "2.1.1"
+  path_provider_windows:
+    dependency: transitive
+    description:
+      name: path_provider_windows
+      sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170"
+      url: "https://pub.dev"
+    source: hosted
+    version: "2.2.1"
+  platform:
+    dependency: transitive
+    description:
+      name: platform
+      sha256: "0a279f0707af40c890e80b1e9df8bb761694c074ba7e1d4ab1bc4b728e200b59"
+      url: "https://pub.dev"
+    source: hosted
+    version: "3.1.3"
+  plugin_platform_interface:
+    dependency: transitive
+    description:
+      name: plugin_platform_interface
+      sha256: da3fdfeccc4d4ff2da8f8c556704c08f912542c5fb3cf2233ed75372384a034d
+      url: "https://pub.dev"
+    source: hosted
+    version: "2.1.6"
   sky_engine:
     dependency: transitive
     description: flutter
@@ -184,5 +256,22 @@ packages:
       url: "https://pub.dev"
     source: hosted
     version: "0.1.4-beta"
+  win32:
+    dependency: transitive
+    description:
+      name: win32
+      sha256: "350a11abd2d1d97e0cc7a28a81b781c08002aa2864d9e3f192ca0ffa18b06ed3"
+      url: "https://pub.dev"
+    source: hosted
+    version: "5.0.9"
+  xdg_directories:
+    dependency: transitive
+    description:
+      name: xdg_directories
+      sha256: "589ada45ba9e39405c198fe34eb0f607cddb2108527e658136120892beac46d2"
+      url: "https://pub.dev"
+    source: hosted
+    version: "1.0.3"
 sdks:
   dart: ">=3.1.0 <4.0.0"
+  flutter: ">=3.7.0"
diff --git a/pubspec.yaml b/pubspec.yaml
index e93129ea5b6054d55af147340a667e6a70b6c719..4772eba700e70331151dc5f8ede51364def94339 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -35,6 +35,8 @@ dependencies:
   # The following adds the Cupertino Icons font to your application.
   # Use with the CupertinoIcons class for iOS style icons.
   cupertino_icons: ^1.0.2
+  path_provider: ^2.1.1
+  path: ^1.8.3
 
 dev_dependencies:
   flutter_test: