Tooling

Metaloot SDK

@metaloot/sdkis a zero-dependency ESM package for game code: stream hosted assets by id, read the player's auth session, and join multiplayer rooms. It works in the browser, Node 20+, and Workers, with optional adapters for three.js and Babylon.js.

Installation#

npm install @metaloot/sdk

The package is tree-shakeable and split into entry points so your bundle only carries what you use:

@metaloot/sdkThe unified client (createMetaloot()) plus re-exports of the most common helpers.
@metaloot/sdk/assetsAsset URLs, metadata, manifests, and loaders.
@metaloot/sdk/authPlayer session for games using Metaloot auth.
@metaloot/sdk/multiplayerjoinRoom() — same API as the zero-install /__metaloot/multiplayer.js module.
@metaloot/sdk/threethree.js adapter (peer dep three >= 0.160).
@metaloot/sdk/babylonBabylon.js adapter (peer deps @babylonjs/core, @babylonjs/loaders >= 7).

The Unified Client#

import { createMetaloot } from "@metaloot/sdk";

const ml = createMetaloot(); // { auth, assets, multiplayer }

const session = await ml.auth.getSession();
const url = await ml.assets.loadAssetObjectUrl("treasure-chest-1a2b3c4d");
const room = await ml.multiplayer.joinRoom("lobby");

createMetaloot(options?) accepts { studioOrigin?, token?, authBasePath?, fetch? } — all optional. A ready-made default instance is exported as metaloot. Every method is also available as a named export from its module if you prefer plain functions.

Assets#

Load hosted assets by id or slug — no bundling, no CORS setup. The SDK picks the same-origin edge proxy on Metaloot hosting and the Studio API everywhere else:

import {
  assetUrl, loadAsset, loadAssetObjectUrl,
  getAsset, getAssetManifest, listAssets, loadAssetFile,
  loadAnimation, loadAnimationObjectUrl,
} from "@metaloot/sdk/assets";

// A loader-ready URL (blob:) for any GLTF loader:
const url = await loadAssetObjectUrl("treasure-chest-1a2b3c4d");

// Raw bytes for an engine's parse API:
const buffer = await loadAsset("treasure-chest-1a2b3c4d");

// Metadata, search, and pack files:
const asset = await getAsset("frost-witch-c78f6ed7");
const found = await listAssets({ query: "sword", kind: "model3d" });
const manifest = await getAssetManifest("kenney-interface-sounds");
const bytes = await loadAssetFile("kenney-interface-sounds", { path: manifest.files[0].path });
  • Variants assetUrl(id, { variant: "auto" | "source" | "lod" }); auto (default) serves the game-ready LOD once built.
  • Private assets — pass { token: "mtl_api_…" } (needs the assets:read scope); dev-only, never ship tokens in a bundle.
  • Animations loadAnimationObjectUrl(id, "idle") per rigged preset; all presets share one skeleton.

Full asset docs, including the CLI side of the pipeline: Assets.

Auth#

For games using Metaloot auth (deployed on Metaloot hosting, or running the @metaloot/auth adapter):

import { getSession, signIn, signOut } from "@metaloot/sdk/auth";

const session = await getSession();
// { signedIn: true, user: { id, name, imageUrl, … }, scope, expiresAt }
// or { signedIn: false }

if (!session.signedIn) signIn();   // redirects to Sign in with Metaloot

The default base path is /auth/metaloot — override with getSession({ basePath }) if you mounted the adapter elsewhere. On Metaloot hosting, window.metaloot.session gives you the same data with no import at all.

Multiplayer#

import { joinRoom, MetalootAuthRequiredError } from "@metaloot/sdk/multiplayer";

const room = await joinRoom("lobby");
room.on("message", ({ from, data }) => handleInput(from, data));
room.send({ kind: "move", x: 3, y: 7 });

Identical API to the zero-install /__metaloot/multiplayer.js module — use the npm import when you want types and bundling, the platform module when you want zero dependencies. Rooms require the game to be on Metaloot hosting. Full event and method reference: Multiplayer.

Engine Adapters#

The adapters take an asset from URL to a placed, animated, cleaned up object in your scene — rendering and gameplay stay yours:

// three.js
import { loadThreeAsset } from "@metaloot/sdk/three";

const hero = await loadThreeAsset("ember-mage", {
  scene,                    // added to your scene, grounded and scaled
  animations: "available",  // load all rigged presets into one mixer
  targetHeight: 1.8,
  shadows: true,
  autoPlay: "idle",
});
hero.update(clock.getDelta());  // tick the mixer each frame
hero.play("run");               // crossfade between presets
hero.dispose();                 // full cleanup

// Babylon.js
import { loadBabylonAsset } from "@metaloot/sdk/babylon";
const chest = await loadBabylonAsset("treasure-chest-1a2b3c4d", { scene });
The adapters are optional entry points — importing @metaloot/sdk or @metaloot/sdk/assets never pulls in three.js or Babylon.