# Custom asset formats and player creations on PlayDrop

Define your own asset format with an asset spec, declare the formats a game reads or writes, and let players save and publish creations with sdk.assets.custom.

Source: https://www.playdrop.ai/create/learn/custom-assets

Asset specs let a game define its own kind of asset, such as a level, a character or a deck, as a contract of files. Players can then make, save and publish creations in that format, and other games that support it can open them.

## Define an asset spec

An asset spec is declared in the `assetSpecs` list of catalogue.json, next to `apps`:

```json
{
  "assetSpecs": [{
    "name": "moon-level",
    "version": "1.0.0",
    "displayName": "Moon Jump level",
    "symbol": "specs/moon-level.svg",
    "contract": "specs/moon-level.contract.json"
  }]
}
```

The contract names the files of each asset and how to check them:

```json
{
  "primaryRole": "level",
  "roles": [
    { "name": "level", "required": true, "contentTypes": ["application/json"], "mode": "TEXT",
      "jsonSchema": { "type": "object", "required": ["tiles"], "properties": { "tiles": { "type": "array" } } } },
    { "name": "thumbnail", "required": false, "contentTypes": ["image/png"], "mode": "BINARY" }
  ]
}
```

Roles may carry a JSON Schema, which PlayDrop checks on every save. Publish a new spec version when the format changes; old assets keep their version.

## Declare what a game supports

```json
{
  "assetSpecSupport": [
    { "assetSpec": "asset-spec:your-name/moon-level", "versionRange": "^1.0.0", "capabilities": ["READ", "WRITE"] }
  ]
}
```

Capabilities are `READ`, `WRITE`, `EDIT`, `PREVIEW` and `IMPORT`.

## Save and load player creations

```js
const levels = sdk.assets.custom.forSpec("asset-spec:your-name/moon-level");

await levels.saveDraft({ assetName: "crater-run", displayName: "Crater run",
  files: { level: new Blob([JSON.stringify(level)], { type: "application/json" }) } });
await levels.publish({ assetName: "crater-run", displayName: "Crater run", files: { level: levelBlob, thumbnail: pngBlob } });

const mine = await levels.listMine();
const latest = mine[0]?.latestPublicVersion;
if (latest) startLevel(await levels.loadJson({ assetRef: latest.assetRef, role: "level" }));

const community = await levels.listPublic({ limit: 20 }); // { assets, pagination }
```

- `saveDraft` keeps a private draft; `publish` makes it public. Both need the complete set of files.
- Asset references look like `asset:creator/name@r2`. `loadJson`, `loadText` and `downloadFile` read published assets; `loadOwned`, `loadOwnedJson` and `loadOwnedFile` read the player's own, drafts included.
- `supports(versionRef, capability)` checks before opening an asset from another version.
- Errors use codes such as `custom_asset_capability_unsupported` and `custom_asset_role_type_mismatch`.

Players need to be signed in to save. Review what players publish the way you would any user content: PlayDrop's content policy applies to it.

## What the connector accepts

Data-only custom assets and specs upload like other files. Executable models, meaning `MODEL_3D` assets with the `procedural` subcategory or the `CUSTOM` format, are refused by the connector because the API does not run creator code; bake them or reference a published version. See [game assets](https://www.playdrop.ai/create/learn/game-assets).

## Related guides

- [Game assets](https://www.playdrop.ai/create/learn/game-assets): Ship images, sound, video and 3D with a PlayDrop game: bundled files, game-owned assets, public assets and packs, and runtime lookup with sdk.assets.
- [catalogue.json](https://www.playdrop.ai/create/learn/catalogue-json): The catalogue.json fields PlayDrop games use: identity, listing, play modes, surfaces, sign-in, Instant, preloading, media, translations, scores and servers.
- [Client SDK](https://www.playdrop.ai/create/learn/sdk): Reference for the PlayDrop browser SDK: host lifecycle, identity and saves, controllers, social, multiplayer, shop, ads, leaderboards, assets and AI.
