Hi everyone,
I’ve been working on an open-source project called ThreeJSON, and I recently released its first alpha version.
ThreeJSON is a JSON-driven declarative scene runtime for Three.js.
The project started from a question:
What if a Three.js scene were not primarily a body of application code, but persistent, addressable and mutable data?
In other words:
Describe 3D worlds as data, and let Three.js run them.


Try it here:
-
Live demos:
-
GitHub / Documentation: nnrj/threejson: ThreeJSON is a JSON-driven declarative scene runtime for Three.js, designed for persistent, mutable and extensible 3D worlds — from human-authored scenes to AI and Agent-driven generation and control.
-
npm:
npm install threejson
ThreeJSON is not intended to replace Three.js. Three.js remains the rendering and 3D foundation underneath it.
It is also not meant to be just a JSON wrapper around a few constructors.
The goal is to build a data-driven pipeline:
Description
→ Parsing
→ Object Creation
→ Scene Assembly
→ Runtime
→ Mutation
→ Extension
→ Persistence
Scene as Data
A ThreeJSON scene can describe not only meshes, but also cameras, renderers, controls, lights, materials, textures, external models, animations, events, resources and domain-specific objects.
A small object can be described like this:
{
"objType": "Box",
"position": [0, 1, 0],
"size": [2, 2, 2]
}
Of course, saving a few lines of new THREE.Mesh() is not the interesting part.
The interesting part is that the scene now has a structured representation.
That representation can be:
-
saved and loaded again;
-
stored in a database;
-
sent over a network;
-
packaged together with resources;
-
edited by tools;
-
mutated incrementally;
-
generated by AI;
-
operated on by an Agent.
ThreeJSON currently supports both a normalized machine-oriented JSON format and a more human-friendly JSON format. The latter is normalized into the same internal pipeline before deployment.
The idea is to make scenes understandable to both humans and machines.
Runtime as Engine
A declarative scene is not very useful if the JSON is parsed once and then forgotten.
So ThreeJSON also provides a runtime layer around the resulting Three.js world.
Objects created from descriptors are registered and can be addressed through stable identities such as:
-
threeJsonId -
uuid -
refName -
name
This means an application does not always need to traverse the entire scene graph or maintain its own object lookup system.
An editor, backend, business system or Agent can express operations like:
find cabinet-42
→ move it to (13, 20, 66)
→ replace its texture
→ hide it
without rebuilding the whole scene.
The runtime layer includes capabilities such as:
-
object registration and lookup;
-
runtime transforms and property changes;
-
resource management and reuse;
-
render/update scheduling;
-
object and scene lifecycle hooks;
-
event binding;
-
animation updates;
-
cleanup and disposal.
This is one of the architectural questions I would especially like feedback on:
Is there value in treating the running scene as a managed, addressable runtime, instead of only as a raw Three.js scene graph owned entirely by application code?
JSON is the canonical descriptor, but Object3D remains free
One design problem appeared very early:
If JSON is the source of truth, does that make the runtime rigid?
I did not want that.
ThreeJSON uses a two-layer model:
JSON descriptor
persistent / canonical state
↓
Three.js Object3D
live runtime state
The JSON descriptor can be used for persistence, synchronization, patching and reloading.
But the live Object3D is still a real Three.js object.
A physics engine can move it. AnimationMixer can animate it. A game loop can update it. Custom application code can manipulate it directly.
When the application needs to return to the data-driven flow, runtime state can be coordinated back with the descriptor.
The goal is not to build an abstraction that fights against Three.js.
The goal is to preserve the freedom of the Three.js runtime while adding a persistent and machine-operable representation above it.
Objects can have behavior, not just appearance
ThreeJSON can describe more than geometry and materials.
Objects can also have:
-
declarative animations;
-
model-embedded animations;
-
mouse and keyboard events;
-
built-in actions;
-
EventScript logic;
-
custom JavaScript behavior;
-
runtime commands.
So scene data can gradually represent:
Object + State + Animation + Event + Behavior
For example, an object can rotate continuously, react to a double click, trigger an action, execute a script, or be changed later through the runtime API.
Domain as Extension
I don’t think every higher-level 3D concept should become another primitive object type in the core.
A cabinet is not a primitive. A cargo ship is not a primitive. A robot, lab instrument, factory device or data-center rack should not have to be hard-coded into the engine.
So ThreeJSON has a Domain system.
A domain can define its own:
-
model definitions;
-
JSON structures;
-
handlers;
-
APIs;
-
behaviors;
-
visual conventions.
For example, the current project includes domain-oriented work around cabinet/data-center scenes and port scenes.
The core runtime does not need to know what a cabinet or cargo ship means. It only provides the mechanism through which that domain participates in scene creation and runtime behavior.
The intended structure is:
ThreeJSON Core
+
built-in objects
+
your own domain model system
+
your own runtime behavior
+
third-party extensions
This is why one of the principles of the project is:
Domain as Extension
Third-party systems should remain third-party systems
The same principle applies to physics engines and other large external capabilities.
ThreeJSON is not trying to reimplement everything.
The project provides extension mechanisms, lifecycle hooks and frame hooks so optional systems can participate in the runtime.
A physics engine should remain a physics engine. ThreeJSON’s job is to provide a clean place for it to connect to a data-described scene and its lifecycle.
Users should also be able to create their own object types, controls, registries and domain systems instead of being limited to what the project already provides.
Agent as Operator
This is the part that made me continue the project even after AI became very good at writing Three.js code.
At one point I asked myself:
Why build this at all? Why not just ask AI to generate native Three.js code?
My current answer is:
Generating code and operating a world are different problems.
Imagine an AI has generated a large Three.js application.
Later, the user says:
“Move the third cabinet in the second row 1.5 meters to the left and open its front door.”
With source-code generation, the AI may need to inspect the project, understand object relationships, find the right variables, modify code and hope the change does not break something else.
In a structured world, the flow can instead be:
User request
→ AI inspects scene/object information
→ AI resolves the target object
→ AI generates a small command
→ ThreeJSON parses the command
→ runtime mutation API performs the change
Conceptually:
find cabinet-03
→ translate x by -1.5
→ find front-door
→ rotate to open angle
The AI does not need to regenerate the world.
It only describes the change.
ThreeJSON explores several levels of AI / Agent interaction.
JSON Patch
If only one part of a descriptor needs to change, an Agent can produce a small JSON Patch instead of regenerating the full scene.
For example:
[
{
"op": "replace",
"path": "/position/0",
"value": 12.5
}
]
This can update the canonical descriptor and participate in the runtime synchronization flow.
Runtime commands
For operations that are naturally imperative, AI can generate compact command sequences.
The intended flow is:
natural-language request
→ AI-generated command
→ ThreeJSON command parser
→ runtime mutation API
→ live scene changes
This is useful for operations such as:
select object A
move it beside object B
rotate it 90 degrees
replace its material
hide objects matching a condition
Spatial context
A useful Agent needs more than object names.
The scene can expose structured object and spatial information so AI can reason about positions, bounds and nearby objects rather than guessing from source code.
This makes requests such as:
“Place this machine between A and B without overlapping the wall.”
a structured scene-operation problem, not a code-editing problem.
The goal is not to claim that AI can already solve every 3D task perfectly.
The goal is to give AI a smaller, more precise and safer action surface than “rewrite the Three.js application.”
Editor, player and examples
The repository is not only the core library.
It currently includes:
-
a scene editor;
-
a scene player;
-
room / cabinet examples;
-
port scene examples;
-
many standalone HTML demos and tutorials;
-
runtime mutation examples;
-
event and script examples;
-
domain examples;
-
import/export related experiments.
The editor is useful both as a scene-building tool and as a place to inspect and test runtime capabilities.
The player provides a lighter way to load and run scenes.
The project also explores packaging scenes with resources and importing/exporting external 3D models, so that ThreeJSON does not become an isolated ecosystem.
What could this be useful for?
Some possible use cases I am interested in:
-
digital twins;
-
data centers, factories, ports, warehouses and campuses;
-
AI-assisted 3D scene generation and editing;
-
procedural game map generation;
-
scientific simulation and visualization;
-
robot training environments;
-
structured generation of simple 3D-printable models;
-
browser-based 3D editors;
-
Agent-operated 3D worlds.
Some of these are already close to the current direction of the project. Others are still hypotheses that need real experiments and feedback.
The philosophy
I eventually condensed the project into four lines:
Scene as Data — The scene is data.
Runtime as Engine — The runtime is the engine.
Domain as Extension — Domains are extensions.
Agent as Operator — Agents are operators.
Or, in plain English:
You define the world. AI creates and reshapes it. ThreeJSON brings it to life.
The project is still alpha.
I am not posting this because I think the architecture is finished. I am posting it because I would like people who actually build things with Three.js to challenge it.
I would especially appreciate feedback on these questions:
-
Does this abstraction solve a real problem, or is it too much structure on top of Three.js?
-
Where does ThreeJSON fight against Three.js instead of helping it?
-
Is the canonical-descriptor / live-Object3D split understandable?
-
Does the Domain system make sense?
-
Is the AI command / JSON Patch approach useful, or would you design the Agent interface differently?
-
Which part would you remove if you wanted a much smaller core?
Critical feedback is very welcome.
Thanks for taking a look.


