---
title: "dynamicAsset (asset type)"
description: "A prompt-driven, self-regenerating 3D asset. A <name>.dynamicAsset/ folder holds a text prompt.json; the asset's model is generated from that prompt by the @builtin::services.meshy text-to-3D…"
section: "Types"
slug: "types-dynamicasset"
canonical: "https://origozero.ai/docs/types-dynamicasset"
updated: "2026-07-31T07:12:26.563223169+00:00"
tags: ["asset-type", "reference"]
---

# dynamicAsset (asset type)

This type is the **reference example** for the asset-type `onChange`
change-callback (the type-level analogue of a component's
`onAssetReload`). Read its `behavior.luau` to see the pattern end to end.

## Folder shape

```
<name>.dynamicAsset/
├── prompt.json        { "prompt": "a mossy stone golem" }
├── .metadata          generation status + start date + last prompt
├── README.md
├── model/             (after first generation)
│   ├── model.glb       the current generated mesh
│   └── model.json      manifest (prompt, date, service handle)
└── versions/          (after the prompt changes at least once)
    ├── v1/
    │   ├── model/model.glb
    │   ├── prompt.json  the prompt that produced this version
    │   └── meta.json
    └── v2/ …
```

## Lifecycle

1. **Generate** — `ref:generate("a mossy stone golem")` writes the prompt,
   sets `.metadata.generationStatus = "inflight"` + `generationStartedAt`,
   and launches Meshy. The GLB is written straight into `model/`.
2. **Status** — `ref:status()` reads the state from `.metadata`:
   `status` (`empty`/`inflight`/`succeeded`/`failed`), `stage` (live Meshy
   stage), `prompt`, `startedAt`, `finishedAt`, `error`, `handle`. Use it
   to find finished / failed / in-flight assets at a glance.
3. **Edit the prompt → regenerate** — writing `prompt.json` (by hand,
   `vfs.write`, `ref:setPrompt(...)`, or a pull) fires `onChange`. If the
   prompt actually changed, the current `model/` is archived into
   `versions/v<N>/` (incrementing) and a fresh generation starts.

## Methods (`AssetRef<dynamicAsset>`)

| Method | What it does |
|--------|--------------|
| `:generate(prompt?)` | Generate/regenerate now (async). Returns the status table. |
| `:status()` | Read generation status from `.metadata`. |
| `:getPrompt()` / `:setPrompt(text)` | Read / set `prompt.json` (set triggers regeneration). |
| `:modelPath()` | VFS path of the current GLB, or nil. |
| `:versions()` | Archived version indices (oldest first). |
| `:spawn(name?, x?, y?, z?)` | Spawn the current model into the scene. |

## Setup

The Meshy service reads its key from the `MESHY_API_KEY` environment
variable (set before launching the engine). Without it, generation fails
with a clear message and `:status().status == "failed"`. Get a key at
https://www.meshy.ai/api.

## Usage

```luau
asset.create("dynamicAsset", "Golem")
local golem = asset.resolve("Golem", "dynamicAsset")
golem:generate("a mossy stone golem, low-poly")
-- … wait …
print(golem:status().status)        -- "succeeded"
golem:spawn("golem", 0, 0, 0)

-- Change the prompt → archives v1, regenerates:
golem:setPrompt("a bronze golem, ornate")
print(golem:versions())             -- { 1 }
```

## Notes

- Generation is async and can take minutes. `onChange` returns immediately;
  the model appears when `:status().status` flips to `"succeeded"`.
- `onChange` is convergent: it only acts when the prompt differs from the
  last generated prompt and queues edits made mid-generation
  (`pendingPrompt`), so it never loops on its own writes.
