Phaser 3 Save System: How to Save Game Progress in Phaser


Nothing stings like losing your high score because the page reloaded. This tutorial shows you exactly how to save game progress in Phaser, step by step, using the browser’s built-in localStorage — with a complete copy-paste SaveManager and a playable demo you can try right now. Every Phaser 3 beginner hits this wall: coins collected, score racked up, tab refreshed — and everything resets.

How This Guide Was Built

This guide is based on official documentation and community reports — the playable demo is provided so you can test the pattern yourself. Primary references include Mozilla’s localStorage reference and Web Storage API documentation, plus Phaser’s official save-and-load tutorial. Last verified: August 2026.

What It Does

What this save system does is persist the player’s score, high score, and settings across page reloads using localStorage, the browser key-value store whose data survives restarts according to Mozilla’s localStorage documentation. The playable demo saves on every point change, restores all data automatically on scene start, and includes a Clear Save button that resets everything to defaults.

  • Save score and high score
  • Save and restore settings (music, difficulty)
  • Load saved data on scene restart
  • Clear/reset save data with a button

How Do I Save Game Progress in Phaser?

You save game progress in Phaser by serializing your game state to JSON and writing it to localStorage, then parsing it back in create() on the next launch; data is stored as origin-scoped strings, so JSON.stringify and JSON.parse are required for objects, as described in Mozilla’s Web Storage API guide.

At a high level, the flow is straightforward: a SaveManager object wraps three operations — save(data), load(), and reset(). When the player earns points or toggles a setting, call save() with the updated state. When the scene boots, call load() in create() to restore it. reset() wipes the stored entry for a fresh start.

The key gotcha is that localStorage only accepts strings, so passing an object directly stores the useless "[object Object]" — always wrap saves in JSON.stringify and loads in JSON.parse, as Mozilla’s localStorage reference explains. Storage is also scoped per origin and synchronous, so keep payloads small and avoid writing on every frame. For larger data, consider compressing or chunking.

Phaser’s own official save-and-load tutorial demonstrates this exact pattern — save key, JSON.stringify/JSON.parse, and a working demo game — so use it as a second reference while you build.

Setting Up the Project

Setting up the project requires a minimal HTML page that loads the Phaser 3.60.0 library on jsDelivr and configures a game instance with a single scene. The scale configuration uses Phaser.Scale.FIT with Phaser.Scale.CENTER_BOTH so the canvas fits responsively.

Here is the boilerplate:

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>Phaser 3 Save System Demo</title>
  <script src="https://cdn.jsdelivr.net/npm/phaser@3.60.0/dist/phaser.min.js"></script>
</head>
<body>
  <script>
    const config = {
      type: Phaser.AUTO,
      scale: {
        mode: Phaser.Scale.FIT,
        autoCenter: Phaser.Scale.CENTER_BOTH,
        width: 800,
        height: 600
      },
      scene: {
        create: function() { /* scene code here */ }
      }
    };
    const game = new Phaser.Game(config);
  </script>
</body>
</html>

Building a SaveManager

Building a SaveManager means wrapping localStorage in a small object with save, load, and reset methods, mirroring the stringify/parse pattern in Phaser’s official save-and-load tutorial, with try/catch guards for blocked storage, a KEY constant for the storage key, and a defaults object for fallback data.

Here is the complete SaveManager:

const SaveManager = {
  KEY: 'phaser_save_system_demo',
  defaults: { score: 0, highScore: 0, settings: { musicOn: true, difficulty: 'normal' } },

  save(data) {
    try {
      localStorage.setItem(this.KEY, JSON.stringify(data));
    } catch (e) {
      console.warn('Save failed:', e.message);
    }
  },

  load() {
    try {
      const raw = localStorage.getItem(this.KEY);
      return raw ? { ...this.defaults, ...JSON.parse(raw) } : { ...this.defaults };
    } catch (e) {
      console.warn('Load failed:', e.message);
      return { ...this.defaults };
    }
  },

  reset() {
    localStorage.removeItem(this.KEY);
    return { ...this.defaults };
  }
};

The save method serializes the data object with JSON.stringify and writes it to localStorage under the KEY. The load method reads the raw string, parses it back into an object, and merges it with defaults using the spread operator for forward compatibility — if you add a field later, old saves won’t break. Both methods wrap their operations in try/catch because blocked storage (private browsing, disabled cookies) throws a SecurityError, per Mozilla’s localStorage documentation. The reset method removes the stored entry entirely and returns a fresh copy of defaults.

Wiring SaveManager into Your Game

Wiring SaveManager into your game means calling load() in the scene’s create() method to restore state, calling save() whenever the score changes, and calling reset() from a “Clear Save” button; per the Phaser DataManager API documentation, you can also track values as scene data before serializing.

Here is the integration code:

class BootScene extends Phaser.Scene {
  create() {
    // Restore saved data on startup
    this.gameData = SaveManager.load();
    this.score = this.gameData.score;
    this.highScore = this.gameData.highScore;

    // Example: update score and persist
    this.collectItem = function() {
      this.score += 10;
      if (this.score > this.highScore) {
        this.highScore = this.score;
      }
      SaveManager.save({
        score: this.score,
        highScore: this.highScore,
        settings: this.gameData.settings
      });
    };

    // Example: clear save button
    this.input.on('pointerdown', () => {
      this.gameData = SaveManager.reset();
      this.score = this.gameData.score;
      this.highScore = this.gameData.highScore;
    });
  }
}

In create(), SaveManager.load() returns the saved state or defaults, and you store it on the scene instance. When the player collects an item, you increment the score, update highScore if needed, and call SaveManager.save() with the full updated object. The “Clear Save” button calls SaveManager.reset(), which wipes the stored data and returns fresh defaults.

Testing and Troubleshooting

Testing and troubleshooting a Phaser save system centers on DevTools’ Application panel, where you can inspect stored values per origin — the same inspection workflow documented in Mozilla’s Web Storage API guide. Reload the page and confirm the KEY entry with its JSON string persists; if it doesn’t, check the console for warnings from the try/catch blocks.

Common pitfalls include forgetting JSON.stringify (you’ll load back "[object Object]" and parsing fails), and exceeding the per-origin quota — typically around 5 MB per the Mozilla Web Storage API overview — which throws a QuotaExceededError. Private browsing and disabled cookies raise SecurityError, which is why every SaveManager call sits in try/catch. And remember that the storage event fires in other tabs, not the one writing the data, so don’t rely on it for same-tab updates.

FAQ

Does localStorage work in all browsers?

Yes, localStorage works in all modern browsers, including Chrome, Firefox, Safari, and Edge, according to the compatibility table in Mozilla’s localStorage documentation. Each origin gets roughly 5 MB of string storage shared across that site’s pages, so objects must be serialized with JSON.stringify before saving and parsed on load.

Can I save Phaser game objects directly?

No, you cannot save Phaser game objects directly to localStorage. You must serialize your data to plain JSON first using JSON.stringify, then parse it back with JSON.parse on load. Phaser’s DataManager API documentation describes an in-memory intermediary that organizes key-value pairs per scene before you serialize the whole structure to storage.

What happens if localStorage is blocked?

If localStorage is blocked — for example, by private browsing mode or disabled cookies — the browser throws a SecurityError on access, as described in Mozilla’s Web Storage API guide. Wrap every call in try/catch and fall back to default values or in-memory storage so the game keeps running instead of crashing.

Where to Go Next

Where to go next is deeper into Phaser and browser storage: the Phaser GitHub repository hosts the engine source and examples for further study, while our Phaser 3 platformer tutorial builds on these fundamentals with physics and level design, and the hubs below collect more playable demos and framework guides.