Quick answer: Local Storage plugin is async — use On Item Set events for sequencing. iOS Safari private mode and some in-app browsers block storage silently. Detect failures, cache in memory as fallback, and prompt users to exit private mode.
Here is how to fix Construct 3 Local Storage not saving on mobile. Your game saves high scores via Local Storage. Desktop and Android: works. iPhone user reports scores reset every launch. Or the user launches from an Instagram link and progress does not persist. Mobile browsers have storage quirks that Construct 3’s Local Storage plugin inherits.
The Symptom
Local Storage Set Item appears to succeed but values do not persist across sessions on mobile. On iOS specifically, or in in-app browser views. Same project works on desktop and some Android browsers.
What Causes This
Private browsing mode. iOS Safari in private mode blocks persistent storage. Set Item appears to work (no error) but data is wiped when the tab closes. Users often do not know they’re in private mode.
In-app browser restrictions. Instagram, Twitter, Discord in-app browsers have various storage restrictions. Some isolate storage per tab, some block entirely.
Async events missed. The plugin is async. Calling Set Item then immediately reading values via Get Item before On Item Set fires returns old values.
Storage quota exceeded. IndexedDB (the backend) has ~50 MB quota on mobile. Large saves silently fail when over quota.
Cookies/site data cleared. Mobile browsers periodically clear site data, especially for sites visited infrequently. Long-term saves require prompting users to add to home screen or install as PWA.
The Fix
Step 1: Use On Item Set for sequencing.
Save Button on clicked:
LocalStorage: Set "score" to score_value
// Do NOT immediately read the value
LocalStorage on item "score" set:
// Confirmed save, show success
Text: Set text to "Saved!"
LocalStorage on error "score":
// Fallback path
Text: Set text to "Save failed (private mode?)"
On Item Set fires after IndexedDB commits. On Error fires if the browser refused. Handle both.
Step 2: Detect private mode. When Local Storage writes fail repeatedly, assume private mode and show a message:
LocalStorage on error (*):
Add 1 to save_failure_count
If save_failure_count >= 2:
Dialog: Show "Saves may not work. If using private browsing, disable it and refresh."
Two failures is a strong signal something is blocked. Inform the user rather than silently losing data.
Step 3: In-memory fallback. Cache data in Construct global variables during play. Attempt save regularly. If save succeeds later, in-memory and persistent match. If it never succeeds, the session’s progress at least works until the tab closes.
Every 30 seconds:
LocalStorage: Set "progress" to global.progress
// Plus save on major events (level complete, boss defeat)
Step 4: Recommend install / add to home screen. PWA or home screen shortcuts give more persistent storage on mobile. After first save, prompt:
Dialog: "Add this game to your home screen to save progress permanently."
// iOS: user must do via Safari share menu
// Android: beforeinstallprompt event, use Browser plugin
Platform Detection
Detect mobile via the Platform Info plugin. Adjust behavior per platform:
System: On start of layout:
If PlatformInfo.OS = "iOS":
// Warn about private mode, suggest home screen
Else If PlatformInfo.IsWebView:
// In-app browser, caution user
“Mobile storage is a best-effort system. Detect failures, fallback in memory, and educate users when platform limits bite.”
Related Issues
For Construct 3 collision issues, see Construct 3 Collision Not Detecting. For function patterns, Construct 3 Function Parameters Not Passed Correctly.
On Item Set for async. On Error for detection. In-memory fallback. Prompt install for persistence.