# Custom assets ## Accepted format Upload a **static, self-contained GLB 2.0**. Textures and binary buffers must be embedded. External URLs, external buffers and unsupported extensions are rejected. Uploaded content must contain triangle meshes and valid references. Accepted image sources are static PNG, JPEG or WebP; the converter fully decodes and re-encodes them. Maximum dimension is 2048 pixels per side, with a total decoded pixel budget of 4,194,304 per prop. Animated images, rigs, skinning, morph targets and prop animation are not supported. Never upload `.blend`, `.py`, JavaScript, HTML, shaders, executables or archives as world assets. A GLB extension does not bypass validation; malformed files are rejected by structure and content. ## Size limits - At most **4 MB decimal** per input GLB and converted output. - At most **100 MB** in the creator's entire uploaded model and background library, shared across their worlds. - At most **512 files** across models and panoramas. Prop uploads allow **60 attempts per rolling hour**, and panoramas allow **12**. Failed conversions count, and MCP and browser uploads share the same allowance. - At most **100 MB total download** for one world: its manifest, standard source assets and distinct uploaded assets. Repeating one model adds entity data but does not download the model again. - A separate **500 MB estimated memory** limit accounts for decoded geometry, textures, instances and collision data. An asset can be small on disk but expensive in memory. Compressed textures expand when decoded. Every placed copy contributes to instance and collision memory and the placed-triangle budget, even though shared geometry downloads once. ## Upload and use your asset Use `asset_upload` through MCP, or send raw GLB bytes to `POST /api/worlds/ai/assets/upload` with your world token. The response includes the converted asset's `hash`, `size`, `triangles`, `pixels` and `meshes`. Both upload methods send file contents directly to Cloudflare. Disposable offline converters sanitize and validate the file before storing the canonical result in R2. Betworld's application server authorizes your account or world token and records the validated hash and geometry budget; it does not receive the uploaded model bytes. Downloads come directly from R2 through the existing Betworld URLs. Use `asset: "upload:"` in a prop entity, or the bare hash in a game-skin manifest. Download the returned asset URL to inspect the converted result. See the [HTTP API](/world-api/) for headers, examples and errors. ## Practical modeling advice Use a modest number of materials and mesh parts. Reuse shared props rather than uploading the same shape with slight color changes; entity tint can handle those. Merge fine details into one sensible mesh. Avoid giant textures for small objects. Bake complex appearance into textures rather than runtime shaders or scripts. The official bridge centers custom models horizontally, places their bottom at zero and normalizes their maximum extent. ## Asset lifecycle Uploading adds a library asset; saving a world references it; publishing makes that layout available to players. Assets use immutable URLs. There is currently no public library-delete tool, so avoid uploading unused variants to conserve the 100 MB library allowance. Requesting an existing owner-scoped canonical asset does not add duplicate stored bytes. ## Game skin assets Replacement bodies, button shapes and playing surfaces use this same static GLB upload pipeline. Upload each distinct piece separately, then use its returned canonical hash in a [skin manifest](/game-skin-contract/). Do not create a separate prop entity for a bound button. The runtime fits the piece to its reference socket, then applies v2 transforms to the visual and native interaction proxy; uploaded names/extras cannot choose an action. Every used hash counts toward world download/texture budgets, and every placed body/binding counts toward geometry and estimated memory budgets. ## Background images Upload panoramas separately from GLB props using **World → Upload panorama**, `background_upload` through MCP, or `POST /api/worlds/ai/backgrounds/upload`. Images use the same 100 MB library quota and have their own format and dimension limits. See [terrain and backgrounds](/terrain-backgrounds/) for the full reference.