Migrating from PlayFab to asobi

View Source

If you're reading this, you've probably been through the PlayFab v2 migration, watched features quietly get removed, or watched your Azure bill climb while the product got thinner. You're not alone — the Imperium42 write-up catalogues the situation far better than we can.

This guide walks you from "my PlayFab stack is working but brittle" to "I run my game on a Docker container I own."

Draft notice. This guide is a starting point, not a playbook — nobody has yet migrated a shipped PlayFab title to asobi end-to-end. The asobi-side endpoints and events below are verified against the current code. PlayFab-side SDK names come from the public PlayFab documentation and may have drifted. The fastest path is pairing with us in the Discord #migrations channel.

TL;DR

  1. Your Unity/Unreal/JS game keeps shipping. You don't touch the client.
  2. Stand up asobi in parallel on Hetzner / Fly / your laptop.
  3. Port one PlayFab API domain at a time — usually Auth → Player Inventory → Virtual Currency → Leaderboards → Matchmaking, in that order.
  4. When all domains are ported, flip a feature flag to point at asobi and retire the PlayFab Title.

Why asobi specifically

  • Apache-2.0, open-source, self-hostable. Not a Microsoft product, not a SaaS. The repos are widgrensit/asobi and widgrensit/asobi_lua. See the exit guide if you want to know what happens if we disappear.
  • Flat infra cost. PlayFab Essentials starts free but scales steeply through compute-based tiers, Data Explorer add-ons, and dedicated multiplayer server VMs. asobi is a single container whose cost you control — a small Hetzner box (€5-15/mo) comfortably holds thousands of players.
  • Linux dedicated servers work. Unlike PlayFab's Unreal OSS SDK which historically forced Windows hosts (and their licensing costs), asobi just runs in any Linux container.
  • Hot-reload Lua. Ship a fix at 11pm. Connected players stay connected.
  • One matchmaking service. Not three (Client::Matchmaker, Multiplayer Matchmaking 2.0, OSS SDK) with no canonical guidance — just asobi_matchmaker with pluggable strategies.
  • Friends work. Request, approve, block — all in the library.
  • Lobbies hold state. Our matchmaker tickets + match "waiting" phase replace the v1 Lobby. Not the stateless read-only v2 Lobby that broke half the games on PlayFab.

Concept map

PlayFabasobiNotes
TitleTenant / deploymentOne Docker container per environment (dev/live).
TitleId + SDK configBase URL of your asobi deploymentNo opaque ID — you point the SDK at a URL.
Entity (master_player_account)PlayerSame concept: durable ID + profile.
Virtual CurrencyEconomygame.economy.grant, debit, balance, purchase. Multiple named currencies; per-player ledgers.
CatalogStore + inventoryasobi_store_listing + asobi_item_def tables; /api/v1/store.
InventoryInventorygame.player_items in Lua / /api/v1/inventory REST.
CloudScript (JS functions)Lua in match.lua + REST controllersYour server logic runs as part of the match process — no separate Functions runtime, no cold starts.
Matchmaking (Queue)asobi_matchmakerStrategies: fill, skill_based, or bring your own via asobi_matchmaker_strategy.
Multiplayer Server (Build)Match processNo container-per-match. One Docker container hosts thousands of matches as BEAM processes. Simpler ops, cheaper.
Data → Player → KeyValue/api/v1/storage/:collection/:keyPer-player and shared collections with public/owner/none permissions.
Data → Title Data/api/v1/storage/global/:keyUse a well-known collection.
Data → Title Internal DataErlang sys.config or Kura schemaSensitive config stays out of the API.
Leaderboards + StatisticsLeaderboards (/api/v1/leaderboards/:id)ETS for microsecond reads, Postgres for persistence.
Friends listFriends (/api/v1/friends)Request / approve / block / update status all work.
Player GroupsGroups (/api/v1/groups)Roles, member management, chat channel per group.
Push NotificationsNotifications table + WS pushmatch.notification event or polled via /api/v1/notifications.
PlayFab Party (voice/chat)Chat channels + DMText only. For voice, pair asobi with Vivox / Dissonance / a WebRTC service.
Receipt validation (IAP)/api/v1/iap/apple, /api/v1/iap/googleVerifies Apple App Store and Google Play receipts.
Automation rules / webhooksShigoto jobsWrite the rule as an Erlang callback or Lua handler.
Insights / Analyticsasobi_telemetry + your pipelineWe emit telemetry; pipe to Prometheus / Grafana / ClickHouse. No hosted analytics yet.
Game Manager (web console)asobi_adminPlayers, leaderboards, economy, chat. Pre-1.0.

Migration path

Phase 1 — stand up asobi alongside PlayFab (1 day)

Bring up asobi on a spare machine:

# docker-compose.yml
services:
  postgres:
    image: postgres:17
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: postgres
      POSTGRES_DB: my_game

  asobi:
    image: ghcr.io/widgrensit/asobi_lua:latest
    depends_on: [postgres]
    ports: ["8080:8080"]
    volumes: ["./lua:/app/game:ro"]
    environment:
      ASOBI_DB_HOST: postgres
      ASOBI_DB_NAME: my_game
docker compose up -d
curl localhost:8080/api/v1/auth/register \
  -H 'content-type: application/json' \
  -d '{"username":"alice","password":"hunter2"}'
# → { "player_id": "...", "session_token": "...", "username": "alice" }

Phase 2 — port Auth (2-5 days)

PlayFab auth paths map 1:1:

// Before (PlayFab)
PlayFabClientAPI.LoginWithCustomID(new LoginWithCustomIDRequest {
  CustomId = deviceId, CreateAccount = true
}, OnSuccess, OnError);

// After (asobi) — generate creds once, persist locally, then login
var client = new AsobiClient("https://api.my-game.com");
if (!PlayerPrefs.HasKey("asobi_pw")) {
  PlayerPrefs.SetString("asobi_pw", Guid.NewGuid().ToString("N"));
  await client.Auth.RegisterAsync(deviceId, PlayerPrefs.GetString("asobi_pw"));
} else {
  await client.Auth.LoginAsync(deviceId, PlayerPrefs.GetString("asobi_pw"));
}

OAuth providers (Google, Apple, Steam) go through POST /api/v1/auth/oauth — same as PlayFab's LoginWithGoogleAccount etc.

Phase 3 — port the data domains one at a time (1-2 weeks)

Run PlayFab and asobi in parallel. For each domain:

  • Migrate the PlayFab data snapshot to asobi's Postgres schema (one-off script per domain)
  • Dual-write: the client hits PlayFab AND asobi for the same action
  • Read from asobi; diff vs PlayFab for a day
  • Switch reads to asobi; keep PlayFab dual-write for rollback
  • After a week of clean asobi reads, stop writing to PlayFab

Order: Leaderboards → Inventory → Virtual Currency → Storage → Friends → Groups → Matchmaking. Leave matchmaking last because it's the most stateful handoff.

Phase 4 — port CloudScript (2 days – 2 weeks)

Rewrite each CloudScript function either as:

  • A Lua callback in match.lua (for per-match logic — e.g. handle_input, tick)
  • An asobi REST controller in Erlang (for domain logic — economy rules, tournament brackets, daily quest resets)

If your PlayFab workload is CloudScript-heavy, budget more time for this phase. The upside: hot-reload replaces the CloudScript deploy loop.

Phase 5 — cut over (1 day)

Flip the SDK base URL from PlayFab to your asobi endpoint via a feature flag. Monitor for 24h. Retire the PlayFab Title.

Deploy story

HostFitRough cost
Hetzner Cloud (CX22–CX42)Best price/perf. EU-only.€4–15 / month
Scaleway ServerlessAuto-scale for dev / low trafficFree tier → pay per req
Fly.ioMulti-region one-liner$5+/month/region
Clever Cloudgit-push deploy, EU€10+/month
On-prem (your datacentre)Regulated / sovereign workloadsYour hardware cost

A studio running PlayFab Multiplayer Servers at, say, $300/month in VM credits typically fits on a €15/month Hetzner CX32 box with asobi.

Things asobi does NOT do (compared to PlayFab)

  • No hosted analytics dashboard. We emit telemetry; you pipe it somewhere. PlayFab Insights is the biggest DX gap.
  • No built-in A/B testing / segmentation framework. Coming in 2026. For now, roll it in your match logic.
  • No push notification service. Use OneSignal, Firebase Cloud Messaging, or APNs directly.
  • No hosted voice. Pair with Vivox / Dissonance / Agora.
  • No Title-as-a-product support tools (refunds portal, player support console). On the admin dashboard roadmap.
  • No mandated Entity model. asobi is pragmatic: player_id is the primary key; you don't have to model everything as Entity-With-Objects.

Things asobi does that PlayFab doesn't

  • Hot-reload game logic without dropping players
  • Open-source — read the code, fork it, own it
  • Linux servers are first-class
  • One unified matchmaker, not three competing services
  • Friends / groups / chat / votes / tournaments / seasons / phases as first-class primitives, not bolt-ons
  • Built-in voting system (plurality, ranked, approval, weighted)
  • Godot and Defold SDKs at engine-parity with Unity

Cost comparison

PlayFab EssentialsPlayFab paidasobi self-hostasobi managed (soon)
BaseFree tier$99+/mo€5–20/mo infra~€9–29/mo
Multiplayer serversN/AVM-minute billingSame containerIncluded
Analytics add-onsLimitedData Explorer meteredBring your own stackBring your own
EgressN/AAzure ratesYour host's ratesFlat
Vendor lock-inHigh (Azure)HighNone (Apache-2)Exit runbook

Do this today

  • [ ] git clone asobi_lua and docker compose up. Register a player. Confirm it works.
  • [ ] Pick the smallest PlayFab API your game calls (often leaderboards or a single CloudScript function). Port it to asobi in a feature flag.
  • [ ] Join the Discord #migrations channel. We'll sanity-check your staging order.

Getting help

See also