PlayerPrefs vs JSON in Unity: What to Save Where

Unity gives you PlayerPrefs out of the box, and it’s tempting to save everything with it: settings, high scores, unlocked levels, inventory, the player’s position. Then the save grows, the code becomes a wall of string keys, and you learn that PlayerPrefs on Windows lives in the registry. On the other side, writing a full JSON save system for a volume slider is overkill. This guide explains how PlayerPrefs vs JSON saving actually works in Unity 6, what each is good for, where the data ends up on every platform, and a simple rule for deciding what goes where.

The Short Answer

Use PlayerPrefs for small, per-device preferences: volume, graphics quality, language, control sensitivity, “has seen the tutorial”. Use a JSON file (or another serialized file) for game progress: levels, inventory, stats, quests, save slots, anything structured or anything the player would be upset to lose. The name is a clue: PlayerPrefs is for player preferences.

How PlayerPrefs Works

PlayerPrefs is a key-value store with three types: int, float and string.

// write
PlayerPrefs.SetInt("QualityLevel", 2);
PlayerPrefs.SetFloat("MusicVolume", 0.8f);
PlayerPrefs.SetString("Language", "en");
PlayerPrefs.Save(); // write to disk now

// read, with defaults if the key doesn't exist
int quality = PlayerPrefs.GetInt("QualityLevel", 1);
float music = PlayerPrefs.GetFloat("MusicVolume", 0.8f);

// check and delete
if (PlayerPrefs.HasKey("Language")) { /* ... */ }
PlayerPrefs.DeleteKey("Language");
PlayerPrefs.DeleteAll(); // careful: everything for this game

There’s no bool; use SetInt(key, value ? 1 : 0). Unity keeps the values in memory and writes them to disk when you call Save() and when the application quits normally. If the game crashes or is killed (common on mobile when the OS closes a background app), unsaved changes are lost. Call Save() after important changes, but not every frame, because it writes to disk.

Where PlayerPrefs Is Stored on Each Platform

Platform Location
Windows (build) Registry: HKEY_CURRENT_USER\Software\<Company>\<Product>
Windows (Editor) Registry: HKEY_CURRENT_USER\Software\Unity\UnityEditor\<Company>\<Product>
macOS ~/Library/Preferences/unity.<Company>.<Product>.plist
Linux ~/.config/unity3d/<Company>/<Product>
Android App’s SharedPreferences XML (private app storage)
iOS NSUserDefaults (app’s preferences plist)
Web (WebGL) Browser IndexedDB, per site

Company and Product come from Player Settings. Change either one and the game looks in a new place, so existing preferences “disappear”. Also note that the Editor and your builds use separate storage on Windows, which is why a value you set while testing in the Editor isn’t there in the build.

PlayerPrefs Limits and Gotchas

  • Only three types. Lists, dictionaries and nested data have to be flattened into many keys or squeezed into a string.
  • Size. On WebGL, PlayerPrefs is limited to about 1 MB. On Windows, large values in the registry are slow and frowned upon.
  • No structure or versioning. There’s no easy way to list all keys, migrate an old format, or keep several save slots without inventing your own naming scheme.
  • Hard to back up and share. Players can’t copy a registry entry the way they can copy a save file, and Steam Cloud’s Auto-Cloud works with files, not the registry.
  • Typos fail silently. GetInt("Higscore") just returns the default. Keep keys as constants.
  • Plain text. Values are trivially editable (see the security section).

How a JSON Save File Works

A JSON save turns a C# class into text and writes it to a file in Application.persistentDataPath, a folder Unity guarantees is writable on every platform:

using System.IO;
using UnityEngine;

[System.Serializable]
public class SaveData
{
    public int version = 1;
    public int level;
    public int coins;
    public string[] unlockedSkins;
}

public static class SaveFile
{
    static string PathFor(int slot) => Path.Combine(Application.persistentDataPath, $"save{slot}.json");

    public static void Write(SaveData data, int slot = 0)
    {
        string json = JsonUtility.ToJson(data, true);
        string path = PathFor(slot);
        string temp = path + ".tmp";
        File.WriteAllText(temp, json);   // write to a temp file first...
        if (File.Exists(path)) File.Delete(path);
        File.Move(temp, path);           // ...then swap, so a crash can't corrupt the save
    }

    public static SaveData Read(int slot = 0)
    {
        string path = PathFor(slot);
        return File.Exists(path) ? JsonUtility.FromJson<SaveData>(File.ReadAllText(path)) : new SaveData();
    }
}

You get structured data, save slots (one file each), a version field to migrate old saves, files players can back up, and something Steam Cloud can sync. JsonUtility doesn’t handle dictionaries or properties; Newtonsoft JSON (the com.unity.nuget.newtonsoft-json package) does. Our full JSON save system tutorial covers slots, dictionaries, WebGL and autosaves in depth.

Side-by-Side Comparison

PlayerPrefs JSON file
Setup None A data class and a few lines of file code
Data types int, float, string Any serializable class, lists, nested data
Save slots Awkward (key prefixes) Natural (one file per slot)
Versioning and migration Manual and messy A version field in the data
Size Small (about 1 MB limit on WebGL) Practically unlimited
Player backup / cloud sync Difficult Easy (copy the file; Steam Auto-Cloud)
Crash safety Lost if not saved before crash As safe as your write strategy (temp file and swap)
Readability Registry or plist Human-readable text
Best for Settings, flags, last-used options Progress, inventory, stats, anything structured

What Goes Where: a Simple Rule

Ask: “If this device’s data were wiped, would the player lose progress, or just have to re-pick settings?”

  • PlayerPrefs: master, music and SFX volume (see our Audio Mixer volume guide), resolution and quality, language, mouse sensitivity, invert Y, subtitle size, last selected save slot, “show tutorial” flags, accepted privacy prompt.
  • JSON file: current level and checkpoint, inventory, currencies, unlocked content, quest state, character stats, play time, per-slot data, high-score tables with names and dates.

A useful refinement: device-specific settings (resolution, graphics quality) belong in PlayerPrefs even on PC, because they shouldn’t follow a cloud save onto a different computer with a different monitor. Anything that should follow the player across devices goes in the save file.

💾 Want a save system that just works?

Easy Save and similar Asset Store tools handle serialization, encryption, slots and cloud-friendly files for you. They’re on our list of tools worth buying.

See the best Unity Asset Store tools →

A Tidy PlayerPrefs Wrapper for Settings

Scattered string keys are where PlayerPrefs code goes wrong. Wrap them in one static class with typed properties:

using UnityEngine;

public static class Settings
{
    const string MusicKey = "settings.musicVolume";
    const string SensitivityKey = "settings.sensitivity";
    const string InvertYKey = "settings.invertY";
    const string LanguageKey = "settings.language";

    public static float MusicVolume
    {
        get => PlayerPrefs.GetFloat(MusicKey, 0.8f);
        set => PlayerPrefs.SetFloat(MusicKey, Mathf.Clamp01(value));
    }

    public static float Sensitivity
    {
        get => PlayerPrefs.GetFloat(SensitivityKey, 1f);
        set => PlayerPrefs.SetFloat(SensitivityKey, value);
    }

    public static bool InvertY
    {
        get => PlayerPrefs.GetInt(InvertYKey, 0) == 1;
        set => PlayerPrefs.SetInt(InvertYKey, value ? 1 : 0);
    }

    public static string Language
    {
        get => PlayerPrefs.GetString(LanguageKey, "en");
        set => PlayerPrefs.SetString(LanguageKey, value);
    }

    public static void Save() => PlayerPrefs.Save();
}

Now the rest of the code reads Settings.InvertY and can’t misspell a key. Defaults live in one place, the settings. prefix keeps keys grouped, and you could switch storage later without touching callers. Call Settings.Save() when the player closes the settings menu.

Cheating, Encryption and Cloud Saves

Neither PlayerPrefs nor a plain JSON file is secure. Registry editors and text editors can change coins and unlocks in seconds. For most single-player games that’s fine: players cheating in their own offline game hurt nobody. If it matters (in-app purchases, leaderboards, competitive progression):

  • Obfuscation (encoding or encrypting the save) stops casual editing but not a determined player, because the key ships with the game.
  • A checksum (a hash of the data plus a secret) detects tampering, so you can reset or flag edited saves.
  • Server-side storage is the only real protection. Purchases and competitive data should be validated on a server, such as Unity Cloud Save or your own backend.

For cross-device progress, a JSON file is also the natural thing to upload: Steam Auto-Cloud syncs files from the save folder, and cloud save services store strings or blobs that a JSON document fits into. Publishing on Steam? Our Steam and itch.io publishing guide covers Auto-Cloud setup.

Troubleshooting

PlayerPrefs values are lost after a crash or force close

They’re only written on Save() and on a normal quit. Call PlayerPrefs.Save() after important changes, and in OnApplicationPause(true) on mobile, where apps are often killed in the background.

PlayerPrefs set in the Editor aren’t in the build

On Windows the Editor and builds use different registry keys. This is expected; test settings in the build itself.

All saved settings disappeared after an update

Company Name or Product Name changed in Player Settings, which changes the storage location. Change them back, or migrate old keys on first launch.

PlayerPrefs not saving on WebGL

The browser may block storage (private mode, cleared site data), you’re over the roughly 1 MB limit, or the page reloaded before data was flushed. Call PlayerPrefs.Save() after changes and keep WebGL saves small.

How do I clear PlayerPrefs while testing?

Use Edit > Clear All PlayerPrefs in the Editor, or call PlayerPrefs.DeleteAll() from a debug button. Don’t ship that button.

JSON save loads with default values

Fields must be public or [SerializeField], the class must be [System.Serializable], and JsonUtility ignores properties and dictionaries. Check the file contents in persistentDataPath to see what was actually written.

FAQ

Should I use PlayerPrefs to save game progress?

Only for very small games. PlayerPrefs is meant for settings. Structured progress such as levels, inventory and stats is easier and safer in a JSON save file.

Where does Unity store PlayerPrefs on Windows?

In the registry under HKEY_CURRENT_USER\Software\<Company>\<Product> for builds. The Editor uses a separate key under Software\Unity\UnityEditor.

Is PlayerPrefs secure?

No. Values are stored as plain data that players can edit. Use server-side validation for anything that must not be cheated, such as purchases.

How much data can PlayerPrefs store?

There’s no strict limit on most platforms, but WebGL caps it at about 1 MB, and storing large data in the Windows registry is a bad idea. Keep it to small settings.

Do I need to call PlayerPrefs.Save?

Unity saves on a normal quit, but crashes and mobile background kills skip that. Calling Save after important changes makes sure the data is written.

Can PlayerPrefs store a bool or a list?

Not directly. Store a bool as an int (0 or 1). For lists or objects, serialize them to a JSON string, or better, use a JSON save file.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *