Create a New Decentraland SDK7 Scene
Runtime constraint: Decentraland runs in a QuickJS sandbox. No Node.js APIs (
,
,
,
). Use the SDK's
+
for async work. See the
scene-runtime skill for details.
CRITICAL — read before generating any code: All initial scene entities (everything present at scene load) go in
assets/scene/main.composite
, NEVER in
. See "Composite vs TypeScript — where entities go" (Step 4) for the rule, decision table, and rationale.
When the user wants to create a new scene, follow these steps:
1. Ask What They Want to Build
If the user hasn't described their scene, ask them:
- What kind of scene? (gallery, game, social space, interactive art, etc.)
- How many parcels? (default: 1 parcel = 16x16m)
- Any specific features? (3D models, interactivity, UI, multiplayer)
2. Scaffold the Project with
Always run first. This uses the official
to create scene.json, package.json, tsconfig.json, and src/index.ts with the correct, up-to-date configuration, and installs dependencies automatically.
Never manually create scene.json, package.json, or tsconfig.json — the SDK templates may change between versions and hand-written copies will diverge.
The
and
tsconfig settings are already included by
— do not modify them.
3. Find Matching 3D Assets
IMPORTANT: Only fetch models from the free catalogs below if the prompt explicitly asks to add new models. Confirm with the user always if they wish to add new models to their scene.
Before writing scene code, check the asset catalog for free models that match the user's theme:
- Search
{baseDir}/../add-3d-models/references/model-catalog.md
(8,800+ models with descriptions, dimensions, animations, and download URLs)
- Read
{baseDir}/../audio-video/references/audio-catalog.md
(50 free sounds — music, ambient, SFX, game mechanics, etc.)
- Suggest matching models and sounds to the user
- Download selected models into the scene's directory:
bash
mkdir -p assets/Models
curl -o assets/Models/arcade_machine.glb "https://models.dclregenesislabs.xyz/blobs/bafybei..."
Important:
only works with local files. Never use external URLs for the model
field.
Important: Always download into
. Never write to the scene root.
Existing folders take precedence. If the scene already has
(legacy layout) or assets under
(Creator Hub asset packs) /
(Creator Hub custom items), reuse those paths instead of creating a parallel
. Same rule applies for
,
, and
.
Done when: every model the user approved exists in
(or the pre-existing asset folder per the precedence rule above), each file is non-empty and begins with the
magic bytes (
) — a curl that saved an HTML error page fails this check — and no downloaded file sits at the project root. If the user declined new models, this step is done with nothing downloaded.
4. Customize the Generated Files
After
completes, customize the generated files based on what the user wants:
scene.json
Update the
fields and parcels:
- — set to the scene name
- — set to a short description
- — for multi-parcel scenes, list all parcels (e.g.,
["0,0", "0,1", "1,0", "1,1"]
for 2x2)
- — set to the southwest corner parcel
Composite vs TypeScript — where entities go
NEVER create initial scene entities in TypeScript. They MUST go in assets/scene/main.composite
. If you find yourself writing
for a piece of scenery or a static prop, stop — put it in the composite instead.
| Use for | Use (index.ts) for |
|---|
| All entities present at scene load (models, lights, primitives, text, audio) | Entities spawned dynamically at runtime (e.g., projectiles, clones, NPCs that appear on demand) |
| Static and decorative objects | Entities whose count or existence depends on runtime state |
| Entities that need behavior added later (fetch by name/tag in code) | Entities whose identity/structure cannot be known at author time |
| Anything the Creator Hub should be able to display and edit visually | — |
Rationale: Composite assets load faster, are visually editable in the Creator Hub, and keep TypeScript code focused on logic rather than scene construction.
assets/scene/main.composite
Create
assets/scene/main.composite
with the initial scene entities. See
{baseDir}/../composites/composite-reference.md
for the full format.
Editing an existing scene? Read the "Editing an existing composite (edit mode)" section of the composite reference FIRST. If the scene has been opened in the Creator Hub,
already contains
components; adding new entities without registering them in
leaves them rendering in-world but invisible and un-selectable in the Creator Hub entity tree. The reference spells out the exact procedure.
Minimal example — a single named box. Components share entity IDs across their
maps, so all of entity
's data lives under the
key:
json
{
"version": 1,
"components": [
{
"name": "core::Transform",
"data": {
"512": {
"json": {
"position": { "x": 8, "y": 1, "z": 8 },
"scale": { "x": 1, "y": 1, "z": 1 },
"rotation": { "x": 0, "y": 0, "z": 0, "w": 1 },
"parent": 0
}
}
}
},
{
"name": "core::MeshRenderer",
"data": { "512": { "json": { "mesh": { "$case": "box", "box": {} } } } }
},
{
"name": "core-schema::Name",
"data": { "512": { "json": { "value": "BlueCube" } } }
}
]
}
For multi-entity scenes, GLB models with collision masks, tags, and the full component-grouping pattern, see
{baseDir}/../composites/composite-reference.md
.
IMPORTANT: When placing a floor entity, always set the y position to 0.01 or higher so that it doesn't z-fight with the default ground.
- Center of a single-parcel scene is (8, 0, 8) at ground level.
- Y axis is up; ground level is Y=0. Floors and walkable surfaces belong at Y ≥ 0 because players cannot descend below ground, but entities can be placed at negative Y — positioning objects underground is a legitimate technique for hiding them.
src/index.ts
- Behavior and interactivity on composite entities (fetch them by name or tag)
- Dynamically spawned entities (e.g., enemies, projectiles, clones)
- Systems, game logic, UI
To add interactivity to a composite entity, look it up by name or tag — do NOT re-create it in code:
typescript
import { engine, pointerEventsSystem, InputAction } from '@dcl/sdk/ecs'
import { EntityNames } from '../assets/scene/entity-names'
export function main() {
// Fetch an entity defined in the composite — never re-create it here
const cube = engine.getEntityOrNullByName(EntityNames.BlueCube)
if (cube) {
pointerEventsSystem.onPointerDown(
{
entity: cube,
opts: { button: InputAction.IA_PRIMARY, hoverText: 'Click me' },
},
() => {
console.log('Cube clicked!')
}
)
}
}
To fetch groups of entities by tag (
) or add/remove tags at runtime, see the "Referencing Composite Entities from Code" section of
{baseDir}/../composites/composite-reference.md
.
scene.json Reference
| Field | Required | Description |
|---|
| Conventional | in SDK7 scenes. Written by ; the build only validates , but keep it for tooling compatibility |
| Yes | Must be |
| Yes | Must be — the compiled output path |
| Recommended | Scene name shown in the map and Places |
| Recommended | Short description for discovery |
| Optional | Image path for the Genesis City minimap |
| Yes | Array of coordinate strings |
| Yes | The origin parcel (usually southwest corner) |
| Optional | Where players appear when entering (see below) |
| Optional | Array of permissions (e.g., ) |
| Optional | Whitelisted domains for external media |
| Optional | Enable/disable SDK features |
| Optional | For Worlds deployment (see deploy-worlds skill) |
| Optional | Boolean, default . Root-level field. Worlds only (single-scene Worlds; ignored in Genesis City). Set to disable the auto-generated grassland/trees/sea landscape around the scene — for open-water/space settings and to free rendering budget. Also applies in local preview. In the Creator Hub, it is a toggle in the Scene Inspector settings (and a preview menu option); a scene-level overrides the preview preference. |
Tags
Valid values for the
array:
,
,
,
,
,
,
,
,
,
,
,
Required Permissions
Add to
when your scene uses these features:
These are the exact 7 permission strings the runtime recognizes (the protocol enum names drop the
prefix):
| Permission | When needed |
|---|
ALLOW_TO_MOVE_PLAYER_INSIDE_SCENE
| (move player within the scene) |
ALLOW_TO_TRIGGER_AVATAR_EMOTE
| and |
| | External video/audio streams — not required (see below) |
| Blockchain interactions |
| HTTP requests ( / ) |
| WebSocket connections |
| (open URLs in the browser) |
Grounded caveat (from the engine test scenes): enforcement is uneven, so declare the correct permission for
intent rather than relying on it being blocked. The
scene declares only
ALLOW_TO_MOVE_PLAYER_INSIDE_SCENE
+
ALLOW_TO_TRIGGER_AVATAR_EMOTE
, yet successfully runs
,
,
, and
without
. The
scene calls
with an empty
.
and emotes are the two whose permissions the engine team consistently declares.
(jump to other Genesis City coords) needs no permission.
and
are
not required — do not add them for new scenes. The permission string still exists in
, but no current client enforces it: unity-explorer gates the hostname check behind the
CHECK_ALLOWED_MEDIA_HOSTNAMES
compile define, which is set in no build config (
falls through to a plain URL syntax check), and bevy-explorer has no enforcement at all. Only the retired web client enforced it. Current clients play external media without it. If a legacy scene still declares it, whitelist the domains as follows:
json
"requiredPermissions": ["ALLOW_MEDIA_HOSTNAMES"],
"allowedMediaHostnames": ["youtube.com", "www.youtube.com", "player.vimeo.com", "twitch.tv"]
Feature Toggles
json
"featureToggles": {
"voiceChat": "enabled",
"portableExperiences": "enabled"
}
Valid values:
,
. For
also:
.
Spawn Points
Configure where and how players enter the scene:
json
{
"spawnPoints": [
{
"name": "spawn1",
"default": true,
"position": { "x": [1, 5], "y": [0, 0], "z": [2, 4] },
"cameraTarget": { "x": 8, "y": 1, "z": 8 }
}
]
}
- Position ranges (e.g., ) spawn players randomly within the range
- orients the player's camera on spawn — point it at the scene's focal area
- Fixed spawn: use single values instead of ranges (e.g., )
Multi-Parcel Layouts
| Layout | Parcels Array | Use Case |
|---|
| Single | | Small games, galleries, single-room experiences |
| Strip | | Hallways, racing tracks, linear journeys |
| L-Shape | | Corner buildings, split experiences |
| 2x2 Square | ["0,0", "1,0", "0,1", "1,1"]
| Open plazas, arenas, medium games |
| 3x3 Square | 9 parcels from to | Large games, multi-room buildings |
Base parcel: Always set
to the southwest (lowest x,y) corner parcel.
Boundaries: each parcel is 16m x 16m; a 2x2 scene spans 32m x 32m. The height limit applies to the whole scene and grows with parcel count:
meters (1 parcel = 20m, 2x2 = ~46m, 3x3 = ~66m).
- Always validate entity positions against parcel bounds. With the default base parcel at the lower-left corner, valid range is and . Any negative X or Z coordinate is outside the scene. An entity entirely outside the bounds is not rendered and no error is shown; a model that straddles the boundary still renders the part that is inside. The bound check uses world positions, so a child whose parent is moved out of bounds disappears with it, and exceeding the height limit hides the entity too. Multi-parcel scenes are only rectangular if you list every parcel; an L-shaped parcel set has "holes" that are out of bounds. (See the example scene.)
Changing parcels in an existing scene: Modifying
shifts the coordinate bounds for the entire scene — entities near the current boundary may end up outside (invisible) after the change. Before editing this field, describe the proposed change and confirm with the user first. See the "Agent Behavioral Guidelines" section in the
skill (
{baseDir}/../sdk-scenes/SKILL.md
).
Done when: (1)
parses as JSON and every entity ID that appears in any component's
map has a
and a
entry; (2) every composite position lies within parcel bounds (
,
) — an entity placed entirely outside is silently hidden, and a model straddling the boundary renders only the part inside; (3)
contains no
for load-time scenery — grep for
and confirm every hit is a runtime spawn; (4)
has
and
is a member of
; (5)
exits 0.
5. Post-Creation Steps
After customizing the files:
- Use the tool to start the preview server (or run
npx @dcl/sdk-commands start --bevy-web
manually)
- The scene will open in a browser at http://localhost:8000
Preview CLI flags
| Flag | Type | Description |
|---|
| (alias ) | boolean | Open the preview in the Bevy Web browser client at decentraland.org/bevy-web/
instead of the Desktop Explorer. Chromium 142+ requires Local Network Access permission for the page to reach the localhost preview server -- when the browser asks to access apps on your device, click "Allow". |
| boolean | Enable the MCP server in the Explorer (forwarded as a deep-link parameter) |
| number | Port for the MCP server in the Explorer |
| boolean | Allow running multiple Explorer instances simultaneously |
| boolean | Suppress auto-launch (desktop deeplink, browser, mobile QR); the file watcher still notifies a desktop Explorer if it connects on its own |
| passthrough | Arguments after a standalone are forwarded verbatim into the Explorer deep link as query params (, , bare = true) |
has been removed.
and
(alias
) are deprecated no-ops kept for backwards compatibility only -- do not use them in new scenes.
Bevy renderer in Creator Hub: Settings > Editor > "Scene renderer" dropdown (Babylon default / Bevy preview). Gated behind the Experimental features toggle. The Bevy editor supports gizmos, multi-select, free-fly camera, spawn point visualization, drag-drop assets, animation clip dropdown, lock/hide entities, screenshots, and hot-reload.
Keep (project root) up to date. It lists files and extensions that are NOT uploaded on deploy. Whenever the project contains working files — Blender/FBX sources, draft models, concept art, spreadsheets, markdown notes — add them (or their extensions) to
proactively so the deployed scene stays light. See the
section in the
deploy-scene skill.
Done when: the preview server responds at
and the scene renders with no errors in the console.
Vibe Coding with AI
AI assistants (Cursor, Claude Code, etc.) can build entire scenes from plain-language prompts. Install Decentraland SDK skills first so the AI knows SDK patterns:
bash
npx skills add decentraland/sdk-skills
The official quickstart teaches a
Script-component-first workflow: attach a Script component to an entity in the Creator Hub, write a class with
,
, and
, and use
to reference the holder entity. This keeps behavior self-contained and reusable across entities. See the
script-components skill for full details.
Cross-References
- Ready to deploy? See the deploy-scene skill (Genesis City) or deploy-worlds skill (personal Worlds)
- Need to optimize for parcel limits? See the optimize-scene skill
- Planning a game? See the game-design skill for design patterns and performance budgets
- Validate entity component combinations: see
{baseDir}/references/entity-validation-rules.md
for rules on which components require each other, mutual exclusions, and common misconfigurations
Example scenes
Engine-team test scenes illustrating
configuration:
- https://github.com/decentraland/sdk7-test-scenes/tree/main/scenes/5,90-scene-bounds-check — multi-parcel, non-rectangular parcel layout (
["5,90","5,89","6,89","6,88"]
); moves many entity types across the parcel/height boundary to show what the engine hides out of bounds.
- https://github.com/decentraland/sdk7-test-scenes/tree/main/scenes/80,-4-restricted-actions — for + emotes.
- https://github.com/decentraland/sdk7-test-scenes/tree/main/scenes/8,8-portable-experience —
featureToggles.portableExperiences: "enabled"
(see also the and sibling scenes).