diff --git a/src/App.svelte b/src/App.svelte
index 0b2c2045db435edcfafd059410bdfbe85d0a5817..f32688988736bcd4dacce89da0e1c8875d561d87 100644
--- a/src/App.svelte
+++ b/src/App.svelte
@@ -3,7 +3,9 @@
   import { fetchDialogSet } from "./utils";
   import Debugger from "./Debugger.svelte";
   import type { Dialog } from "./types";
-import SavingComponent from "./SavingComponent.svelte";
+  import SavingComponent from "./SavingComponent.svelte";
+
+  const gameStateServerBaseUrl = "http://localhost:4000/";
 
   const dialogSetPromise = fetchDialogSet();
   let currentDialog: Dialog;
@@ -15,7 +17,7 @@ import SavingComponent from "./SavingComponent.svelte";
 
     <br />
     <Debugger {dialogSet} bind:currentDialog />
-    <SavingComponent/>
+    <SavingComponent {gameStateServerBaseUrl} />
   {:catch _error}
     <h3>Oh no :(</h3>
     <p>
diff --git a/src/SavingComponent.svelte b/src/SavingComponent.svelte
index b1aee2c817e7ec9a04ed04890538d4bda18383ec..0af4a6d712238dbbfd77e1f7fa5a587e8eb8977a 100644
--- a/src/SavingComponent.svelte
+++ b/src/SavingComponent.svelte
@@ -1,15 +1,48 @@
+<script lang="ts">
+  import { identity } from "svelte/internal";
+  import { gameFactsStore } from "./gameFacts";
+
+  import { GameStateServerClient } from "./gameStateServerClient";
+  import type { GameState } from "./types";
+
+  export let gameStateServerBaseUrl: string;
+  $: gameStateServerClient = new GameStateServerClient(gameStateServerBaseUrl);
+  let pretixOrderCode: string = "XXXYYY";
+
+  async function handleSaveGameState(): Promise<void> {
+    const gameState: GameState = {
+      gameFacts: $gameFactsStore,
+    };
+    gameStateServerClient.saveState(pretixOrderCode, gameState);
+  }
+
+  async function handleLoadGameState(): Promise<void> {
+    const newGameState = await gameStateServerClient.loadState(pretixOrderCode);
+    $gameFactsStore = newGameState.gameFacts;
+  }
+</script>
+
 <div>
-    <input type="text" placeholder="Your Pretix order code" maxlength="5" minlength="5" >
-    <button style="width: 25rem;"
-        >💾 Save game (not implemented yet)</button
-      >
+  <input
+    type="text"
+    placeholder="Your Pretix order code"
+    maxlength="5"
+    minlength="5"
+    bind:value={pretixOrderCode}
+  />
+  <button style="width: 10rem;" on:click|preventDefault={handleSaveGameState}>
+    💾 Save game
+  </button>
+  <button style="width: 10rem;" on:click|preventDefault={handleLoadGameState}>
+    ▶️ Load game
+  </button>
 </div>
+
 <style>
-    div {
-      border: 1px solid;
-      padding: 10px;
-      padding-bottom: 0px;
-      margin: 5px;
-    }
-  </style>
-  
\ No newline at end of file
+  div {
+    border: 1px solid;
+    padding: 10px;
+    padding-bottom: 0px;
+    margin: 5px;
+  }
+</style>
diff --git a/src/gameStateServerClient.ts b/src/gameStateServerClient.ts
new file mode 100644
index 0000000000000000000000000000000000000000..cd93d70f0df47ca024bdb6c5141479cfb5b2b280
--- /dev/null
+++ b/src/gameStateServerClient.ts
@@ -0,0 +1,48 @@
+import { GameState, isValidGameState } from "./types";
+
+export class GameStateServerClient {
+  _apiBaseUrl: string;
+  constructor(apiBaseUrl: string) {
+    this._apiBaseUrl = apiBaseUrl;
+  }
+
+  async saveState(id: string, gameState: GameState): Promise<boolean> {
+    try {
+      await fetch(this._apiBaseUrl + "/game/state/" + id, {
+        method: "POST",
+        mode: "cors",
+        cache: "no-cache",
+        credentials: "omit",
+        headers: {
+          "Content-Type": "application/json",
+        },
+        referrerPolicy: "no-referrer",
+        body: JSON.stringify(gameState),
+      });
+      return true;
+    } catch (saveStateToServerError) {
+      console.error(
+        "Failed to save state to server. The following info may help to solve this problem:",
+        saveStateToServerError
+      );
+      return false;
+    }
+  }
+
+  async loadState(id: string): Promise<GameState> {
+    try {
+      const rawResponse = await fetch(this._apiBaseUrl + "/game/state/" + id, {
+        method: "GET",
+        mode: "cors",
+        cache: "no-cache",
+        credentials: "omit",
+        referrerPolicy: "no-referrer",
+      });
+      const decodedResponse = await rawResponse.json();
+      if (!isValidGameState(decodedResponse)) {
+        throw new TypeError("State from server is not a valid gameState");
+      }
+      return decodedResponse;
+    } catch (loadGameStateFromServerError) {}
+  }
+}
diff --git a/src/types.ts b/src/types.ts
index 3a82a881598582e3292dd12b90afacafd0b2fdaf..fc40a9a63f4ef38b225b2606533555f610ba53ee 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -97,3 +97,16 @@ export interface DialogOption {
    */
   forbiddenFacts?: String[];
 }
+
+export interface GameState {
+  gameFacts: String[];
+}
+
+export function isValidGameState(input: unknown): input is GameState {
+  if (typeof input !== "object") return false;
+  return (
+    input["gameFacts"] !== undefined &&
+    Array.isArray(input["gameFacts"]) &&
+    input["gameFacts"].every((gameFact) => typeof gameFact === "string")
+  );
+}