Project URL and anon/publishable key live in the runtime settings asset and are safe for client builds when RLS is configured correctly.
Before you start
ShipIt is designed for teams that want Supabase as their game backend while keeping the Unity workflow practical. The runtime talks to your Supabase project directly; editor-only tools use privileged keys only when you explicitly configure them.
Secret or legacy service-role keys are stored locally for schema browsing, SQL provisioning, migrations and AI context generation.
Scores, economy, entitlements and moderation actions should be validated by Postgres functions, Edge Functions or service-role workflows.
Install & configure
- Import the package (Package Manager → My Assets), then open
Tools → ShipIt → Supabase Toolkit. - Open Setup Wizard and enter your Project URL plus anon/publishable key (Supabase dashboard → Project Settings → API Keys).
- Add a Secret key or legacy service-role key for schema and admin tools. It stays in machine-local editor preferences and is never written to runtime assets or builds.
- Click Test & save. It validates the connection and writes the settings asset to a
Resourcesfolder so it loads at runtime. Test only checks the connection without saving.
Initialize
using ShipIt.Supabase;
void Awake()
{
SupabaseClient.InitializeFromResources();
}
Everything hangs off SupabaseClient.Instance:
.Auth, .Database, .Realtime, .Storage,
.Functions, .Rpc, plus the .Http layer the feature services use.
Common workflows
| Goal | Use these tabs | Result |
|---|---|---|
| Connect a new project | Setup Wizard, Environments, Diagnostics | Runtime settings asset plus editor-local admin credentials. |
| Generate safe C# models | Schema Browser, Code Generator, AI Assistant | Serializable model classes and typed query snippets based on your schema. |
| Add a game backend system | SQL Packs, Provision, RLS Simulator | Tables, RLS policies, helper functions and usage notes for the selected feature. |
| Debug live network behavior | Request Inspector, Realtime Monitor, Error Explainer | Readable requests, responses, channel state and actionable troubleshooting hints. |
| Prepare launch operations | Live-Ops Dashboard, Remote Config, Feature Flags | Config, announcements, rewards and moderation flows without a new client build. |
Authentication
var auth = SupabaseClient.Instance.Auth;
await auth.SignUpWithEmailAsync(email, password);
var session = await auth.SignInWithEmailAsync(email, password);
var oauthUrl = auth.BuildOAuthSignInUrl("google", "mygame://auth");
Application.OpenURL(oauthUrl);
var oauthSession = await auth.HandleOAuthCallbackAsync(callbackUrl);
var nativeSession = await auth.SignInWithIdTokenAsync("apple", identityToken, nonce: rawNonce);
await auth.SendMagicLinkAsync(email);
await auth.SendPhoneOtpAsync("+14155551234");
await auth.VerifyPhoneOtpAsync("+14155551234", "123456");
var user = auth.CurrentSession?.User;
await auth.SendPasswordResetAsync(email);
await auth.SignOutAsync();
string export = await auth.ExportMyDataAsync();
await auth.DeleteAccountAsync();
Browser OAuth uses PKCE and Unity deep links. The drop-in Auth screen completes callbacks automatically. Auth also supports CAPTCHA-protected flows, MFA (TOTP), native identity linking and unlinking, reauthentication, resilient token refresh, and session persistence.
Database
A typed query builder with lambda filters, ordering, pagination, full-text search and resource embedding.
var db = SupabaseClient.Instance.Database;
var top = await db.From<Player>("players")
.Where(p => p.score > 1000)
.OrderByDescending(p => p.score)
.Limit(50)
.ExecuteAsync();
var posts = await db.From<Post>("posts")
.TextSearch("body", "dragon OR wizard")
.ExecuteAsync();
var active = await db.From<Player>("players")
.WhereNot("status", FilterOperator.Equals, "banned")
.Or("tier.eq.gold,tier.eq.platinum")
.ExecuteAsync();
var members = await db.From<GuildMemberRow>("guild_members")
.Select("role", "guilds(name,tag)")
.ExecuteAsync();
.Where(...)
filter — the toolkit refuses to mutate the whole table by accident.Realtime
await SupabaseClient.Instance.Realtime.ConnectAsync();
var channel = SupabaseClient.Instance.Realtime.FromTable("players");
channel.OnInsert<Player>(p => Debug.Log($"New: {p.name}"));
await channel.SubscribeAsync();
var room = SupabaseClient.Instance.Realtime.Channel("room:42");
room.OnBroadcast("move", json => HandleMove(json));
await room.SubscribeAsync();
await room.SendBroadcastAsync("move", new { x = 1, y = 2 });
var priv = SupabaseClient.Instance.Realtime.Channel("room:42", isPrivate: true);
await priv.SubscribeAsync();
The client reconnects with bounded exponential backoff, rejoins known channels and pushes refreshed session tokens to private channels automatically. WebGL uses the browser's native WebSocket.
Storage
var bucket = SupabaseClient.Instance.Storage.FromBucket("avatars");
await bucket.UploadFileAsync("user/avatar.png", localPath, upsert: true);
var files = await bucket.ListAsync("user", new StorageListOptions {
Search = "avatar",
Limit = 50
});
Texture2D tex = await bucket.DownloadTextureAsync("user/avatar.png");
Sprite icon = await bucket.DownloadSpriteAsync("user/avatar.png");
AudioClip sfx = await bucket.DownloadAudioClipAsync("sfx/hit.mp3", AudioType.MPEG);
string thumb = bucket.GetPublicTransformUrl("user/avatar.png",
new ImageTransform(128, 128, ImageResize.Cover, 80));
await bucket.UploadResumableAsync("clips/replay.bin", bytes,
onProgress: (sent, total) => Debug.Log($"{sent}/{total}"));
TUS upload locations are persisted locally. Repeating the same upload after an interruption probes the server offset and continues from the confirmed byte.
Edge Functions & RPC
var result = await SupabaseClient.Instance.Functions
.InvokeAsync<MyResponse>("hello-world", new { name = "Unity" });
var rows = await SupabaseClient.Instance.Rpc
.CallListAsync<LeaderboardRow>("get_leaderboard", new { limit_n = 10 });
The Edge Function gallery ships ready-to-deploy templates: GDPR account (export/delete), FCM push, IAP receipt validation, anti-cheat score, AI NPC streaming proxy, and AI content moderation.
Backend packs
38 drop-in, RLS-secured SQL recipes. Each creates tables, policies and helper functions for a common game system. Browse them in the Backend Packs tab; every pack lists exactly what it creates and how to use it.
| Area | Packs |
|---|---|
| Players | Profiles, Cloud Saves, Cloud Save Versioning & History |
| Progression | Leaderboard, Anti-Cheat Scores, Achievements, Daily Rewards, Quests, Seasons & Battle Pass |
| Economy | Inventory & Economy, Economy: Ledger & Trading, Auction House / Marketplace, In-App Purchases |
| Social | Friends, Guilds/Clans, Mailbox/Gifts, Moderation (reports & blocks), Referrals / Invite Codes |
| Multiplayer | Lobbies & Matchmaking, Ranked Matchmaking (ELO), Tournaments & Brackets, Realtime Authorization (private channels) |
| Live-ops | Remote Config, A/B Experiments, Localization, Announcements, Push Notifications, Seasons & scheduled jobs, Webhooks / Event Outbox |
| AI | Semantic Search (pgvector) |
| Security | Realtime Authorization (private channels), Rate Limiting / Abuse Guard, Idempotency Keys, Audit Log / Admin Action Trail |
Provisioning
Three ways to apply a pack to your project:
- Copy SQL from the Backend Packs tab into the Supabase SQL editor.
- One-click Provision tab — tick the packs you need and apply them (uses your editor-only service_role key; idempotent).
- MCP — let an AI agent apply packs for you (see AI & agents).
Leaderboards
var lb = new LeaderboardService(settings, SupabaseClient.Instance.Http);
await lb.SubmitAsync("Aria", 12345);
var top10 = await lb.GetTopAsync(10);
int rank = await lb.GetRankAsync(12345);
long total = await lb.GetCountAsync();
For tamper-proof boards use SecureLeaderboardService + the Anti-Cheat
Scores pack (server-validated range, ceiling and rate limit).
Economy & trading
Server-authoritative and auditable — built on a double-entry currency ledger. Apply the Inventory & Economy and Economy: Ledger & Trading packs.
var econ = new EconomyService(settings, SupabaseClient.Instance.Http);
long bal = await econ.AdjustCurrencyAsync("gold", 500, "quest_reward");
await econ.TransferCurrencyAsync(toUserId, "gold", 100);
var offer = new TradeBundle().Currency("gold", 50).Item("sword", 1);
var request = new TradeBundle().Item("shield", 2);
var tradeId = await econ.CreateTradeAsync(toUserId, offer, request);
await econ.AcceptTradeAsync(tradeId);
Seasons / live-ops automation
var seasons = new SeasonService(settings, SupabaseClient.Instance.Http);
var current = await seasons.GetCurrentSeasonAsync("leaderboard");
var mine = await seasons.GetMyResultsAsync();
Rollover and reward payout run automatically via pg_cron (or your
service_role key) — they pay into the currency ledger. Admin-only by design.
Remote config, flags, experiments, localization
var config = new RemoteConfigService(settings, SupabaseClient.Instance.Http);
await config.RefreshAsync();
float coin = config.GetFloat("coin_multiplier", 1f);
var flags = new FeatureFlagService(settings, http);
await flags.RefreshAsync();
bool newUi = flags.IsEnabled("new_inventory_ui");
var variant = await new ExperimentService(settings, http).GetVariantAsync("new_ui");
var loc = new LocalizationService(settings, http);
await loc.LoadAsync("es");
title.text = loc.Get("welcome");
Retune balance, toggle features, run A/B tests, and translate UI without shipping a build.
Live-Ops Dashboard (web console)
A self-hostable, zero-dependency web console for running your live game straight against Supabase — no backend to deploy.
It is a single static HTML file shipped at Documentation~/LiveOpsDashboard/index.html
(a hosted copy is linked from this site as Live-Ops). Open it,
paste your Project URL and an API key, and operate the systems the toolkit
already creates:
| Tab | Backend pack | What you can do |
|---|---|---|
| Overview | analytics, players, Auth | KPIs: auth users, player rows, active users today, events today, bans |
| Announcements | announcements | Publish / delete server-driven news, events, maintenance notices |
| Feature Flags | feature_flags | Toggle flags, set percentage rollouts, add / delete |
| Remote Config | app_config | Edit live tunables (balance, toggles, copy) |
| Rewards & Mail | mailbox | Send mail and gift reward payloads (claimed in-game) |
| Moderation | moderation | Ban / unban players, review and resolve reports |
| Leaderboard | players | Read the default name/score board |
| Players | Auth admin API | Look up users, copy ids, jump to reward / ban |
sessionStorage (cleared on close); nothing is
baked into the file. Connect with the anon key for read-only views. Install the
matching backend pack from the SQL Packs tab before using a tab.AI & agents (bring your own key)
Chat & streaming NPCs
npc.Say("Got any swords for sale?");
var ai = new AiChatClient(config);
await ai.CompleteStreamAsync(system, user, token => appendToUi(token));
Semantic search, RAG & moderation
var search = new SemanticSearchService(settings, http);
var rag = new RagService(search, embedder, chat);
var mod = new ModerationService(settings, http);
var verdict = await mod.AnalyzeAsync(playerChatText);
MCP server (agent-operable backend)
A bundled Model Context Protocol server (Documentation~/McpServer) lets
Claude, Cursor, Kiro or Copilot list/apply backend packs, run SQL, inspect your schema, and
generate C# models — against your project, with your service_role key. See its README for setup.
UI kit
Two ways to get gameplay UI fast:
- Themeable UI Toolkit screens — drop-in
SupabaseAuthScreen,SupabaseLeaderboardScreen,SupabaseProfileScreen, restyled from a singleSupabaseTheme.uss(light/dark). - uGUI prefab builders — one-click menu items for Auth, Leaderboard, Profile, Chat, Inventory, Cloud Save, and an achievement toast.
Offline & caching
Enable the offline cache in settings for stale-while-revalidate reads plus a mutation queue that replays writes when the network returns. Useful for flaky mobile connections.
Platforms
Windows, macOS, Linux, Android, iOS, and WebGL. Mono and IL2CPP (AOT-safe; a bundled
link.xml protects data types from code stripping). See Documentation~/PlatformSupport.md
for the full matrix and WebGL specifics. Run your own device QA per platform, especially consoles.
Security best practices
- Ship only the anon/publishable key in clients. The toolkit stores the Secret or legacy service-role key in machine-local editor preferences, never in the runtime settings asset.
- Enable RLS on every table (the packs do this) and verify with the RLS Simulator + coverage checker.
- Use private channels for sensitive realtime. Currency grants, quest progress and battle-pass XP require a trusted service-role context; client-reported scores still need game-specific validation.
- The SQL Console / Provisioner executor runs arbitrary SQL — it's dev-only. Remove it before production:
drop function if exists public.shipit_exec_sql_json(text);
Troubleshooting
| Symptom | Fix |
|---|---|
| 401 on runtime data | Check the anon/publishable key and your RLS policies. The error message includes the server response + a hint. |
| Schema access is forbidden | Add a Secret key or legacy service-role key in Setup Wizard. Supabase requires privileged authentication for OpenAPI schema introspection. |
| Connection failed | Use the API URL (ends in .supabase.co), not the dashboard URL. |
| Update/Delete throws | Add a .Where(...) filter — required for mutations. |
| Realtime joins do nothing | Subscribe after connecting; for private channels apply the Realtime Authorization pack and sign in. |
| Provision says executor missing | Run the SQL Console one-time setup once, then provision. |
| IL2CPP/WebGL build issues | The bundled link.xml covers DTOs; see PlatformSupport.md. |
Social & progression services
First-class C# services wrap each pack so you never hand-write REST/RPC. All are RLS-scoped to the caller.
FriendServiceGuildServiceMailboxServiceQuestServiceincrement_quest)AchievementServiceDailyRewardServiceInventoryServiceMatchmakingServiceTournamentServiceRankedMatchmakingServiceAuctionHouseServiceRateLimitServiceCloudSaveHistoryServiceWebhookServiceReferralServiceIdempotencyServiceAuditServiceBattlePassServicePresenceServiceModerationServiceAnalyzeAsync