Supabase Cloud Saving

Crystal Save integrates directly with Supabase Storage to store save files, metadata, and screenshots in the cloud. The integration supports public buckets, per-device folders, Unity Authentication, and custom JWT-based authorization with Row-Level Security (RLS).

1. Prepare Your Supabase Project

  1. Create a Supabase Project in the Supabase Dashboard.

  2. Open “Explore Storage” in the Storage section.

  3. Create a bucket (e.g. game-saves).

  4. Enable Row-Level Security (RLS) for the bucket by adding policies under storage.objects:

    • Read (SELECT)

    • Insert (upload)

    • Update (overwrite)

    • Delete (optional)

Example shared public bucket rule:

bucket_id = 'game-saves'

Example per-user private folder rules:

-- Allow only the authenticated user to read/write their own saves
bucket_id = 'game-saves'
AND name LIKE ('users/' || auth.uid() || '/%')

(See “Supabase User ID Strategy” below for more.)

2. Configure Crystal Save Settings

  1. Create or open a SaveSettings asset: Tools → Crystal Save → Settings → Create Save Settings File

  2. Enable Cloud Save.

  3. Set Save Backend to Supabase.

  4. Fill in:

    • Supabase URL (from your project dashboard)

    • Supabase Anon Key

    • Storage Bucket name (e.g. game-saves)

  5. Choose a User Folder Strategy:

    • Shared – one global folder (users/guest/...).

    • PublicPerBuild – shared folder but different per release build.

    • GuidPerDevice – client generates a GUID and uses users/{guid}.

    • UnityAuthentication – folder from Unity Authentication auth.uid().

    • Custom – fully custom folder path & token (see below).


3. Using a Custom Folder & Auth Resolver

If using the Custom strategy:

  • Assign a CustomFolderAuthResolver (or your own IUserAuthorizationResolver) to customUserFolderResolver in SaveSettings.

  • The resolver:

    • Persists a userId and optional JWT token.

    • Implements:

      string ResolveUserFolder(); // e.g. "users/{uid}"
      string ResolveAccessKey();  // JWT or anon key

Fires SupabaseAuthRelay.FireLoggedIn(userId) automatically when cached credentials exist.

Call:

resolver.SetUserId(uid);
resolver.SetToken(jwt);

after login to store credentials.


4. Handling Authentication

Crystal Save ships with SupabaseRestAuth helper methods for:

  • Sign-up (SignUpWithEmailPassword)

  • Sign-in (SignInWithPassword)

  • Send magic link (SendMagicLink)

These methods:

  • POST to Supabase Auth REST endpoints.

  • Return jwt (access token) and uid (user ID).

  • Store them in your resolver to enable authenticated storage.

Example sign-in flow:

StartCoroutine(SupabaseRestAuth.SignInWithPassword(
    supabaseUrl, supabaseAnonKey,
    email, password,
    (jwt, uid) => {
        resolver.SetUserId(uid);
        resolver.SetToken(jwt);
        SupabaseAuthRelay.FireLoggedIn(uid);
    },
    error => Debug.LogError(error)
));

5. Waiting for Login Before Saving

If you require login before any cloud save:

await SaveManager.Instance.WaitForSupabaseLoginAsync();

This waits until SupabaseAuthRelay reports a login or times out.


6. Saving & Loading with Supabase

Once logged in:

  • Save:

    await SaveManager.Instance.SaveAsync(slotNumber);
  • Load:

await SaveManager.Instance.LoadAsync(slotNumber);

Behind the scenes:

  • Files and metadata are uploaded to bucket/users/{uid}/...

  • Authorization header is set from:

    • Resolver’s JWT (custom strategy)

    • Anon key (if no JWT)


7. Local Mirrors & Offline Play

If Keep Local Mirror is enabled:

  • SupabaseSaveSystem writes .sav and .meta files locally.

  • Allows offline play and syncs when back online.


8. Supabase User ID Strategies & RLS Summary

Strategy
Folder Example
RLS Policy Example

Shared

users/guest/...

bucket_id = 'game-saves'

PublicPerBuild

users/{buildId}/...

Same as Shared

GuidPerDevice

users/{guid}/...

Same as Shared (public bucket)

UnityAuthentication

users/{auth.uid()}/...

`bucket_id='game-saves' AND name LIKE ('users/'

Custom

Depends on resolver

Match resolver path in RLS

9. Logout

SaveManager.Instance.LogoutFromSupabase();
  • Clears resolver credentials.

  • Switches back to guest/public access if possible.


10. Tips & Best Practices

  • Use GuidPerDevice for quick per-device separation without full auth.

  • Use UnityAuthentication or Custom+JWT for secure per-user isolation.

  • Keep your anon key private (even though it’s public by design).

  • Use RLS to enforce folder isolation on the server side.

Last updated