Reusable compositions, overrides, and ID expansion.
Prefabs & instances
Most games place the same kind of object many times: a doorway in every wall, an enemy in every wave, a pickup in every room. A prefab is a saved composition of entities that you edit once and place anywhere. Each placement is an instance — a live copy that follows the prefab unless you deliberately change it. This page shows why prefabs exist, how to place one in a scene, how overrides work, and how IDs expand.
Why prefabs exist
Without prefabs you would copy an entity block into every scene and edit every copy when the design changes. Prefabs give you:
- Reuse — define a doorway once and place it in any scene.
- Edit once — change the prefab document and every instance that does not override that field picks up the new value.
- Local variation — per-instance overrides cover the cases that really differ, like one door starting locked.
A prefab is its own file under prefabs/ with a prefab:// identity, for example prefab://doorway. Full document shape: Prefab files.
A prefab document
{
"format_version": 1,
"id": "prefab://characters/player",
"entities": [
{
"id": "body",
"components": {
"Transform3D": { "position": [0, 0, 0] },
"CapsuleCollider3D": { "radius": 0.3, "height": 1.8 },
"CharacterController3D": { "slope_limit": 45 }
},
"children": [
{
"id": "visual",
"components": {
"MeshRenderer": { "model": "asset://models/hero" }
}
}
]
}
]
}
format_version— the document format number. Present on every durable document.id— the stable prefab identity,prefab://characters/player. Scenes reference this, never the file path.entities— the root entities of the composition. Here one root,body, with a transform, a character controller, and a childvisualthat renders the hero model.- Field values inside
componentsbehave exactly as in a scene: omitted fields use component defaults (see Entities & components).
Placing an instance in a scene
List prefab placements alongside ordinary entities in entities. An entry with prefab expands a reusable source rather than defining its own component set:
{
"format_version": 1,
"id": "scene://game/main",
"entities": [
{
"id": "player",
"prefab": "prefab://characters/player",
"overrides": {
"body": {
"components": {
"Transform3D": { "position": [2, 0, 0] }
}
}
}
}
]
}
id: "player"— the placement's prefix. Scripts address expanded entities such asplayer/body.prefab: "prefab://characters/player"— which prefab to place. A URI reference, not a path.overrides— a map of per-instance changes keyed by the prefab's local entity IDs. Herebodygets a different start position.
Scene files may also group placements in an instances array; the semantics are the same. Details: Scene files.
How overrides combine
Overrides are layered on top of the prefab document. Each field follows one rule, and knowing the rule tells you exactly what the instance will contain:
| Override value | Result | Example |
|---|---|---|
| Inherit — field absent from the override | The prefab's value is used unchanged. | Override only position; CharacterController3D.slope_limit stays 45. |
| Merge — object values | Keys combine recursively; override keys win per key, other prefab keys survive. | Override components.Transform3D.position; prefab's Transform3D.rotation is kept. |
| Replace — array values | The override array replaces the prefab array as a whole. | Override tags: ["boss"]; the prefab's tags list is gone, not appended to. |
Remove — null |
The field, component, or entity is deleted from this instance. | "visual": null removes the child entity from this instance only. |
| Single field — dotted keys | A dotted key sets exactly one nested field without writing the surrounding objects. | "body.Transform3D.position": [2, 0, 0] sets only that field. |
Worked example. Prefab body has position [0, 0, 0], slope_limit 45, and the instance override is:
"overrides": {
"body": {
"components": {
"Transform3D": { "position": [2, 0, 0] }
},
"tags": ["boss"]
},
"visual": {
"components": { "MeshRenderer": null }
}
}
Transform3D.position→[2, 0, 0]— merged in, replacing the prefab's position.Transform3D.rotationand any other transform fields — inherited from the prefab because they are absent from the override.CharacterController3D— inherited entirely; the merge touched other components only.- The
visualchild'sMeshRenderer— removed because its override isnull; the body and its controller remain. tags→["boss"]— replaced as a whole because it is an array.
How IDs expand
When a prefab is instantiated, every entity ID inside it is prefixed with the instance ID so copies never collide. Local ID body under instance player becomes player/body; local visual becomes player/visual. Override keys use the local name (body), while runtime lookups use the expanded name (player/body). Expansion is identity bookkeeping only — nested children in the prefab are still ordinary hierarchy, not a separate ID syntax.
Child IDs remain document-wide: a child visual under body expands to player/visual, not player/body/visual. Nest entities that share a spatial domain under their owner. The owner needs a transform; a child without one receives an identity transform in that domain. Conflicting explicit parents, mixed transform domains, duplicate IDs, and nesting beyond 128 levels are validation errors. Explicit parent links are for relationships a local source tree cannot express.
Nested prefabs
A prefab may place other prefabs, so compositions stay modular: a prefab://characters/hero can contain an instance of prefab://weapons/sword. Override and ID expansion rules apply level by level, and the expanded IDs keep accumulating prefixes so every leaf stays unique. A prefab that eventually contains itself is a cycle; validation rejects it and reports the chain of files involved so you can break the loop.
Creating instances at runtime
Placing prefabs in a scene covers designed layouts. When gameplay needs an object mid-run — a dropped item, a spawned enemy — Lua can instantiate a prefab directly:
local Prefab = require("demi.prefab")
local id = Prefab.instantiate("prefab://characters/player", {
id = "player_2",
position = { 2, 0, 0 }
})
require("demi.prefab")— imports the prefab service; the call returns its function table.Prefab.instantiate(uri, options)— places a prefab at runtime.options.idis required; optionalposition,overrides, andpooledconfigure this instance.- Returns the instance ID string, or
nilon failure. Expanded entity IDs such asplayer_2/bodyaddress its contents.
Prefab.release(instance_id) releases the instance. A pooled release disables its entities; the next matching instantiate resets them from prefab source before enabling them. Without pooling, release queues destruction.
Return shapes, options for nested IDs, and cleanup are covered in Scene & prefab API.
Next steps
- Prefab files — the complete prefab document format.
- Scene & prefab API — loading scenes and instantiating from Lua.
- Entities & components — what every prefab is built from.
To turn existing scene objects into a reusable prefab, choose Scene → Create prefab from selection. Replacement with a linked instance is enabled by default and preserves the existing entity IDs. Disable the checkbox to create an unlinked copy. See Prefab files for the explicit identity mapping and Undo behavior.
Open a prefab from Assets to edit it in the Prefab stage. Saving its source refreshes composed instances in other open authoring documents, including after Undo/Redo followed by Save. Scene overrides, unsaved scene edits, selection and command history are retained. Play owns its separate runtime world; stop and start Play to instantiate the newly saved source there.