GUIDELINES

Plain rules. We want this to feel like the old portals — fun, weird, and safe to click.

What to upload

What's not allowed

How publishing works

Saving progress (optional)

Your game can persist up to 64 KB of JSON per save slot, scoped to the signed-in player — nobody else can read or write it. Games have no network access at all (see the sandboxing note above), so saves go through the parent page via postMessage. Paste this into your HTML and call Save.get() / Save.put(data):

<script>
  let _saveReqId = 0;
  const _saveWaiters = new Map();
  window.addEventListener("message", (e) => {
    const m = e.data;
    if (!m || m.channel !== "domainless-save" || m.type !== "result") return;
    const w = _saveWaiters.get(m.reqId);
    if (!w) return;
    _saveWaiters.delete(m.reqId);
    m.ok ? w.resolve(m.data) : w.reject(new Error(m.error));
  });
  function _saveCall(type, extra) {
    return new Promise((resolve, reject) => {
      const reqId = ++_saveReqId;
      _saveWaiters.set(reqId, { resolve, reject });
      window.parent.postMessage(
        Object.assign({ channel: "domainless-save", type, reqId }, extra),
        "*",
      );
    });
  }
  const Save = {
    get: (slot = "default") => _saveCall("get", { slot }),
    put: (data, slot = "default", version = 1) =>
      _saveCall("put", { slot, data, version }),
    del: (slot = "default") => _saveCall("delete", { slot }),
    list: () => _saveCall("list", {}),
  };
  // Your game cannot show a sign-in form itself (no network, opaque
  // origin), so ask the page around you to do it instead.
  const Auth = {
    state: () => _saveCall("authstate", {}),
    signIn: (reason) => _saveCall("signin", { reason }),
  };
  // await Save.put({ level: 3, coins: 120 });
  // const saved = await Save.get(); // { slot, version, data, updated_at } or null
</script>

Asking the player to sign in. A rejected save now carries needsAuth: true when the only problem was that nobody is signed in, so you can tell that apart from a real failure. Call await Auth.signIn("Sign in to keep your progress.") and the sign-in sheet opens over your game; it resolves to { signedIn }, which is false if the player backed out. Auth.state() tells you where you stand without prompting anyone. Nothing navigates away, so a player never loses their place mid-run.

Not signed in? Save.get() / Save.put() reject with an error your game can catch (e.g. show "sign in to save progress"). Slot names: letters, numbers, - and _, up to 40 characters — use more than one slot for multiple save files. version is yours to use however you like (e.g. bump it when you change your save format).

Questions? Email us.

Submit a game Back to arcade