scene-runtime
Scene Runtime APIs
Cross-cutting runtime APIs available in every Decentraland SDK7 scene.
Async Tasks
The scene runtime is single-threaded. Wrap any async work in executeTask():
import { executeTask } from '@dcl/sdk/ecs'
executeTask(async () => {
const res = await fetch('https://api.example.com/data')
const data = await res.json()
console.log(data)
})
HTTP: fetch & signedFetch
Plain fetch works for public APIs:
const res = await fetch('https://api.example.com/data')
signedFetch proves the player's identity to your backend. Use getHeaders() to obtain only the signed headers (useful when a library manages its own fetch):
import { signedFetch, getHeaders } from '~system/SignedFetch'
// Full signed request
const res = await signedFetch({ url: 'https://your-server.com/api', init: { method: 'POST', body: JSON.stringify(payload) } })
// Get signed headers only (for custom fetch calls)
const { headers } = await getHeaders({ url: 'https://your-server.com/api' })
Permission: External HTTP requires
"ALLOW_TO_MOVE_PLAYER_INSIDE_SCENE"or no special permission for plain fetch;signedFetchneeds the player to have interacted with the scene.
WebSocket
const ws = new WebSocket('wss://your-server.com/ws')
ws.onopen = () => ws.send('hello')
ws.onmessage = (event) => console.log(event.data)
ws.onclose = () => console.log('disconnected')
Scene & Realm Information
import { getSceneInformation, getRealm } from '~system/Runtime'
import { getExplorerInformation } from '~system/EnvironmentApi'
executeTask(async () => {
// Scene info: URN, content mappings, metadata JSON, baseUrl
const scene = await getSceneInformation({})
const metadata = JSON.parse(scene.metadataJson)
console.log(scene.urn, scene.baseUrl, metadata)
// Realm info: baseUrl, realmName, isPreview, networkId, commsAdapter
const realm = await getRealm({})
console.log(realm.realmInfo?.realmName, realm.realmInfo?.isPreview)
// Explorer info: agent string, platform, configurations
const explorer = await getExplorerInformation({})
console.log(explorer.agent, explorer.platform)
})
World Time
import { getWorldTime } from '~system/Runtime'
executeTask(async () => {
const { seconds } = await getWorldTime({})
// seconds = coordinated world time (cycles 0-86400 for day/night)
})
Read Deployed Files
Read files deployed with the scene at runtime:
import { readFile } from '~system/Runtime'
executeTask(async () => {
const result = await readFile({ fileName: 'data/config.json' })
const text = new TextDecoder().decode(result.content)
const config = JSON.parse(text)
})
EngineInfo Component
Access frame-level timing:
import { EngineInfo } from '@dcl/sdk/ecs'
engine.addSystem(() => {
const info = EngineInfo.getOrNull(engine.RootEntity)
if (info) {
console.log(info.frameNumber, info.tickNumber, info.totalRuntime)
}
})
Restricted Actions
These require player interaction before they can execute. Import from ~system/RestrictedActions:
import {
movePlayerTo,
teleportTo,
triggerEmote,
changeRealm,
openExternalUrl,
openNftDialog,
triggerSceneEmote,
copyToClipboard,
setCommunicationsAdapter
} from '~system/RestrictedActions'
// Move player within scene bounds
movePlayerTo({ newRelativePosition: { x: 8, y: 0, z: 8 } })
// Teleport to coordinates in Genesis City
teleportTo({ worldCoordinates: { x: 50, y: 70 } })
// Play a built-in emote
triggerEmote({ predefinedEmote: 'wave' })
// Open URL in browser (prompts user)
openExternalUrl({ url: 'https://decentraland.org' })
// Open NFT detail dialog
openNftDialog({ urn: 'urn:decentraland:ethereum:erc721:0x06012c8cf97BEaD5deAe237070F9587f8E7A266d:558536' })
// Copy text to clipboard
copyToClipboard({ value: 'Hello from Decentraland!' })
// Change realm
changeRealm({ realm: 'other-realm.dcl.eth', message: 'Join this realm?' })
Timers
setTimeout / setInterval are supported via the QuickJS runtime polyfill:
setTimeout(() => console.log('delayed'), 2000)
const id = setInterval(() => console.log('tick'), 1000)
clearInterval(id)
System-based timers (recommended for game logic — synchronized with the frame loop):
let elapsed = 0
engine.addSystem((dt: number) => {
elapsed += dt
if (elapsed >= 3) {
elapsed = 0
// Do something every 3 seconds
}
})
Component.onChange() Listener
React to component changes on any entity:
Transform.onChange(engine.PlayerEntity, (newValue) => {
if (newValue) {
console.log('Player moved to', newValue.position)
}
})
Utility: removeEntityWithChildren
Recursively remove an entity and all its children:
import { removeEntityWithChildren } from '@dcl/sdk/ecs'
removeEntityWithChildren(engine, parentEntity)
Portable Experiences
Scenes that persist across world navigation:
import { spawn, kill, exit, getPortableExperiencesLoaded } from '~system/PortableExperiences'
// Spawn a portable experience by URN
const result = await spawn({ urn: 'urn:decentraland:entity:bafk...' })
// List currently loaded portable experiences
const loaded = await getPortableExperiencesLoaded({})
// Kill a specific portable experience
await kill({ urn: 'urn:decentraland:entity:bafk...' })
// Exit self (if this scene IS a portable experience)
await exit({})
Testing Framework
SDK7 includes a testing framework for automated scene tests:
import { test, assert, assertEquals, assertComponentValue } from '@dcl/sdk/testing'
import { setCameraTransform } from '@dcl/sdk/testing'
test('cube is at correct position', async (context) => {
// Set up camera for the test
setCameraTransform({ position: { x: 8, y: 1, z: 8 } })
// Wait for systems to run
await context.helpers.waitNTicks(2)
// Assert component values
assertComponentValue(cubeEntity, Transform, {
position: Vector3.create(8, 1, 8)
})
// Basic assertions
assert(Transform.has(cubeEntity), 'Entity should have Transform')
assertEquals(1 + 1, 2)
})
Run tests with:
npx @dcl/sdk-commands test
Best Practices
- Always wrap async code in
executeTask()— bare promises will be silently dropped - Use
signedFetch(not plainfetch) when your backend needs to verify the player's identity - Prefer system-based timers over
setTimeout/setIntervalfor game logic — they stay in sync with the frame loop - Check
realm.realmInfo?.isPreviewto detect preview mode and enable debug features - Use
readFile()for data files (JSON configs, level data) deployed alongside the scene removeEntityWithChildren()is essential when cleaning up complex entity hierarchies
For complete executeTask patterns, all RestrictedActions, realm detection, and portable experiences, see {baseDir}/references/runtime-apis.md.
More from dcl-regenesislabs/opendcl
optimize-scene
Optimize Decentraland scene performance. Scene limit formulas (triangles, entities, materials, textures, height per parcel count), object pooling, LOD patterns, texture optimization, system throttling, and asset preloading. Use when the user wants to optimize, improve performance, fix lag, reduce load time, check limits, or reduce entity/triangle count. Do NOT use for deployment (see deploy-scene).
51game-design
Plan and design Decentraland games and interactive experiences. Scene limit formulas, performance budgets, texture requirements, asset preloading, state management patterns (module-level, component-based, state machines), object pooling, UX/UI guidelines, input design, and MVP planning. Use when the user wants game design advice, scene architecture, performance planning, or help structuring a game. Do NOT use for specific implementation (see add-interactivity, build-ui, multiplayer-sync).
30audio-video
Add sound effects, music, audio streaming, and video players to Decentraland scenes. Covers AudioSource (local files), AudioStream (streaming URLs), VideoPlayer (video surfaces), video events, and media permissions. Use when the user wants sound, music, audio, video screens, radio, or media playback. Do NOT use for 3D model animations (see animations-tweens).
30add-3d-models
Add 3D models (.glb/.gltf) to a Decentraland scene using GltfContainer. Covers loading, positioning, scaling, colliders, parenting, and browsing 5,700+ free assets from the OpenDCL catalog. Use when the user wants to add models, import GLB files, find free 3D assets, or set up model colliders. Do NOT use for materials/textures (see advanced-rendering) or model animations (see animations-tweens).
28nft-blockchain
NFT display and blockchain interaction in Decentraland. NftShape (framed NFT artwork), wallet checks (getPlayer, isGuest), signedFetch (authenticated requests), smart contract interaction (eth-connect, createEthereumProvider), and RPC calls. Use when the user wants NFTs, blockchain, wallet, smart contracts, Web3, crypto, or token gating. Do NOT use for player avatar data or emotes (see player-avatar).
27build-ui
Build 2D screen-space UI for Decentraland scenes using React-ECS (JSX). Create HUDs, menus, health bars, scoreboards, dialogs, buttons, inputs, and dropdowns. Use when the user wants screen overlays, on-screen UI, HUD elements, menus, or form inputs. Do NOT use for 3D in-world text (see advanced-rendering) or clickable 3D objects (see add-interactivity).
26